Tłumacz, licencja

Autorem tłumaczenia jest Rafał Jońca. Tłumaczenie jest dostępne na tej samej licencji co oryginał - GNU Free Documentation License.

Ostatnia aktywność w witrynie

Rozdział 3. Podstawy dynamicznych stron WWW

W poprzednim rozdziale wyjaśniliśmy, w jaki sposób utworzyć projekt Django i uruchomić serwer w wersji deweloperskiej. W tym rozdziale zajmiemy się podstawami tworzenia w Django dynamicznych stron WWW.

Twoja pierwsza strona w Django: Witaj, świecie

Jak przystało na książkę informatyczną, rozpoczniemy przygodę od utworzenia strony z tekstem "Witaj, świecie!".

Gdybyśmy chcieli utworzyć taką stronę bez stosowania frameworka, po prostu umieścilibyśmy tekst "Witaj, świecie!" w pliku tekstowym, nazwalibyśmy plik hello.html i umieścilibyśmy go na serwerze WWW. Zauważ, że w tym procesie określiliśmy dwa bardzo istotne elementy tworzonej strony WWW: jej zawartość (tekst "Witaj, świecie!") i jej adres URL (http://www.example.com/hello.html lub http://www.example.com/files/hello.html, gdyby umieścić plik w podfolderze).

W Django również trzeba określić obie informacje, ale czyni się to w inny sposób. Zawartość strony tworzy funkcja widoku natomiast adres URL określa system URLconf. Najpierw napiszmy funkcję widoku wyświetlającą tekst "Witaj, świecie!".

Pierwszy widok

Wewnątrz folderu mysite utworzonego w poprzednim rozdziale przez polecenie django-admin.py startproject, utwórz plik o nazwie views.py. Plik ten, w rzeczywistości moduł Pythona, będzie zawierał widoki tworzone w tym rozdziale. W samej nazwie views.py nie ma nic szczególnego — Django nie zwraca uwagi na nazwę pliku, o czym się wkrótce przekonasz — ale jako konwencję warto wybrać nazwę views.py, choćby ze względu na innych programistów analizujących kod.

Pierwszy widok z tekstem "Witaj, świecie!" jest bardzo prosty. Poniżej znajduje się cała funkcja wraz z instrukcją importu, którą powinieneś wpisać do pliku views.py:

from django.http import HttpResponse

def hello(request):
    return HttpResponse("Witaj, świecie!")

Przeanalizujmy kod wiersz po wierszu:

  • Najpierw importujemy klasę HttpResponse, która znajduje się w module django.http. Importujemy ją, ponieważ jest stosowana w dalszej części kodu.

  • Następnie definiujemy funkcję o nazwie hello — funkcję widoku.

    Każda funkcja widoku przyjmuje przynajmniej jeden parametr, przez konwencję nazywany request. Obiekt ten zawiera informacje na temat aktualnego żądania HTTP, które spowodowało wywołanie widoku. Obiekt ten jest egzemplarzem klasy django.http.HttpRequest. Choć w przykładzie nie korzystamy z żądania, musi być ono pierwszym parametrem widoku.

    Zauważ, że nazwa funkcji nie ma znaczenia; nie musi być nazwana w szczególny sposób, aby została rozpoznana przez Django. Nadaliśmy jej nazwę hello, ponieważ, ponieważ oddaje to znaczenie funkcji, ale równie dobrze moglibyśmy zastosować nazwę przywitanie_ziemian. W następnym punkcie wyjaśnimy, w jaki sposób Django odnajduje odpowiednią funkcję.

  • Funkcja jest w zasadzie jednowierszowa: po prostu zwraca obiekt HttpResponse, który jako treść zawiera tekst "Witaj, świecie!".

Z tej lekcji należy wynieść następującą informację: widok jest zwykłą funkcją Pythona, która jako pierwszy parametr przyjmuje obiekt HttpRequest, a jako odpowiedź zwraca obiekt HttpResponse. Aby funkcja Pythona mogła być widokiem, musi spełnić oba warunki. (Istnieją wyjątki od tej reguły, ale wrócimy do nich w dalszej części książki).

Pierwszy URLconf

Jeśli w tym momencie uruchomiłbyś ponownie polecenie python manage.py runserver, nadal widziałbyś komunikat powitalny Django bez jakiegokolwiek śladu widoku "Witaj, świecie!". Wynika to z faktu, iż projekt mysite jeszcze nic nie wie na temat widoku hello; musimy jawnie wskazać Django, że chcemy pod wskazanym adresem URL wywoływać określony widok. (Stosując analogię do statycznych plików HTML, możemy powiedzieć, że utworzyliśmy plik HTML, ale jeszcze nie umieściliśmy go na serwerze). Aby połączyć adres URL i widok, użyj systemu URLconf.

System URLconf to jak spis treści dla witryny Django. Najprościej rzecz ujmując, to odwzorowanie szablonów adresów URL na funkcje widoków wywoływane dla tych wzorców. Informujemy Django: dla tego URL, wywołaj tę funkcję, a dla tamtego, tamtą funkcję. Przykładowo możemy napisać: gdy ktoś odwiedzi adres /foo/, wywołaj funkcję foo_view(), który znajduje się w module views.py.

Po wykonaniu polecenia django-admin.py startproject w poprzednim rozdziale, skrypt automatycznie utworzył plik konfiguracji URLconf o nazwie urls.py. Domyślnie wygląda on następująco:

from django.conf.urls.defaults import *

# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()

urlpatterns = patterns('',
    # Example:
    # (r'^mysite/', include('mysite.foo.urls')),

    # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
    # to INSTALLED_APPS to enable admin documentation:
    # (r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # Uncomment the next line to enable the admin:
    # (r'^admin/(.*)', admin.site.root),
)

Domyślny URLconf zawiera kilka często używanych elementów Django umieszczonych w komentarzach, więc aktywacja tych funkcji wymaga tylko usunięcia kilku znaków. Gdy pominiemy kod umieszczony jako komentarz, pozostała część będzie bardzo krótka:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
)

Przeanalizujmy kod wiersz po wierszu:

  • Pierwszy wiersz importuje wszystkie obiekty z modułu django.conf.urls.defaults, który stanowi podstawową infrastrukturę URLconf. Dotyczy to również funkcji nazwanej patterns.
  • Drugi wiersz wywołuje funkcję patterns i zapisuje wynik w zmiennej o nazwie urlpatterns. Funkcja patterns otrzymuje tylko jeden argument — pusty tekst. (Tekst ten może posłużyć do ustawienia wspólnego przedrostka dla funkcji widoków, ale to już bardziej zaawansowany temat).

Najważniejszym elementem pliku jest zmienna urlpatterns, którą Django oczekuje odnaleźć w module URLconf. Zmienna definiuje odwzorowanie między adresami URL i kodem, który ma za zadanie obsłużyć żądanie. Domyślnie URLconf jest pusty — aplikacja Django jest czysta. (Warto wspomnieć, że to właśnie dlatego Django wiedziało, że powinno wyświetlić stronę powitalną. Jeśli URLconf jest pusty, Django zakłada, że dopiero rozpoczynasz tworzenie projektu i wyświetla przyjazny komunikat).

Aby dodać adres URL i widok do URLconf, dopisz Pythonową krotkę zawierającą wzorzez adresu URL i funkcję widoku. Oto w jaki sposób wyglądałoby to dla widoku hello:

from django.conf.urls.defaults import *
from mysite.views import hello

urlpatterns = patterns('',
    ('^hello/$', hello),
)

(Zauważ, że dla zwiększenia czytelności usunęliśmy komentarze. Jeśli chcesz, możesz je pozostawić).

Dokonaliśmy dwóch zmian:

  • Importujemy widok hello z jego modułu — mysite/views.py, co w Pythonie można przetłumaczyć jako mysite.views. (Zakłada to istnienie plików na ścieżce wyszukiwania Pythona, ale więcej na ten temat w osobnej ramce).
  • Dodaliśmy wiersz ('^hello/$', hello) do urlpatterns. Wiersz ten nazywa się często wzorcem URL. Jest to krotka, która jako pierwszy element zawiera wzorzec dopasowania (wyrażenie regularne, o których więcej za chwilę), a jako drugi element funkcję widoku do zastosowania po zgodności wzorca.

W dużym skrócie możemy powiedzieć, że poinstruowaliśmy Django, aby żądania dotyczące adresu URL /hello/ obsługiwało za pomocą funkcji widoku hello.

Ścieżka wyszukiwania Pythona

Ścieżka wyszukiwania Pythona to lista folderów w systemie, które Python przeszukuje, gdy korzystamy z polecenia importowania.

Przypuśćmy, że ścieżka wyszukiwania jest ustawiona na '', '/usr/lib/python2.4/site-packages', '/home/username/djcode']. Jeśli wykonamy polecenie from foo import bar, Python będzie szukał modułu o nazwie foo.py w aktualnym folderze (pusty tekst jako pierwszy element oznacza, aktualny folder). Jeśli plik tam nie istnieje, Python poszuka pliku w /usr/lib/python2.4/site-packages/foo.py. Jeśli i tam go nie znajdzie, poszuka w /home/username/djcode/foo.py. Jeśli tam także go nie odnajdzie, zgłosi wyjątek ImportError.

Jeśli chciałbyś sprawdzić zawartość ścieżki wyszukiwania Pythona, uruchom interaktywny interpreter Pythona i wpisz następujący kod:

>>> import sys
>>> print sys.path

Najczęściej nie musisz się martwić o ustawienie poprawniej ścieżki wyszukiwania — Python i Django zajmują się tym automatycznie. (Dodanie odpowiedniej ścieżki wyszukiwania do jedno z zadań skryptu manage.py.

Warto choć pokrótce omówić składnię odwzorowania adresów URL, bo może nie być to oczywiste. Chcemy uzyskać dopasowania do adresu /hello/, ale wzorzec jest nieco bardziej złożony. Oto dlaczego:

  • Django usuwa początkowy ukośnik z każdego nadchodzącego adresu URL przez rozpoczęciem sprawdzania dopasowań. Oznacza to, że nasz wzorzec nie powinien zawierać pierwszego ukośnika. (Początkowo może to wydawać się nieintuicyjne, ale w rzeczywistości upraszcza cały system — między innymi dołączanie dodatkowych konfiguracji adresów do głównego URLconf, co omówmy dokładniej w rozdziale 8.).

  • Wzorzec zawiera znaki daszka (^) i dolara ($). To znaki o specjalnym znaczeniu w wyrażeniach regularnych. Daszek oznacza, że wzorzec musi pasować do początku tekstu, a znak dolara oznacza, że musi pasować do końca ekstu.

    Tę koncepcję najprościej wyjaśnić na przykładzie. Gdybyśmy użyli wzorca '^hello/' (bez znaku dolara na końcu), wtedy dopasowanie działałoby dla dowolnego adresu URL rozpoczynającego się od /hello/, na przykład /hello/foo lub /hello/bar, a nie tylko samego /hello/. Podobnie, gdybyśmy pominęli znak daszka na początku, Django dopasowałoby się do dowolnego adresu URL kończącego się na hello/, na przykład /foo/bar/hello/. Gdybyśmy użyli tylko i wyłącznie tekstu hello/ bez dodatkowych znaków, wtedy dopasowanie dotyczyłoby każdego adresu zawierającego fragment hello/, czyli na przykład /foo/hello/bar. Stosując oba znaki dodatkowe, mamy pewność, że jedynym pasującym adresem będzie /hello/ — nic więcej i nic mniej.

    Większość adresów będzie rozpoczynało się od znaku daszka i kończyło znakiem dolara, ale zawsze dobrze mieć dostęp do pełnej elastyczności.

    Możesz zastanawiać się, co się stanie, jeśli ktoś zażąda adresu URL /hello (czyli bez końcowego ukośnika). Ponieważ nasz wzorzec wymaga ukośnika, nie dojdzie do dopasowania. Domyślnie jednak, każdy adres URL, który nie zostanie dopasowany i nie zawiera ukośnika, będzie przekierowany na identyczny adres, ale z dołączonym na końcu ukośnikiem. (Zachowaniem tym steruje opcja APPEND_SLASH Django opisana w dodatku E). Pamiętając o tym, najlepszym podejściem będzie dodanie ukośników końcowych do wszystkich generowanych adresów i pozostawienie opcji APPEND_SLASH na wartości True.

Zauważ również, że w URLConf przekazaliśmy funkcję widoku hello jako obiekt nie powodując jej wywołania. To jedna z kluczowych cech języka Python (i wielu innych dynamicznych języków programowania): funkcje to pełnoprawne obiekty, więc można je przekazywać w taki sam sposób jak jakiekolwiek zmienne. Wspaniale, prawda?

Aby przetestować zmiany w URLconf, uruchom serwer w wersji deweloperskiej w taki sam sposób, jak w rozdziale 2., czyli poleceniem python manage.py runserver. (Jeśli nie wyłączyłeś go od rozdziału 2., nie musisz nic robić. Serwer deweloperski automatycznie wykryje zmiany w kodzie i przeładuje kod. Ręcznie przeładowanie serwera nie jest potrzebne). Serwer działa pod adresem http://127.0.0.1:8000/, więc otwórz przeglądarkę internetową i odwiedź adres http://127.0.0.1:8000/hello/. Powinieneś zobaczyć tekst "Witaj, świecie!" — wynik wykonania widoku Django.

Hura! Udało nam się stworzyć pierwszą stronę WWW korzystającą z Django.

Wyrażenia regularne

Wyrażenia regularne to bardzo zwięzły sposób określania wzorców w tekście. Choć Django umożliwia stosowanie dowolnych elementów wyrażeń regularnych w odwzorowaniach adresów URL na funkcje, prawdopodobnie wystarczy, że zapamiętasz tylko kilka najprostszych symboli wyrażeń regularnych, by objąć większość możliwych sytuacji. Oto kilka popularnych symboli:

Symbol Dopasowanie
. (kropka) Dowolny pojedynczy znak
\d Dowolna pojedyncza cyfra
[A-Z] Dowolny znak między A i Z (duże litery)
[a-z] Dowolny znak między a i z (małe litery)
[A-Za-z] Dowolny znak między a i z (bez względu na wielkość znaków)
+ Jedno lub więcej powtórzeń poprzedniego wyrażenia (np., \d+ dopasowuje się do jednej lub więcej cyfr)
[^/]+ Jeden lub więcej znaków aż do (ale bez) znaku ukośnika
? Brak lub jedno wystąpienie poprzedniego wyrażenia (np., \d? dopasowuje się do zera lub jednej cyfry)
* Zero lub więcej powtórzeń poprzedniego wyrażenia (np., \d* dopasowuje się do zera lub więcej cyfr)
{1,3} Od jednego do trzech (łącznie z trzema) powtórzeń poprzedniego wyrażenia (np., \d{1,3} dopasowuje się do jednej do trzech cyfr)

Więcej informacji na temat wyrażeń regularnych znajdziesz na stronie http://www.djangoproject.com/r/python/re-module/.

Krótka uwaga na temat błędów 404

W tym momencie plik URLconf definiuje tylko jeden wzorzec adresu URL; tylko ten obsługujący żądania dla adresu /hello/. Co się stanie, jeśli zażądamy innego adresu URL?

Aby się o tym przekonać, uruchom serwer deweloperski i odwiedź stronę, która nie istnieje: http://127.0.0.1:8000/goodbye/, http://127.0.0.1:8000/hello/subdirectory/, lub nawet http://127.0.0.1:8000/ (główny folder witryny). Powinien pojawić się komunikat o braku strony (patrz rysunek 3.2). Django wyświetla ten komunikat, ponieważ żądany adres nie został zdefiniowany w pliku URLconf.

Zrzut strony 404 z Django. Rysunek 3.2. Strona błędu 404 z Django

Zwrócona strona zawiera znacznie więcej informacji niż tylko prosty komunikat o błędzie 404. Informuje dokładnie, które wzorce z konfiguracji adresów zostały sprawdzone przez Django. Dzięki tym informacjom łatwo stwierdzić, dlaczego żądany adres zgłosił błąd 404.

Oczywiście taka informacja jest kierowana jedynie do Ciebie, twórcy aplikacji. Jeśli witryna została umieszczona na serwerze produkcyjnym i jest publicznie dostępna, zdecydowanie nie chcesz prezentować takiej informacji wszystkim osobom. Z tego powodu prezentowany błąd zostaje wyświetlony tylko w sytuacji, gdy aplikacja znajduje się w trybie testowym. Wkrótce wyjaśnimy, jak wyłączyć tryb testowy. Na razie pamiętaj, że każdy nowoutworzony projekt znajduje się w trybie testowym. Jeśli tryb testowy jest wyłączony, Django wyświetla inny komunikat 404.

Jak Django przetwarza żądanie?

Zanim przejdziemy do drugiej funkcji widoku, zatrzymajmy się na chwilę i wyjaśnijmy podstawowy sposób działania Django. W szczególności opiszmy, jak to się dzieje, że po odwiedzeniu adresu http://127.0.0.1:8000/hello/ w przeglądarce internetowej, otrzymaliśmy tekst "Witaj, świecie!".

Wszystko zaczyna się od pliku ustawień. Po uruchomieniu polecenia python manage.py runserver, skrypt poszukuje pliku nazwanego settings.py w tym samym folderze co manage.py. Plik zawiera różnego rodzaju ustawienia dla tego konkretnego projektu Django, wszystkie zapisane dużymi literami: TEMPLATE_DIRS, DATABASE_NAME itp. Najważniejszym ustawieniem jest prawdopodobnie ROOT_URLCONF. Wartość ta informuje Django, który moduł Pythona powinien być stosowany jako główna konfiguracja adresów URL dla tej witryny.

Przypomnij sobie, że polecenie django-admin.py startproject utworzyło pliki settings.py i urls.py? Automatycznie wygenerowany plik settings.py zawiera wartość ROOT_URLCONF wskazującą na automatycznie wygenerowany plik urls.py. Otwórz plik settings.py i obacz to na własne oczy:

ROOT_URLCONF = 'mysite.urls'

Odpowiada to plikowi mysite/urls.py.

Gdy przesyłamy żądanie pobrania zawartości konkretnego adresu URL — na przykład żądania dla /hello/ — Django wczytuje plik URLconf wskazywany przez ustawienie ROOT_URLCONF. Następnie sprawdza wszystkie zawarte tam wzorce w podanej kolejności, porównując za każdym razem adres URL z podanym wzorcem aż do momentu odnalezienia dopasowania. Gdy znajdzie dopasowanie, wywołuje powiązaną z nim funkcję widoku, przekazując obiekt HttpRequest jako pierwszy parametr. (Wkrótce dokładniej omówimy szczegóły obiektu HttpRequest).

Jak pokazaliśmy w pierwszej funkcji widoku, musi ona zwrócić obiekt HttpResponse. Gdy to uczyni, Django zajmie się resztą, konwertując obiekt Pythona na odpowiednią odpowiedź serwera z odpowiednimi nagłówkami HTTP oraz treścią (czyli zawartością strony WWW).

Podsumowując:

  1. Przychodzi żądanie dotyczące /hello/.
  2. Django określa główny plik URLconf, analizując zawartość zmiennej ROOT_URLCONF.
  3. Django przegląda wszystkie wzorce URL z pliku URLconf w poszukiwaniu pierwszego pasującego do /hello/.
  4. Jeśli znajdzie dopasowanie, wywołuje powiązaną z nim funkcję widoku.
  5. Funkcja widoku zwraca HttpResponse.
  6. Django konwertuje HttpResponse na odpowiednią odpowiedź HTTP zawierająca stronę WWW.

Teraz wiesz już, jak tworzyć strony WWW za pomocą Django. Nie jest to trudne: wystarczy napisać funkcje widoków i odwzorowania adresów na otworzone funkcje w pliku URLconf.

Drugi widok — dynamiczna zawartość

Przykładowy widok "Witaj, świecie!" stanowił dobrą podstawę do pokazania sposobu działania Django, ale nie był przykładem dynamicznej strony WWW, ponieważ zwracana zawartość zawsze była identyczna. Za każdym razem, gdy odwiedzisz stronę /hello/ zobaczysz dokładnie ten same tekst, jakby to była zwykła, statyczna strona HTML.

Jako drugi widok utwórzmy coś bardziej dynamicznego — stronę WWW wyświetlającą aktualną godzinę i czas. To kolejny bardzo prosty krok, gdyż nie wymaga bazy danych lub informacji od użytkownika — po prostu zwrócimy wewnętrzną godzinę serwera. Choć sama użyteczność przykładu jest niewiele większa od "Witaj, świecie!", zapewni wprowadzenie kilku nowych elementów.

Widok wymaga dwóch rzeczy: obliczenia aktualnej daty i czasu oraz zwrócenia HttpResponse zawierającego te informacje. Jeśli masz doświadczenie z językiem Python, wiesz, że język ten zawiera moduł datetime do obliczania dat. Oto jak z niego skorzystać:

>>> import datetime
>>> now = datetime.datetime.now()
>>> now
datetime.datetime(2008, 12, 13, 14, 9, 39, 2731)
>>> print now
2008-12-13 14:09:39.002731

Użycie jest bardzo proste i nie ma nic wspólnego z Django. To zwykły kod Pythona. (Chcemy byś wiedział dokładnie, gdzie kod to zwykły Python, a gdzie dodatki wprowadzane przez Django. Poznając Django, będziesz mógł część wiedzy zaaplikować do innych projektów Pythona, które niekoniecznie stosują Django).

Aby utworzyć widok Django, który wyświetla aktualną datę i czas, wystarczy dołączyć do widoku wywołanie datetime.datetime.now(), a następnie zwrócić obiekt HttpResponse. Oto wynik tej operacji:

from django.http import HttpResponse
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>Teraz jest %s.</body></html>" % now
    return HttpResponse(html)

Podobnie jak wcześniejsza funkcja widoku, także ta powinna znaleźć sięw pliku views.py. Zauważ, że w tym przykładzie z racji przejrzystości pominęliśmy funkcję hello, ale w rzeczywistości plik views.py będzie miał następująca zawartość:

from django.http import HttpResponse
import datetime

def hello(request):
    return HttpResponse("Hello world")

def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>Teraz jest %s.</body></html>" % now
    return HttpResponse(html)

(Od tej pory w kolejnych przykładach nie będziemy umieszczać prezentowanych wcześniej fragmentów kodu, chyba że okaże się to niezbędne. Z kontekstu łatwo będzie można wywnioskować, czy dany fragment jest nowy, czy stary).

Prześledźmy zmiany wprowadzone w views.py prowadzące do powstania widoku current_datetime.

  • Dodaliśmy na początku pliku import modułu datetime, by móc obliczać daty.

  • Nowa funkcja current_datetime wylicza aktualną datę i czas jako obiekt datetime.datetime i zapamiętuje ją w zmiennej lokalnej.

  • Drugi wiersz kodu widoku tworzy odpowiedź HTML, wykorzystując tekst ze znacznikami formatowania. Element %s w tekście to specjalna konstrukcja tymczasowa powodująca wstawienie w jej miejscu zawartości zmiennej tekstowej. Zmienna now to tak naprawdę obiekt obiekt typu datetime.datetime, a nie tekst, ale %s wymusza konwersję dowolnego obiektu do jego reprezentacji tekstowej, która będzie miała postać "2008-12-13 14:09:39.002731". W wyniku powstanie tekst o zawartości "<html><body>It is now 2008-12-13 14:09:39.002731.</body></html>".

    (Tak, zaprezentowany kod HTML nie jest poprawny, ale staramy się, by przykład był krótki i prosty).

  • Na końcu widok zwraca obiekt HttpResponse zawierający wygenerowaną odpowiedź — podobnie jak to miało miejsce w funkcji widoku hello.

Po dodaniu kodu do views.py, musimy jeszcze dodać wzorzec do pliku urls.py, by poinformować Django o adresie URL, dla którego ma wywołać widok. Użyjmy adresu w postaci /time/:

from django.conf.urls.defaults import *
from mysite.views import hello, current_datetime

urlpatterns = patterns('',
    ('^hello/$', hello),
    ('^time/$', current_datetime),
)

Dokonaliśmy w tym miejscu dwóch zmian. Po pierwsze, zaimportowaliśmy funkcję current_datetime. Po drugie i ważniejsze, dodaliśmy odwzorowanie adresu URL na nowy widok. Proste, prawda?

Po napisaniu widoku i uaktualnieniu URLconf, uruchom serwer i odwiedź adres http://127.0.0.1:8000/time/ w swojej przeglądarce. Powinieneś zobaczyć aktualną datę i czas.

Django i strefy czasowe

W zależności od komputera, data i czas mogą różnić się o kilka godzin od rzeczywistych wartości. Wynika to z faktu, iż Django jest świadome stref czasowych i domyślnie stosuje strefę America/Chicago. (Musieliśmy wybrać jakąś domyślną strefę, więc wybraliśmy tę, w której mieszkali pierwsi programiści Django). Jeśli mieszkasz w innym rejonie świata, zmień wartość w pliku settings.py. Dokładniejsze informacje o dostępnych strefach czasowych znajdują się w komentarzu powyżej zmiennej.

Pliki URLconf i luźne powiązanie komponentów

Nadszedł właściwy czas, by przyjrzeć się dokładniej kluczowej filozofii kryjącej się za URLconf i Django jako takim: zasadzie luźnego powiązania komponentów. Mówiąc krótko, luźne powiązanie komponentów to technika tworzenia oprogramowania, która ceni sobie łatwość wymiany poszczególnych komponentów. Jeśli dwa komponenty są luźno powiązane, zmiana w jednym z nich będzie miała niewielki lub żaden wpływ na pozostałe elementy.

Pliki URLconf z Django to dobry, praktyczny przykład tej zasady. W aplikacji Django, definicje adresów URL i wywoływane przez nie funkcje widoków są luźno powiązane; decyzja co do adresu URL wybranej funkcji i sama funkcja znajdują się w dwóch różnych miejscach. Pozwala to zmienić jeden z elementów bez wpływania na drugi.

Przyjrzyjmy się widokowi current_datetime. Gdybyśmy chcieli zmienić adres URL tego elementu aplikacji — np. z /time/ na /current-time/ — wystarczyłoby wprowadzić modyfikację w pliku URLconf bez martwienia się o funkcję widoku. Podobnie, gdybyśmy chcieli zmienić funkcję widoku — zmodyfikować jej logikę lub przenieść do innego modułu — możemy to zrobić bez wpływania na adres URL, do którego funkcja przynależy.

Co więcej, gdybyśmy chcieli udostępnić tę samą funkcjonalność związaną z datą pod kilkoma różnymi adresami URL, moglibyśmy to zrobić edytując plik URLconf bez jakichkolwiek zmian w kodzie widoku. W poniższym przykładzie funkcja current_datetime jest dostępna pod dwoma różnymi adresami URL. To bardzo uproszczony przykład, ale sama technika bywa ogromnie pomocna:

urlpatterns = patterns('',
    ('^hello/$', hello),
    ('^time/$', current_datetime),
    ('^another-time-page/$', current_datetime),
)

Luźne powiązanie widoków i adresów URL pokazaliśmy na praktycznym przykładzie. Inne przykłady tej bardzo istotnej filozofii pojawią się w dalszych rozdziałach książki.

Trzeci widok — dynamiczny adres URL

W widoku current_datetime zawartość strony — aktualna data i czas — były dynamiczne, ale same adres URL (/time/) pozostawał statyczny. W większości dynamicznych aplikacji WWW, adres URL zawiera parametry wpływające na wynik działania strony. Przykładowo internetowy sklep z książkami może każdej książce przypisać własny adres URL, na przykład /books/243/ lub /books/81196/.

Utwórzmy trzeci widok, który będzie wyświetlał aktualną datę i czas przesuniętą o określoną liczbę godzin. Naszym celem jest stworzenie strony, która po użyciu adresu /time/1/ wyświetli czas o jedną godzinę w przyszłość, dla adresu /time/2/ wyświetli czas o dwie godziny w przyszłość, dla /time/3/ trzy godziny w przyszłość itd.

Nowicjusz mógłby zechcieć utworzyć osobną funkcję widoku dla każdego przesunięcia, co spowodowałoby powstanie konfiguracji URL o następującej treści:

urlpatterns = patterns('',
    (r'^time/$', current_datetime),
    (r'^time/plus/1/$', one_hour_ahead),
    (r'^time/plus/2/$', two_hours_ahead),
    (r'^time/plus/3/$', three_hours_ahead),
    (r'^time/plus/4//$', four_hours_ahead),
)

Oczywiście ten kod ma poważne wady. Nie tylko doprowadziłby do powielenia funkcji widoków, ale również sama aplikacja byłaby mocno ograniczona ze względu na obsługę tylko kilku predefiniowanych przesunięć godziny. Gdybyśmy w przyszłości zechcieli utworzyć stronę, która wyświetla przesunięcie o pięć godzin w przyszłość, musielibyśmy utworzyć osobny widok i wpis w URLconf, jeszcze bardziej pogłębiając duplikację kodu. Potrzebujemy abstrakcyjnego rozwiązania.

Kilka słów na temat ładnych adresów URL

Jeśli tworzyłeś aplikacje dla innej platformy WWW, na przykład PHP lub Java, zapewne myślisz "Hej, użyjmy parametrów adresu URL" i masz na myśli zapis adresu w postaci /time/plus?hours=3, w którym to przesunięcie jest podawane jako jako część parametrów adresu (po znaku ?).

W Django również możesz tak zrobić (w dalszej części pokażemy, jak tego dokonać, jeśli rzeczywiście chcesz wiedzieć), ale jedną z podstawowych filozofii Django są ładne adresy URL. Adres URL /time/plus/3/ jest czystszy, prostszy, bardziej czytelny, łatwiejszy do podyktowania i po prostu ładniejszy od swojego parametrowego odpowiednika. Ładne adresu URL to cecha charakterystyczna wysokiej jakości aplikacji WWW.

System URLconf Django zachęca do tworzenia ładnych adresów URL, gdyż ich obsługa jest prostsza niż innych rozwiązań.

W jaki sposób zaprojektować aplikację, by używała dowolnych przesunięć godzinowych? Kluczem okazuje się użycie znaków wieloznacznych we wzorcu adresu URL. Jak wspomnieliśmy wcześniej, wzorzec jest wyrażeniem regularnym, więc wystarczy, że zastosujemy element \d+, by dopasować się do jednej lub większej liczb cyfr:

urlpatterns = patterns('',
    # ...
    (r'^time/plus/\d+/$', hours_ahead),
    # ...
)

(Używamy konstrukcji # ..., by wskazać, że mogą istnieć inne wzorce nie przedstawione w tym przykładzie).

Nowy wzorzec dopasuje się do dowolnego adresu URL o strukturze /time/plus/2/, /time/plus/25/ a nawet /time/plus/100000000000/. Ograniczmy więc maksymalne przesunięcie do 99 godzin. Oznacza to, że będziemy dopuszczać wprowadzenie jednej lub dwóch cyfr, więc fragment wyrażenia regularnego przekształcimy w \d{1,2}:

(r'^time/plus/\d{1,2}/$', hours_ahead),

Uwaga

Gdy projektujesz aplikację WWW, zawsze warto zastanowić się nad najbardziej wymyślnymi danymi wejściowymi i zastanowić się, czy aplikacja powinna je obsługiwać. W tym przypadku ucinamy wszystkie dziwaczne wartości, ograniczając przesunięcie do 99 godzin.

Po określeniu elementu wieloznacznego w adresie URL, musimy w jakiś sposób przekazać tę informację do funkcji widoku, by móc wykorzystać jedną funkcję do obsługi dowolnych przesunięć godziny. Zadanie to wykonamy, umieszczając nawiasy okrągłe wokół danych we wzorcu adresu. W tym konkretnym przypadku jako dane do przekazania do funkcji chcemy potraktować część wieloznaczną adresu, więc otocz nawiasami fragment \d{1,2}:

(r'^time/plus/(\d{1,2})/$', hours_ahead),

Jeśli używałeś wcześniej wyrażeń regularnych, z pewnością znasz znaczenie nawiasów okrągłych: stosuje się je do zapamiętania danych z dopasowanego tekstu.

Końcowa wersja URLconf wraz z dwoma poprzednimi widokami wygląda następująco:

from django.conf.urls.defaults import *
from mysite.views import hello, current_datetime, hours_ahead

urlpatterns = patterns('',
    ('^hello/$', hello),
    ('^time/$', current_datetime),
    (r'^time/plus/(\d{1,2})/$', hours_ahead),
)

Po zakończeniu tematu adresów, możemy przejść do tworzenia widoku hours_ahead.

Kolejność pisania kodu

W tym przykładzie najpierw napisaliśmy wzorzec adresu URL, a dopiero później widok, ale we wcześniejszych przykładach najpierw napisaliśmy widok, a dopiero później wzorzec adresu. Która technika jest lepsza?

Każdy programista pracuje inaczej.

Jeśli jesteś osobą dobrze radząca sobie z ogólnym widokiem sytuacji, być może dobrze będzie, jeśli za jednym zamachem, na początku projektu napiszesz wszystkie wzorce adresów, a dopiero później zajmiesz się widokami. Ma to tę zaletę, że dosyć szybko uzyskuje się prostą listę zadań do wykonania wraz z parametrami wymaganymi dla wszystkich tworzonych widoków.

Jeśli wolisz pracować od szczegółu do ogółu, możesz najpierw napisać widoki, a następnie dołączyć je do adresów URL. Nic nie stoi na przeszkodzie, by wykonać to właśnie w ten sposób.

Wybierz technikę, która pasuje do Ciebie jak najlepiej. Oba podejścia są prawidłowe.

Funkcja hours_ahead jest bardzo podobna do napisanej wcześniej funkcji current_datetime z jedną istotną różnicą: przyjmuje dodatkowy argument, liczbę godzin przesunięcia. Oto kod widoku:

def hours_ahead(request, offset):
    offset = int(offset)
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    html = "<html><body>Za %s godzin(y) będzie %s.</body></html>" % (offset, dt)
    return HttpResponse(html)

Przeanalizujmy kod wiersz po wierszu:

  • Funkcja widoku hours_ahead przyjmuje dwa parametry: żądanie i przesunięcie.

    • Parametr request (żądanie) jest obiektem HttpRequest, podobnie jak w funkcjach hello i current_datetime. Powtórzmy to raz jeszcze: każdy widok zawsze pobiera obiekt HttpRequest jako swój pierwszy parametr.

    • Parametr offset (przesunięcie) jest wartością tekstową pobraną przez nawiasy z wzorca adresu. Jeśli adres URL miał postać /time/plus/3/, parametr offset będzie zawierał tekst '3'. Jeśli adres URL miał postać /time/plus/21/, parametr offset będzie zawierał tekst '21'. Pamiętaj, że pobrane z adresu wartości zawsze będą tekstami, a nie liczbami, nawet jeśli składają się z samych cyfr jak '21'.

      Nadaliśmy zmiennej nazwę offset, ale możesz jej nadać dowolną nazwę, o ile jest poprawnym identyfikatorem Pythona. Nazwa zmiennej nie ma znaczenia; istotne jest tylko to, że stanowi drugi parametr funkcji tuż za żądaniem. (Można również w URLconf określać parametry kluczowe zamiast pozycyjnych, ale więcej informacji na ten temat pojawi się dopiero w rozdziale 8.).

  • Pierwszym krokiem wykonywanym w funkcji jest wywołanie int() na parametrze offset. Powoduje to konwersję tekstu na liczbę.

    Pamiętaj, że Python zgłosi wyjątek ValueError, jeśli wywołamy int() na wartości, której nie można skonwertować na wartość całkowitą, na przykład tekstu 'foo'. W prezentowanym przykładzie nie musimy się o to martwić, ponieważ możemy być pewni, że parametr offset będzie tekstem zawierającym tylko i wyłącznie cyfry. Wiemy to, gdyż tak napisaliśmy wyrażenie regularne (\d{1,2}), by przyjmowało tylko liczby. To jedna z interesujących cech URLconf — zapewniają one prosty, ale skuteczny system walidacji danych wejściowych.

  • W następnym wierszu wyliczamy aktualną datę i czas oraz dodajemy do wyniku odpowiednią liczbę godzin. Funkcję datetime.datetime.now() wykorzystaliśmy już w widoku current_datetime. Nowym elementem jest arytmetyka dotycząca dat wykonywana za pomocą obiektu datetime.timedelta dodawanego do obiektu datetime.datetime. Wynik zostaje zapamiętany w zmiennej dt.

    Ten wiersz pokazuje również, dlaczego wywołaliśmy int() dla parametru offset — funkcja datetime.timedelta wymaga, by parametr hours był liczbą całkowitą.

  • Następnie konstruujemy wynikowy kod HTML, podobnie jak we wcześniejszym widoku current_datetime. Mała różnica polega na tym, że tym razem przekazujemy do systemu formatującego dwie wartości, a nie tylko jedną. Z tego powodu pojawiają się dwa symbole zastąpienia i krotka z wartościami do wstawienia: (offset, dt).

  • Na końcu zwracamy obiekt HttpResponse zawierający kod HTML. To fragment, który dobrze znamy.

Po napisaniu funkcji widoku i URLconf, uruchom serwer deweloperski Django (jeśli jeszcze nie został uruchomiony) i odwiedź adres http://127.0.0.1:8000/time/plus/3/, by przekonać się czy działa. Następnie odwiedź adresy http://127.0.0.1:8000/time/plus/5/ i http://127.0.0.1:8000/time/plus/24/. Na końcu sprawdź adres http://127.0.0.1:8000/time/plus/100/, by upewnić się, że adres obsługuje tylko jedno lub dwucyfrowe wartości przesunięcia. W ostatnim przypadku Django powinien zgłosić błąd o nieodnalezieniu strony, podobny do tego opisywanego w notce o błędach 404. Adres URL http://127.0.0.1:8000/time/plus/ (bez podania przesunięcia) również zgłosi błąd 404.

Ładne strony błędów Django

Popodziwiaj przez chwilę tę wspaniałą aplikację, która udało się nam napisać... bo za chwilę ją zepsujemy! Celowo wprowadźmy błąd w pliku views.py, tworząc z wiersza offset = int(offset) widoku hours_ahead komentarz:

def hours_ahead(request, offset):
    #offset = int(offset)
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    html = "<html><body>Za %s godzin(y) będzie %s.</body></html>" % (offset, dt)
    return HttpResponse(html)

Uruchom serwer deweloperski i przejdź do adresu /time/plus/3/. Pojawi się strona błędu z mnóstwem informacji, włącznie z komunikatem o błędzie TypeError umieszczonym na samym początku strony: "unsupported type for timedelta hours component: str".

Co się stało? A więc, funkcja datetime.timedelta oczekuje, że parametr hours będzie liczbą całkowitą, a my zamieniliśmy kod dokonujący takiej konwersji na komentarz. Z tego powodu datetime.timedelta zgłosiło błąd. To rodzaj błędu, który wcześniej czy później pojawia się u każdego programisty.

Celem tego przykładu było zaprezentowanie stron błędu Django. Dokładnie przeanalizuj stronę, by poznać wszystkie udostępniane przez nią informacje.

Zwróć uwagę na kilka interesujących elementów:

  • Na górze strony pojawiają się kluczowe informacje o wyjątku: jego rodzaj, parametry związane z wyjątkiem (najczęściej komunikat dokładniej opisujący zdarzenie), plik oraz numer wiersza, w którym wystąpił wyjątek.

  • Poniżej podstawowych informacji strona wyświetla pełną informacje o stosie Pythona w momencie wystąpienia wyjątku. Przypomina to standardowe informacje o stosie z interpretera Pythona, ale w trybie interaktywnym. Dla każdego poziomu na stosie Django wyświetla nazwę pliku, nazwę funkcji/metody, numer wiesza i kod źródłowy danego wiersza.

    Kliknij wiersz z kodem źródłowym (w kolorze ciemnoszarym), a pojawi się kilka wierszy przed i po wierszu z błędem, zapewniając odpowiedni kontekst.

    Kliknij element "Local vars" poniżej dowolnej ramki stosu, aby zobaczyć tablicę ze wszystkimi zmiennymi lokalnymi i ich wartościami z momentu wystąpienia wyjątku. Taka informacja znacząco pomaga w odnalezieniu źródła błędu.

  • Zauważ tekst "Switch to copy-and-paste view" poniżej nagłówka "Traceback". Kliknij te słowa, a stos zamienia się na alternatywną wersję, którą można łatwo kopiować i wklejać. Użyj tej informacji, jeśli chcesz poinformować o wyjątku inne osoby, by uzyskać pomoc — na przykład poprosić o radę osoby na kanale IRC Django lub na liście dyskusyjnej Django.

    Znajdujący się niżej przycisk "Share this traceback on a public Web site" automatycznie, za pomocą jednego kliknięcia, prześle cały stos wywołań do http://www.dpaste.com/, gdzie otrzymasz unikatowy adres URL ułatwiający współdzielenie danych z innymi.

  • Następny dział nazwany "Request information" zawiera sporo informacji o żądaniu WWW, które spowodowało pojawienie się błędu: dane GET oraz POST, wartości ciasteczek i dane meta takie jak nagłówki CGI. Dodatek H zawiera opis wszystkich informacji znajdujących się w obiekcie żądania.

    Jeszcze niżej znajduje się dział "Settings" zawierający wszystkie ustawienia danej instalacji Django. (Wspomnieliśmy wcześniej o ustawieniu ROOT_URLCONF, a w dalszej części książki przedstawimy ich znacznie więcej). Wszystkie dostępne opcje są szczegółowo opisane w dodatku E).

Strony błędów są w stanie wyświetlić dodatkowe informacje w niektórych szczególnych sytuacjach, na przykład jeśli wystąpi błąd składniowy szablony. Zagadnieniem tym zajmiemy się w momencie omawiania systemu szablonów Django. Na razie usuń znak komentarza z wiersza offset = int(offset), by funkcja widoku znowu działała prawidłowo.

Czy jesteś programistą, który lubi szukać błędów za pomocą odpowiednio umieszczonych instrukcji print? Możesz w tym celu wykorzystać strony błędów Django, ale bez instrukcji print. W dowolnym miejscu widoku tymczasowo umieść polecenie assert False, by wymusić wyświetlenie strony błędu. Następnie możesz przejrzeć zmienne lokalne i stan programu. Oto przykład używający widoku hours_ahead:

def hours_ahead(request, offset):
    offset = int(offset)
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    assert False
    html = "<html><body>Za %s godzin(y) będzie %s.</body></html>" % (offset, dt)
    return HttpResponse(html)

Warto pamiętać, że większość prezentowanych informacji jest bardzo ważna — pojawiają się fragmenty kodu Pythona oraz konfiguracji Django. Byłoby wielką nierozwagą pokazywanie takich informacji publicznie. Nieprzyjazna osoba mogłaby wykorzystać te dane do poznania sposobu działania witryny i zaatakowania jej. Z tego powodu opisywane strony błędów wyświetlane są tylko w trybie testowania projektu Django. Wkrótce wyjaśnimy, jak wyłączyć tryb testowy. Na razie pamiętaj, że każdy nowy projekt znajduje się domyślnie w trybie testowym. (Brzmi znajomo? Opisywane wcześniej strony błędów działają w podobny sposób).

Co dalej?

Do tej pory tworzyliśmy funkcje widoków z zakodowanym na stałe w Pythonie kodem HTML. Ułatwiało to zaprezentowanie pierwszych przykładów, ale w rzeczywistych aplikacjach to bardzo rzadko jest słuszne rozwiązanie.

Django zawiera prosty, ale bardzo użyteczny system szablonów, który umożliwia separację projektu strony od kodu. System szablonów omówimy w następnym rozdziale.