Open Source

Twoja pierwsza aplikacja w Django, część 4

Ten tutorial zaczyna się tam, gdzie kończy się tutorial 3. Kontynuujemy tworzenie aplikacji ankiety, w tej części skupimy się na prostym przetwarzaniu formularzy oraz upraszczaniu naszego kodu.

Piszemy prosty formularz

Uaktualnijmy nasz szablon zawierający szczegóły ankiety (“polls/detail.html”) z poprzedniego tutoriala, tak, aby szablon zawierał znacznik HTML <form>:

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

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

<form action="/polls/{{ poll.id }}/vote/" method="post">
{% for choice in poll.choice_set.all %}
    <input type="radio" name="choice"
        id="choice{{ forloop.counter }}"
        value="{{ choice.id }}"
    />
    <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
{% endfor %}
<input type="submit" value="Głosuj" />
</form>

Szybkie wyjaśnienie:

  • Powyższy szablon wyświetla przycisk typu "radio" dla każdego wyboru ankiety. Pole value dla każdego przycisku opcji jest skojarzone z ID obiektu choice. Atrybut name każdego przycisku opcji jest dostępny w "choice". To oznacza, że jeśli ktoś wybiera jeden z przycisków i wysyła formularz form, to przeglądarka otrzyma metodą POST dane o zawartości choice=3.
  • Ustawiliśmy atrybut formularza action na /polls/{{ poll.id }}/vote/ oraz method na "post". Używanie method="post" (zamiast method="get") jest bardzo ważne, ponieważ wysyłanie formularza zmieni dane po stronie serwera. Jeżeli kiedykolwiek tworzysz formularz, który zmienia dane po stronie serwera, używaj method="post". Ta wskazówka nie ma nic wspólnego z Django, jest to dobra praktyka tworzenia aplikacji internetowych.
  • forloop.counter oznacza ile razy znacznik for wykonał kod wewnątrz pętli. Więcej informacji znajdziesz w dokumentacji dla znacznika “for”.

Teraz utwórzmy widok Django, który obsługuje wysłane dane oraz coś z nimi robi.

Pamiętaj, że w tutorialu nr 3, utworzyliśmy URLconf dla aplikacji pools, zawierający linie:

(r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),

Utwórzmy więc funkcję vote() w mysite/polls/views.py:

from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from mysite.polls.models import Choice, Poll
# ...
def vote(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Pokaz ponownie formularz do glosowania.
        return render_to_response('polls/detail.html', {
            'poll': p,
            'error_message': "Nie wybrales zadnej opcji",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Zawsze zwróć HttpResponseRedirect po udanym obsłużeniu danych z POST.
        # To zapewnia, że dane nie zostaną wysłane dwa razy, jeżeli użytkownik
        # kliknie w przeglądarce przycisk Wstecz .
        return HttpResponseRedirect(reverse('mysite.polls.views.results',
                                             args=(p.id,)))

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

  • request.POST jest obiektem przypominającym słownik, który pozwala na dostęp do otrzymanych danych po nazwie klucza. W tym przypadku request.POST['choice'] zwraca (jako tekst) ID wybranej opcji ankiety. Wartości z request.POST są zawsze tekstem (obiektami string).

    Django dostarcza również request.GET do pobierania w ten sam sposób danych z metody GET. W naszym kodzie używamy jednak request.POST, aby upewnić się, że dane będą zmieniane tylko przez wywołanie POST.

  • request.POST['choice'] zgłosi wyjątek KeyError, jeżeli choice nie był dostarczony w danych metody POST. Powyższy kod oczekuje wyjątku KeyError oraz wyświetla ponownie formularz ankiety z informacją o błędzie, jeżeli choice nie był podany.

  • Po zwiększeniu liczby głosów (selected_choice.votes += 1), kod zwraca HttpResponseRedirect zamiast zwykłego HttpResponse. HttpResponseRedirect przyjmuje pojedynczy argument: adres URL, do którego użytkownik zostanie przekierowany (zobacz kolejny punkt, jak tworzymy w tym przypadku adres URL). Zgodnie z komentarzem w kodzie, po udanym obsłużeniu danych z POST powinieneś zawsze zwracać obiekt HttpResponseRedirect. To nie jest specyficzne dla Django; to po prostu dobra praktyka przy tworzeniu aplikacji internetowych.

  • Używamy funkcji reverse() w przekierowaniu HttpResponseRedirect. Ta funkcja pomaga uniknąć sztywnego wpisywania adresu URL w funkcji widoku. Jako parametr przekazujemy nazwę widoku, który obsłuży zapytanie oraz zmienne, które są użyte w tym adresie URL i wskazują na ten widok. W tym przypadku, używając URLConf, który ustawiliśmy w tutorialu 3, wywołanie reverse() zwróci tekst podobny do tego:

    '/polls/3/results/'
    

    … gdzie 3 jest wartością p.id. Ten przekierowany URL wtedy wywoła widok 'results', który wyświetli końcową stronę. Zwróć uwagę, że musisz tutaj użyć pełnej nazwy widoku (łącznie z prefiksem).

    Więcej informacji o reverse() możesz znaleźć w dokumentacji URL dispatchera.

Jak wspomniano w tutorialu 3, request jest obiektem HTTPRequest. Więcej o obiektach HTTPRequest znajdziesz w dokumentacji request and response.

Po tym, jak ktoś zagłosuje w ankiecie, widok vote() przekierowuje do strony z wynikami ankiety.

Napiszmy widok:

def results(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    return render_to_response('polls/results.html', {'poll': p})

To prawie to samo co widok detail() z tutoriala nr 3. Jedyną różnicą jest nazwa szablonu. Poprawimy ten zbyteczny kod później.

Utwórzmy szablon results.html:

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

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

Teraz otwórz /polls/1/ w przeglądarce i zagłosuj w ankiecie. Powinieneś zobaczyć stronę z wynikami, która jest uaktualniana za każdym razem, jak głosujesz. Jeżeli wysyłasz formularz bez wyboru na co głosujesz, powinieneś zobaczyć informacje o błędzie.

Użyj widoków generycznych - im mniej kodu tym lepiej

Widoki detail() (z tutoriala nr 3) oraz results() są wręcz niemiłosiernie proste — i jak wspomniano wcześniej — zbyteczne. Widok index() (także z tutoriala nr 3), który wyświetla listę ankiet, jest podobny.

Te widoki wykonują często powtarzane czynności w aplikacji WWW: pobranie danych z bazy danych zgodnie z parametrem przekazanym w adresie URL, załadowanie szablonu oraz zwrócenie przetworzonego szablonu. Ponieważ jest to tak często wykonywane, Django dostarcza skrót nazywany systemem “widoków generycznych”.

Widoki generyczne są abstrakcją do punktu, w którym nie musisz nawet pisać kodu w Pythonie, aby napisać aplikację.

Przeróbmy teraz naszą aplikację ankiet, aby używała systemu widoków generycznych. Możemy więc skasować trochę kodu, który napisaliśmy wcześniej. Musimy tylko wykonać kilka kroków, aby dokonać tej konwersji.

Dlaczego zmieniamy kod w ten sposób?

Ogólnie, gdy piszesz aplikacje w Django, musisz ocenić czy widoki generyczne są dobrym rozwiązaniem Twojego problemu. Jeżeli tak, to będziesz ich używać od samego początku zamiast zmieniać kod na końcu pisania aplikacji. Jednak w tym tutorialu celowo skupiamy się na pisaniu widoków od podstaw, aby pokazać podstawową koncepcje.

Powinieneś znać podstawy matematyki, aby móc korzystać z kalkulatora.

Otwórz plik ustawień adresów URL polls/urls.py. Wygląda mniej więcej tak, zgodnie z tym, co napisaliśmy do tej pory:

from django.conf.urls.defaults import *

urlpatterns = patterns('mysite.polls.views',
    (r'^$', 'index'),
    (r'^(?P<poll_id>\d+)/$', 'detail'),
    (r'^(?P<poll_id>\d+)/results/$', 'results'),
    (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)

Zmień go, aby wyglądał następująco:

from django.conf.urls.defaults import *
from mysite.polls.models import Poll

info_dict = {
    'queryset': Poll.objects.all(),
}

urlpatterns = patterns('',
    (r'^$', 'django.views.generic.list_detail.object_list', info_dict),
    (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail',
                                info_dict),
    url(r'^(?P<object_id>\d+)/results/$',
            'django.views.generic.list_detail.object_detail',
            dict(info_dict, template_name='polls/results.html'),
            'poll_results'),
    (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)

Używamy tutaj dwóch widoków generycznych: object_list oraz object_detail. Te dwa widoki są abstrakcją koncepcji “wyświetl listę obiektów” oraz “wyświetl stronę ze szczegółami dla konkretnego typu obiektu”.

  • Każdy widok generyczny musi wiedzieć na jakich danych będzie działać. Te dane podaje się w słowniku. Klucz queryset w tym słowniku wskazuje na listę obiektów, które będą obsługiwane przez generyczny widok.
  • Widok generyczny object_detail oczekuje wartości ID przekazanej z adresu URL, która nazywa się "object_id", wiec zmieniliśmy poll_id na object_id dla widoków generycznych.
  • Dodaliśmy nazwę poll_results do widoku wyników, aby mieć możliwość odwołania się do jego adresu URLa w późniejszym czasie (zobacz dokumentację na temat konfigurowania wzorców adresów URL, aby uzyskać więcej informacji).

Używamy tutaj także funkcji url() z pakietu django.conf.urls.defaults. Jest to dobry zwyczaj, aby używać url() kiedy podaje się wzorzec taki, jak tutaj.

Domyślnie, widok generyczny object_detail używa szablonu nazywającego się <nazwa aplikacji>/<nazwa modelu>_detail.html. W naszym przypadku, używa szablonu "polls/poll_detail.html". Zmień nazwę szablonu polls/detail.html na polls/poll_detail.html oraz zmień wiersz render_to_response() w vote().

Podobnie, widok generyczny object_list używa szablonu nazwywającego się <nazwa aplikacji>/<nazwa modelu>_list.html. Zmień więc polls/index.html na polls/poll_list.html.

Ponieważ mamy więcej niż jeden wpis w URLconf, który używa object_detail dla aplikacji polls, określamy ręcznie nazwę szablonu dla widoku wyników: template_name='polls/results.html'. W przeciwnym razie obydwa widoki używałyby tego samego szablonu. Zauważ, że używamy dict(), aby zwrócić w tym miejscu zmieniony słownik.

Note

all() jest leniwe

To może wyglądać trochę przerażająco: wywołanie Poll.objects.all() w widoku szczegółu, który tylko potrzebuje jednego obiektu Poll, ale nie martw się; Poll.objects.all() jest właściwie specjalnym obiektem nazywanym QuerySet, który jest “leniwy” i nie wyciąga nic z bazy danych, dopóki nie musi. W czasie, gdy wystąpi zapytanie do bazy, widok generyczny object_detail będzie miał swój zasięg zmniejszony do pojedynczego obiektu, więc ewentualne zapytanie wybierze z bazy danych tylko jeden wiersz.

Jeżeli chciałbyś się dowiedzieć, jak to działa, zajrzyj do dokumentacji Django na temat API bazy danych, która wyjaśnia leniwą naturę obiektów QuerySet.

W poprzednich częściach tego tutoriala, szablony miały podany kontekst, który zawierał zmienne poll oraz latest_poll_list. Jednak widoki generyczne dostarczają zmienne object oraz object_list jako kontekst. Dlatego musisz zmienić szablony, aby zawierały nowe zmienne kontekstu. Przejrzyj szablony i zmodyfikuj każde odniesienie do latest_poll_list na object_list. Zmień też każde wystąpienie poll na object.

Możesz teraz usunąć widoki index(), detail() oraz results() z polls/views.py. Już ich nie potrzebujemy — zostały zastąpione przez widoki generyczne.

Widok vote() jest jednak ciągle potrzebny. Musi być jednak zmodyfikowany, aby odpowiadał nowemu kontekstowi zmiennych. W wywołaniu render_to_response(), zmień nazwę zmiennej poll na object.

Ostania rzecz do zrobienia to naprawa obsługi adresów URL z uwzględnieniem użycia widoków generycznych. W widoku vote powyżej użyliśmy funkcji reverse(), aby uniknąć sztywnego wpisywania adresów URL w widokach. Teraz przełączyliśmy się na widok generyczny, więc musimy zmienić wywołanie reverse(), aby wskazywało z powrotem do naszego widoku generycznego. Nie możemy już po prostu użyć funkcji widoku — widoki generyczne mogą (i są) używane wielokrotnie — ale możemy użyć nazwy, której użyliśmy:

return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))

Uruchom teraz serwer i użyj nowej aplikacji ankiet opartej o widoki generyczne.

Aby dowiedzieć się więcej o widokach generycznych, zobacz dokumentację widoków generycznych.

Już wkrótce

Ten tutorial kończy się w tym momencie. Ale sięgnij też po inne informacje:

  • Zaawansowane przetwarzanie formularzy
  • Używanie framework’a RSS
  • Używanie framework’a cache’owania
  • Używanie framework’a komentarzy
  • Zaawansowane funkcje admina: uprawnienia
  • Zaawansowane funkcje admina: Własny JavaScript

W międzyczasie możesz przeczytać resztę dokumentacji Django i zacząć pisać własne aplikacje.

Pytania/Wsparcie

Jeżeli zauważyłeś błędy w tłumaczeniu dokumentacji proszę zgłoś je nam.