# Django 3.0.9.dev - dokumentacja

# Pisanie pierwszej aplikacji Django, część 4.

Ten tutorial zaczyna się, gdzie skończył się Tutorial 3 </intro/tutorial03>. Kontynuujemy prace nad aplikacją web-ankiet i skupimy się na przetwarzaniu formularza i przycinaniu naszego kodu.

Gdzie szukać pomocy:

Jeśli masz trudności w przejściu tego tutorialu, przejdź do sekcji Uzyskiwanie pomocy często zadawanych pytań.

## Napisz mały formularz

Zaktualizujmy nasz szablon szczegółów ankiety („polls/detail.html”) z ostatniego tutoriala, aby zawierał element HTML `<form>`:

polls/templates/polls/detail.html

    <h1>{{ question.question_text }}</h1>

    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

    <form action="{% url 'polls:vote' question.id %}" method="post">
    {% csrf_token %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
    {% endfor %}
    <input type="submit" value="Vote">
    </form>

Krótkie sprawozdanie:

- Powyższy szablon wyświetla przycisk opcji dla każdej z odpowiedzi. Atrybut `value` każdego przycisku opcji to ID związanej z pytaniem odpowiedzi. Atrybut `name` każdego przycisku opcji to `"choice"`. To znaczy, że kiedy ktoś wybierze jeden z przycisków opcji i wyśle formularz, formularz wyśle dane POST `choice=#`, gdzie # jest ID wybranej odpowiedzi. To jest podstawowa idea formularzy HTML.
- Ustawiamy atrybut `action` formularza na `{% url 'polls:vote' question.id %}` i ustawiamy `method="post"`. Użycie `method="post"` (w opozycji do `method="get"`) jest bardzo ważne, ponieważ akt wysłania formularza zmieni dane po stronie serwera. Kiedykolwiek tworzysz formularz, który zmienia dane po stronie serwera, używaj `method="post"`. Ta wskazówka nie dotyczy tylko Django; jest dobrą praktyką w web-dewelopmencie.`
- `forloop.counter` mówi ile razy tag `for` przeszedł przez swoją pętlę
- Jako że stworzymy formularz POST (który może mieć efekt modyfikacji danych), musimy martwić się o cross-site request forgeries. Na szczęście, nie musisz przejmować się za bardzo, bo Django oferuje pomocny system przed nimi chroniący. W skrócie: wszystkie formularze POST, które są skierowane na wewnętrzne URL-e powinny używać szablonowego taga `{% csrf_token %}`.

Teraz stwórzmy widok Django, który obsługuje wysłane dane i coś z nimi robi. Pamiętaj, w Tutorialu 3, stworzyliśmy URLconfa dla aplikacji ankietowej, który zawiera tę linię:

polls/urls.py

    path('<int:question_id>/vote/', views.vote, name='vote'),

Stworzyliśmy także zaślepkową implementację funkcji `vote()`. Stwórzmy prawdziwą wersję. Dodaj następujący kod do `polls/views.py`:

polls/views.py

    from django.http import HttpResponse, HttpResponseRedirect
    from django.shortcuts import get_object_or_404, render
    from django.urls import reverse

    from .models import Choice, Question
    # ...
    def vote(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        try:
            selected_choice = question.choice_set.get(pk=request.POST['choice'])
        except (KeyError, Choice.DoesNotExist):
            # Redisplay the question voting form.
            return render(request, 'polls/detail.html', {
                'question': question,
                'error_message': "You didn't select a choice.",
            })
        else:
            selected_choice.votes += 1
            selected_choice.save()
            # Always return an HttpResponseRedirect after successfully dealing
            # with POST data. This prevents data from being posted twice if a
            # user hits the Back button.
            return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

Ten kod zawiera kilka rzeczy, których jeszcze nie omówiliśmy w tym tutorialu:

- `request.POST` to słownikowy obiekt, który daje dostęp do wysłanych danych według nazwy klucza. W tym przypadku `request.POST['choice']` zwraca ID wybranej odpowiedzi jako stringa. Wartości `request.POST` są zawsze stringami.

  Zwróć uwagę, że Django ma także `request.GET` dające dostęp do danych GET w ten sam sposób – ale my specjalnie używamy `request.POST` w naszym kodzie, aby upewnić się, że dane są zmieniane tylko przez wywołanie POST.

- `request.POST['choice']` zgłosi `KeyError` jeśli atrybutu `choice` nie było wśród danych POST. Powyższy kod sprawdza `KeyError` i ponownie wyświetla formularz pytania z komunikatem błędu, jeśli `choice` nie został wskazany.

- Po zwiększeniu licznika odpowiedzi, kod zwraca `HttpResponseRedirect` zamiast normalnej `HttpResponse`. `HttpResponseRedirect` bierze jeden argument: URL, do którego użytkownik będzie przekierowany (zobacz następny punkt, aby dowiedzieć się, jak w tym przypadku kontruujemy URL-e).

  Tak jak wskazuje komentarz powyżej w Pythonie, powinieneś zawsze zwracać `HttpResponseRedirect` po poradzeniu sobie z danymi POST. Ta wskazówka nie jest szczególna dla Django; to ogólnie dobra praktyka w web-dewelopmencie.

- Używamy funkcji `reverse()` w konstruktorze `HttpResponseRedirect` w tym przykładzie. Ta funkcja pomaga uniknąć konieczności hardkodowania URL-i w funkcji widoku. Daje jej się nazwę widoku, któremu chcemy przekazać kontrolę i zestaw zmiennych z wzorca URL-a, który prowadzi do tego widoku. W tym przypadku, używając URLconfa, którego napisaliśmy w Tutorialu 3, to wywołanie `reverse()` zwróci taki ciąg znaków:

      '/polls/3/results/'

  gdzie `3` jest wartością `question.id`. Ten przekierowany URL później wywoła widok `'results'`, aby wyświetlić ostateczną stronę.

Jak wspomniano w Tutorialu 3, `request` jest obiektem `HttpRequest`. Po więcej informacji na temat obiektów `HttpRequest`, zobacz dokumentację żądań i odpowiedzi.

Po tym, jak ktoś zagłosuje w danym pytaniu, widok `vote()` przekieruje go na stronę wyników dla pytania. Napiszmy ten widok:

polls/views.py

    from django.shortcuts import get_object_or_404, render

    def results(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        return render(request, 'polls/results.html', {'question': question})

Jest prawie identyczny z widokiem `detail()` z Tutoriala 3. Jedyna różnica to nazwa szablonu. Naprawimy tę redundancję później.

Teraz stwórzmy szablon `polls/results.html`:

polls/templates/polls/results.html

    <h1>{{ question.question_text }}</h1>

    <ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
    {% endfor %}
    </ul>

    <a href="{% url 'polls:detail' question.id %}">Vote again?</a>

Teraz wejdź na `/polls/1/` w swojej przeglądarce i zagłosuj w ankiecie. Powinieneś zobaczyć stronę wyników, która aktualizuje się za każdym razem, kiedy oddasz głosz. Jeśli wyślesz formularz bez wybranej odpowiedzi, powinieneś zobaczyć komunikat błędu.

Informacja

Jest mały problem w kodzie naszego widoku `vote()`. Najpierw dostaje obiekt `selected_choice` z bazy danych, następnie oblicza nową wartość `` votes i dalej zapisuje ją z powrotem do bazy danych. Jeśli dwóch użytkowników twojej witryny spróbuje zagłosować dokładnie w tym samym czasie, może to pójść źle: Ta sama wartość, powiedzmy 42, będzie pobrana dla `votes`. Następnie dla obu użytkowników nowa wartość 43 jest obliczona i zapisana, ale oczekiwaną wartością jest 44.

Nazywa się to hazardem (race condition). Jeśli jesteś zainteresowany, możesz przeczytać Avoiding race conditions using F(), aby dowiedzieć się, jak możesz rozwiązać ten problem.

## Użycie widoków generycznych: Im mniej kodu tym lepiej

Widoki `detail()` (z Tutoriala 3) i `results()` są bardzo krótkie – i, jak wspomnieliśmy wcześniej, redundantne. Widok `index()`, który wyświetal listę ankiet, jest podobny.

Te widoki reprezentują częsty przypadek w podstawowym web dewelopmencie: pobieranie danych z bazy danych na podstawie parametru podanego w URL, ładownie szablonu i zwracanie wyrenderowanego szablonu. Z powodu, że jest to tak powszechne, Django dostarcza skrót, nazwany systemem „generycznych widoków”.

Widoki generyczne wyodrębniają powszechne wzorce do punktu, gdzie nie potrzebujesz nawet pisać pythonowego kodu, aby napisać aplikację.

Przekonwertujmy naszą aplikację ankietową, aby używała systemu widoków generycznych, abyśmy mogli usunąć sporo naszego własnego kodu. Musimy podjąć kilka kroków, aby dokonać konwersji. Będziemy:

1. Konwertować URLconf.
2. Usuwać stare, niepotrzebne widoki.
3. Wprowadzać nowe widoki oparte na generycznych widokach Django.

Kontynuuj czytanie, aby dowiedzieć się szczegółów.

Dlaczego podmiana kodu?

W ogólności, pisząc aplikację Django, ocenisz, czy widoki generyczne są dobre do twojego problemu i będziesz używał ich od początku zamiast robić refactoring kodu w połowie pracy. Ten tutorial specjalnie był skupiony na pisaniu widoków „w trudny sposób” aż do teraz, aby skupić się podstawowych konceptach.

Powinieneś znać podstawową matematykę zanim zaczniesz używać kalkulatora.

### Modyfikacja URLconf

Najpierw otwórz URLconfa w `polls/urls.py` i zmień go tak:

polls/urls.py

    from django.urls import path

    from . import views

    app_name = 'polls'
    urlpatterns = [
        path('', views.IndexView.as_view(), name='index'),
        path('<int:pk>/', views.DetailView.as_view(), name='detail'),
        path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
        path('<int:question_id>/vote/', views.vote, name='vote'),
    ]

Zwróć uwagę, że nazwa dopasowanego wzorca w ciągach znaków ścieżki drugiego i trzeciego wzorca zmieniła się z `<question_id>` na `<pk>`.

### Modyfikacja widoków

Następnie usuniemy nasze stare widoki `index`, `detail` i `results` i użyjemy zamiast nich generycznych widoków Django. Aby to zrobić, otwórz plik `polls/views.py` i zmień go w ten sposób:

polls/views.py

    from django.http import HttpResponseRedirect
    from django.shortcuts import get_object_or_404, render
    from django.urls import reverse
    from django.views import generic

    from .models import Choice, Question

    class IndexView(generic.ListView):
        template_name = 'polls/index.html'
        context_object_name = 'latest_question_list'

        def get_queryset(self):
            """Return the last five published questions."""
            return Question.objects.order_by('-pub_date')[:5]

    class DetailView(generic.DetailView):
        model = Question
        template_name = 'polls/detail.html'

    class ResultsView(generic.DetailView):
        model = Question
        template_name = 'polls/results.html'

    def vote(request, question_id):
        ... # same as above, no changes needed.

Używamy tutaj dwóch widoków generycznych: `ListView` i `DetailView`. Odpowiednio, te dwa widoki abstrahują koncepty „wyświetlania listy obiektów” i „wyświetlania strony szczegółów dla wybranego typu obiektu”.

- Każdy widok generyczny potrzebuje wiedzieć, na podstawie jakiego modelu będzie działał. Ta informacja jest dostarczona przez atrybut `model`.
- Widok generyczny `DetailView` spodziewa się wartości klucza głównego zebranego z URL-a i nazwanego `"pk"`, więc zmieniliśmy `question_id` na `pk` dla widoków generycznych.

Domyślnie widok generyczny `DetailView` używa szablonu o nazwie `<app name>/<model name>_detail.html`. W naszym przypadku użyłby szablonu `"polls/question_detail.html"`. Atrybut `template_name` jest używany, by kazać Django użyć wskazanego szablonu zamiast samo-wygenerowanej domyślnej nazwy szablonu. Wskazujemy również `template_name` dla widoku listy `results` – to powoduje, że widok wyników i widok szczegółów mają inny wygląd po wyrenderowaniu, mimo że oba są `DetailView` pod maską.

Podobnie widok generyczny `ListView` używa domyślnego szablonu o nazwie `<app name>/<model name>_list.html`; używamy `template_name`, aby powiedzieć `ListView`, by używała naszego istniejącego szablonu `"polls/index.html"`.

W poprzednich częściach tutoriala szablonom dawaliśmy kontekst, który zawierał zmienne kontekstu `question` i `latest_question_list`. Dla `DetailView` zmienna `question` jest dostarczona automatycznie – ponieważ używamy modelu Django (`Question`), Django może wyznaczyć odpowiednią nazwę dla zmiennej kontekstowej. Jednak dla ListView automatycznie generowaną zmienną kontekstową jest `question_list`. Aby to nadpisać nadajemy wartość atrybutowi `context_object_name`, wskazując, że chcemy użyć zamiast niej `latest_question_list`. Alternatywnym podejściem byłoby zmienienie szablonów, aby zgadzały się z nowymi domyślnymi zmiennymi kontekstowymi – ale dużo prościej jest powiedzieć Django, aby używało zmiennej, jakiej chcesz.

Uruchom serwer i użyj nowej aplikacji ankietowej opartej na widokach generycznych.

Po wszystkie szczegóły widoków generycznych, sprawdź dokumentację widoków generycznych.

Kiedy już czujesz się dobrze z formularzami i widokami generycznymi, przeczytaj część 5 tego tutoriala, aby nauczyć się testowania naszej ankietowej aplikacji.