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ł 4. System szablonów Django

W poprzednim rozdziale zapewne zauważyłeś ten szczególny sposób zwracania tekstu w przykładowych widokach. W szczególności kod HTML był na sztywno zakodowany w Pythonie i wyglądał następująco:

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

Choć to rozwiązanie wystarczało, by móc przedstawić zasadę działania widoków, umieszczanie kodu HTML w widokach nie jest dobrym pomysłem. Oto dlaczego:

  • Dowolna zmiana projektu strony wymaga zmiany w kodzie Pythona. Wygląd nierzadko zmienia się częściej niż kod Pythona, więc byłoby dobrze, gdyby istniała możliwość niezależnej modyfikacji kodu Pythona.
  • Tworzenie kodu Pythona i projektowanie wyglądu strony w HTML to dwie różne dyscypliny, więc większość profesjonalnych zespołów rozdziela te zadania na różne osoby (czasem nawet działy). Projektanci i twórcy kodu HTML oraz CSS nie powinni być zmuszani do edycji kodu Pythona, by wykonywać własną pracę.
  • Praca posuwa się znacznie sprawniej, jeśli programiści mogą pracować nad kodem Pythona, a projektanci nad szablonami. Jedna osoba nie musi czekać na drugą, bo tamta edytuje właśnie plik zawierający kod Pythona i HTML.

Z tych powodów znacznie lepiej jest oddzielić wygląd strony od kodu widoku napisanego w Pythonie. Możemy to zrobić, korzystając z systemu szablonów Django opisywanego w niniejszym rozdziale.

Podstawy systemu szablonów

Szablon Django to łańcuch tekstowy mający na celu odseparowanie prezentacji dokumentu od jego danych. Szablon definiuje miejsca wstawień danych oraz zawiera prostą logikę (znaczniki szablonu), które określając sposób wyświetlania dokumentu. Najczęściej szablony używa się do generowania kodu HTML, ale szablony Django doskonale nadają się do generowania danych w dowolnym formacie tekstowym.

Zacznijmy od prostego, przykładowego szablonu. Ten szablon opisuje stronę HTML, która dziękuje osobie za złożenie zamówienia. Potraktuj go jak list formalny:

<html>

<head><title>Informacja o zamówieniu</title></head>

<body>

<h1>Informacja o zamówieniu</h1>

<p>Szanowny {{ person_name }},</p>

<p>Dziękujemy za złożenie zamówienia w {{ company }}. Jest ono przewidziane do wysyłki w dniu {{ ship_date|date:"j F Y" }}.</p>

<p>Oto lista zamówionych produktów:</p>

<ul>
{% for item in item_list %}

<li>{{ item }}</li>
{% endfor %}
</ul>

{% if ordered_warranty %}
<p>W paczce znajdzie się informacja na temat gwarancji.</p>
{% else %}
<p>Nie zamawiałeś gwarancji, więc musisz sobie radzić sam w przypadku zepsucia się produktu.</p>

{% endif %}

<p>Z wyrazami szacunku,<br />{{ company }}</p>

</body>
</html>

Ten szablon to podstawowy kod HTML z kilkoma zmiennymi i znacznikami szablonu. Przeanalizujmy go dokładniej:

  • Dowolny tekst otoczony podwójną parą nawiasów klamrowych (np. {{ person_name }}) jest zmienną. Oznacza "wstaw w tym miejscu wartość zmiennej o podanej nazwie". (W jaki sposób określimy wartości zmiennych? Tm tematem zajmiemy się za chwilę).

  • Dowolny tekst otoczony nawiasem klamrowym i znakiem procentu (np. {% if ordered_warranty %}) jest znacznikiem szablonu. Definicja szablonu jest bardzo obszerna: znacznik informuje po prostu system szablonowy o potrzebie wykonania pewnego działania).

    W tym przykładzie znalazły się dwa znaczniki: znacznik for ({% for item in item_list %}) i znacznik if ({% if ordered_warranty %}).

    Znacznik for działa bardzo podobnie do polecenia for w Pythonie, pozwalając przejść sekwencyjnie przez wszystkie elementy. Znacznik if to instrukcja warunkowa znana ze wszystkich języków programowania. W tym konkretnym przypadku szablon sprawdzi, czy zmienna ordered_warranty jest równa True. Jeśli tak, system szablonów wyświetli wszystko między {% if ordered_warranty %} i {% else %}. Jeśli nie, wyświetli wszystko między {% else %} and {% endif %}. Pamiętaj, że część {% else %} jest opcjonalna.

  • Drugi akapit szablonu zawiera filtr, który stanowi najbardziej wygodny sposób formatowania zmiennej. W tym przykładzie {{ ship_date|date:"j F Y" }} przekazujemy zmienną ship_date do filtra daty wraz z parametrem "j F Y". Filtr date formatuję datę w zadanym formacie. Filtry dołącza się za pomocą znaków |, które stanowią nawiązanie do potoków w systemie Unix.

Każdy szablon Django ma dostęp do kilku wbudowanych znaczników i filtrów, z których wiele jest opisywanych w poniższej sekcji. Dodatek F zawiera pełną listę znaczników i filtrów. Dobrze się z nią zapoznać, by dowiedzieć się, o można osiągnąć. Można także tworzyć własne filtry i znaczniki; tym tematem zajmiemy się w rozdziale 10.

Korzystanie z systemu szablonów

Zagłębmy się w system szablonów, by zobaczyć, jak on działa — na tym etapie nie będziemy jeszcze integrować szablonów z widokami opisanymi w poprzednim rozdziale. Chcemy pokazać działanie szablonów w oderwaniu od pozostałej części Django. Najczęściej system szablonów stosuje się w połączeniu z widokami Django, ale warto pamiętać, że system szablonów to zwykła biblioteka Pythona, z której można korzystać wszędzie, nie tylko w widokach.

Oto najprostszy sposób wykorzystania systemu szablonów Django w kodzie Pythona:

  1. Utwórz obiekt Template, przekazując treść szablonu jako tekst.
  2. Wywołaj metodę render() obiektu Template wraz z zestawem zmiennych zwanym kontekstem. Wynikiem jest w pełni zrenderowany kod ze wszystkimi zmiennymi i znacznikami szablonowymi wyliczonymi i wstawionymi zgodnie z zawartością kontekstu.

Oto w jaki sposób rozwiązanie to wygląda w kodzie:

>>> from django import template
>>> t = template.Template("Nazywam się {{ name }}.")
>>> c = template.Context({'name': 'Adrian'})
>>> print t.render(c)
Nazywam się Adrian.
>>> c = template.Context({'name': 'Fred'})
>>> print t.render(c)
Nazywam się Fred.

Poniższe punkty w dokładniejszy sposób opisują działanie zaprezentowanego kodu.

Tworzenie obiektów szablonu

Najprostszym sposobem utworzenia obiektu typu Template jest bezpośrednie użycie jego klasy. Klasa Template znajduje się w module django.template. Konstruktor przyjmuje jeden argument, którym jest nieprzetworzony kod szablonu. Przejdźmy do interaktywnego interpretera Pythona, by przekonać się jak to działa w praktyce.

Z poziomu folderu mysite utworzonego wcześniej poleceniem django-admin.py startproject (patrz rozdział 2.), wpisz python manage.py shell, by uruchomić interaktywny interpreter kodu.

Specjalna powłoka Pythona

Jeśli korzystałeś wcześniej z Pythona, zapewne zastanawiasz się, dlaczego korzystamy z polecenia python manage.py shell zamiast zwykłego python. Oba polecenia uruchamiają interaktywny interpreter, ale polecenie shell wykonuje coś jeszcze: przed uruchomieniem interpretera informuje Django, z którego pliku ustawień należy skorzystać. Wiele części Django, włączając w to system szablonów, korzysta z pliku ustawień. Nie będziesz mógł ich używać, jeśli nie będą znały lokalizacji pliku ustawień.

Jeśli jesteś ciekaw, oto jak wszystko działa "od środka". Django szuka zmiennej środowiskowej nazwanej DJANGO_SETTINGS_MODULE, która powinna wskazywać ścieżkę importowania pliku ustawień settings.py. Przykładowo DJANGO_SETTINGS_MODULE może być równe 'mysite.settings', zakładając, że mysite znajduje się w ścieżce wyszukiwania Pythona.

Gdy uruchamiasz polecenie python manage.py shell, zajmuje się ono automatycznym ustawieniem zmiennej środowiskowej DJANGO_SETTINGS_MODULE. Zalecamy stosowanie tego polecenia w celu zminimalizowania liczby kroków konfiguracyjnych.

Gdy będziesz bardzo często korzystał z Django, zapewne przestaniesz używać polecenia manage.py shell na rzecz ręcznego ustawiania DJANGO_SETTINGS_MODULE w pliku .bash_profile lub innym pliku konfiguracyjnym powłoki.

Prześledźmy kilka podstawowych elementów szablonów:

>>> from django.template import Template
>>> t = Template("Nazywam się {{ name }}.")
>>> print t

Jeśli wykonujesz polecenia w trybie interaktywnym, na ekranie zobaczysz poniższy wynik:

<django.template.Template object at 0xb7d5f24c>

Adres 0xb7d5f24c będzie za każdym razem inny. Nie przejmuj się nim; nie ma dla nas żadnego znaczenia. To adres w pamięci, pod którym interpreter Pythona przechowuje obiekt.

Tworząc obiekt Template system szablonów kompiluje nieprzetworzony kod szablony do wewnętrznej, zoptymalizowanej postaci gotowej do zrenderowania. Jeśli jednak kod szablonu zawiera dowolne błędy składniowe, wywołanie Template spowoduje zgłoszenie wyjątku TemplateSyntaxError:

>>> from django.template import Template
>>> t = Template('{% notatag %}')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  ...
django.template.TemplateSyntaxError: Invalid block tag: 'notatag'

Termin "znacznik blokowy" (ang. block tag) dotyczy {% notatag %}. "Znacznik blokowy" i "znacznik szablonu" to synonimy.

System zgłasza wyjątek TemplateSyntaxError dla każdego z podanych niżej przypadków:

  • niepoprawnych znaczników,
  • niepoprawnych argumentów poprawnych znaczników,
  • błędnych filtrów,
  • niepoprawnych argumentów poprawnych filtrów,
  • złej składni szablonu,
  • niedomkniętych znaczników (dla znaczników wymagających zamknięcia).

Rendering szablonu

Po utworzeniu obiektu Template możemy zacząć przekazywać do niego dane, wykorzystując tzw. kontekst. Kontekst to po prostu zestaw nazw zmiennych i powiązanych z nimi wartości. Szablon używa tych danych do zastąpienia zmiennych i wyliczenia znaczników.

Kontekst w Django reprezentuje klasa Context znajdująca się w module django.template. Konstruktur klasy przyjmuje jeden opcjonalny argument: słownik odwzorowujący nazwy zmiennych na wartości. Wywołaj metodę render() szablonu przekazując kontekst, aby wypełnić szablon danymi:

>>> from django.template import Context, Template
>>> t = Template("Nazywam się {{ name }}.")
>>> c = Context({"name": "Stephane"})
>>> t.render(c)
u'Nazywam się Stephane.'

W tym miejscu warto wspomnieć, że wartością zwracaną przez t.render(c) jest obiekt Unicode, a nie standardowy tekst. Łatwo to rozpoznać po u przez apostrofem. Django używa obiektów unikodowych zamiast standardowych tekstów w całym frameworku. Jeśli rozumiesz reperkusje takiego podejścia, podziękuj Django, że nie musisz samemu zajmować się całą złożonością procesu. Jeśli nie rozumiesz reperkusji, nie przejmuj się nimi; wystarczy, że będziesz wiedział, iż obsługa Unicode w Django umożliwia łatwe i bezbolesne korzystanie z różnych zestawów znaków (między innymi polskich znaków diakrytycznych) w całym systemie.

Słowniki i konteksty

Słownik Pythona to odwzorowanie znanych kluczy na odpowiadające im wartości. Obiekt Context jest bardzo podobny do słownika, ale zapewnia dodatkową funkcjonalność opisywaną dokładniej w rozdziale 10.

Nazwy zmiennych muszą zaczynać się od litery (A-Z lub a-z) i mogą zawierać cyfry, znaki podkreślenia i kropki (kropki mają specjalne znaczenie, a czym za chwilę). Nazwy zmiennych są czułe na wielkość liter.

Poniżej znajduje się kod kompilujący i renderujący szablon bardzo podobny do tego, który został zaprezentowany na początku rozdziału:

>>> from django.template import Template, Context
>>> raw_template = """<p>Szanowny {{ person_name }},</p>

...
... <p>Dziękujemy za złożenie zamówienia w {{ company }}. Jest ono przewidziane do wysyłki w dniu {{ ship_date|date:"j F Y" }}.</p>
...
... {% if ordered_warranty %}
... <p>W paczce znajdzie się informacja na temat gwarancji.</p>
... {% else %}
... <p>Nie zamawiałeś gwarancji, więc musisz sobie radzić sam w przypadku zepsucia się produktu.</p>

... {% endif %}
...
... <p>Z wyrazami szacunku,<br />{{ company }}</p>"""
>>> t = Template(raw_template)
>>> import datetime
>>> c = Context({'person_name': 'Jan Kowalski',
...     'company': 'Outdoor Equipment',
...     'ship_date': datetime.date(2009, 4, 2),
...     'ordered_warranty': False})
>>> t.render(c)
u"<p>Szanowny Jan Kowalski,</p>\n\n<p>Dziękujemy za złożenie zamówienia w Outdoor Equipment. Jest ono przewidziane do wysyłki w dniu 2 April 2009.</p>\n\n\n<p>Nie zamawiałeś gwarancji, więc musisz sobie radzić sam w przypadku zepsucia się produktu.</p>\n\n\n<p>Z wyrazami szacunku,<br />Outdoor Equipment</p>"

Przeanalizujmy kod krok po kroku:

  • Najpierw importujemy klasy Template i Context. Obie znajdują się w module django.template.

  • Nieprzetworzony kod szablonu zapamiętujemy w zmiennej raw_template. Ponieważ kod szablonu rozciąga się na wiele wierszy, stosujemy zapis z potrójnymi znakami cudzysłowu. W Pythonie tekst z pojedynczym cudzysłowem nie może rozciągać się na wiele wierszy.

  • Tworzymy obiekt szablonu, t, przekazując zawartość raw_template do konstruktora klasy Template.

  • Importujemy moduł datetime ze standardowej biblioteki, ponieważ potrzebujemy go w następnych poleceniach.

  • Tworzymy obiekt klasy Context o nazwie c. Konstruktor przyjmuje jako parametr słownik Pythona, który odwzorowuje nazwy na wartości. W tym momencie informujemy, że person_name to 'Jan Kowalski', a company to 'Outdoor Equipment'.

  • W ostatnim kroku wywołujemy metodę render() dla obiektu szablonu, przekazując jej kontekst. Powoduje to zwrócenie zrenderowanego szablonu — przede wszystkim zastąpienie nazw zmiennych rzeczywistymi wartościami zmiennych i wykonanie wszystkich znaczników szablonowych.

    Zauważ, że akapit o braku gwarancji został wyświetlony, ponieważ zmienna ordered_warranty była równa False. Zauważ również, że przekazana data została sformatowana zgodnie z zaleceniami ('j F  Y') jako 2 April 2009. (Wkrótce dokładniej wyjaśnimy znaczniki formatujące używane przez filtr daty).

    Jeśli dopiero zaczynasz przygodę z Pythonem, możesz zastanawiać się, dlaczego wynik zawiera znaki przejścia do nowego wiersza \n zamiast zastosować je w trakcie wyświetlania tekstu. Wynika to z pewnych subtelności w działaniu interaktywnego interpretera Pythona: wywołanie t.render(c) zwraca tekst, a interaktywny interpreter domyślnie wyświetla reprezentację tekstu, a nie wartość tekstową do wyświetlenia. Jeśli chcesz zobaczyć wynik z prawidłowym podziałem na wiersze, użyj polecenia print t.render(c).

To podstawy stosowania systemu szablonów Django: napisz treść szablonu, utwórz obiekt Template, utwórz obiekt Context i wywołaj metodę render().

Ten szam szablon, wiele kontekstów

Po utworzeniu obiektu Template można go wykorzystać do zrenderowania wielu kontekstów. Oto przykład:

>>> from django.template import Template, Context
>>> t = Template('Witaj, {{ name }}')
>>> print t.render(Context({'name': 'Janie'}))
Witaj, Janie
>>> print t.render(Context({'name': 'Julie'}))
Witaj, Julie
>>> print t.render(Context({'name': 'Pat'}))
Witaj, Pat

Jeśli wykorzystujesz to samo źródło szablonu do zrenderowania wielu kontekstów, znacznie wydajniej jest jednokrotnie utworzyć obiekt Template i wielokrotnie wywołać jego metodę render():

# Źle
for name in ('Janie', 'Julie', 'Pat'):
    t = Template('Witaj, {{ name }}')
    print t.render(Context({'name': name}))

# Dobrze
t = Template('Witaj, {{ name }}')
for name in ('Janie', 'Julie', 'Pat'):
    print t.render(Context({'name': name}))

System analizy składniowej szablonów Django jest dosyć szybki. W zasadzie większość analizy przeprowadza się za pomocą pojedynczego wyrażenia regularnego. Rozwiązanie to jest znacznie bardziej optymalne od systemów szablonowych opartych na XML, które to nierzadko wprowadzają narzut związany z analizą XML i są tym samym dziesiątki razy wolniejsze od systemu renderowania Django.

Wyszukiwanie zmiennej kontekstowej

W prezentowanych do tej pory przykładach jako kontekst przekazywaliśmy proste wartości — głównie tekst, ale i obiekt datetime.date. system szablonu elegancko obsługuje również bardziej złożone struktury danych, na przykład listy, słowniki i dowolne obiekty.

Kluczem do efektywnego pobierania danych ze złożonych struktur w szablonach Django jest znak kropki (.). Użyj kropki, by uzyskać dostęp do kluczy słownika, atrybutów, indeksów i metod obiektów.

Najlepiej można to zilustrować kilkoma przykładami. Przypuśćmy, że przekazujemy do szablonu słownik. Aby uzyskać dostęp do kluczy słownika, używamy notacji kropkowej:

>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name }} ma {{ person.age }} lata.')
>>> c = Context({'person': person})
>>> t.render(c)
u'Sally ma 43 lata.'

W podobny sposób kropki zapewniają dostęp do atrybutów obiektu. Obiekt Pythona datetime.date zawiera atrybuty year, month i day. W szablonie za pomocą notacji kropkowej łatwo odczytamy wartości atrybutów:

>>> from django.template import Template, Context
>>> import datetime
>>> d = datetime.date(1993, 5, 2)
>>> d.year
1993
>>> d.month
5
>>> d.day
2
>>> t = Template('Miesiącem jest {{ date.month }} a rokiem jest {{ date.year }}.')
>>> c = Context({'date': d})

>>> t.render(c)
u'Miesiącem jest 5 a rokiem jest 1993.'

Poniższy przykład wykorzystuje klasę użytkownika, pokazując, że notacja kropkowa zapewnia dostęp do dowolnych właściwości dowolnych obiektów:

>>> from django.template import Template, Context
>>> class Person(object):
...     def __init__(self, first_name, last_name):
...         self.first_name, self.last_name = first_name, last_name
>>> t = Template('Witaj, {{ person.first_name }} {{ person.last_name }}.')
>>> c = Context({'person': Person('Jan', 'Kowalski')})
>>> t.render(c)
u'Witaj, John Smith.'

Notacja kropkowa współpracuje również z metodami obiektów. Każdy ciąg znaków w Pythonie zawiera metody upper() i isdigit(). Można je wywołać, używając tej samej notacji kropkowej:

>>> from django.template import Template, Context
>>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
>>> t.render(Context({'var': 'witaj'}))
u'witaj -- WITAJ -- False'

>>> t.render(Context({'var': '123'}))
u'123 -- 123 -- True'

Zauważ, że nie umieszcza się nawiasów w wywołaniach metod. Do metod nie można także przekazać argumentów, więc możliwe jest wywołanie tylko metod nie mających wymaganych parametrów. (Powody tych ograniczeń wyjaśnimy w dalszej części rozdziału).

Notacja kropkowa służy również do pobierania zawartości indeksów list:

>>> from django.template import Template, Context
>>> t = Template('Element 2 to {{ items.2 }}.')
>>> c = Context({'items': ['gruszki', 'banany', 'marchewki']})

>>> t.render(c)
u'Element 2 to marchewki.'

Nie dopuszcza się stosowania ujemnych wartości indeksów. Przykładowo umieszczenie w szablonie tekstu {{ items.-1 }} spowoduje zgłoszenie wyjątku TemplateSyntaxError.

Listy Pythona

Przypomnijmy, że listy w języku Python stosując indeksy zaczynające się od 0. Pierwszy element ma indeks 0, drugi 1 itd.

Notację kropkową można podsumować w następujący sposób: jeśli system szablonów odnajdzie kropkę w nazwie zmiennej, próbuje wyszukać zawartość wewnątrz zmiennej w następującej kolejności:

  • wyszukanie w słowniku (np. foo["bar"]),
  • wyszukanie atrybutu (np. foo.bar),
  • wywołanie metody (np. foo.bar()),
  • wyszukanie indeksu listy (np. foo[bar]).

System stosuje pierwsze rozwiązanie, które działa, pomijając w takiej sytuacji wszystkie pozostałe przypadki.

Notacja kropkowa może być wielokrotnie zagnieżdżona. Poniższy przykład wykorzystuje zmienną {{ person.name.upper }}, która przekłada się najpierw na wyszukanie w słowniku (person['name']), a następnie na wywołanie metody (upper()):

>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name.upper }} ma {{ person.age }} lata.')
>>> c = Context({'person': person})
>>> t.render(c)
u'SALLY ma 43 lata.'

Zachowanie wywoływania metod

Wywołania metod są nieco bardziej złożone niż inne typy wyszukań. Oto kilka kwestii, o których warto pamiętać:

  • Jeśli w trakcie wykonywania metody zgłosi ona wyjątek, zostanie on propagowany wyżej, chyba że wyjątek posiada atrybut silent_variable_failure ustawiony na wartość True. Jeśli atrybut wystąpi i ma wymaganą wartość, zmienna zostanie zrenderowana jako pusty tekst. Oto przykład:

    >>> t = Template("Mam na imię {{ person.first_name }}.")
    >>> class PersonClass3:
    ...     def first_name(self):
    ...         raise AssertionError, "foo"
    
    >>> p = PersonClass3()
    >>> t.render(Context({"person": p}))
    Traceback (most recent call last):
    ...
    AssertionError: foo
    
    >>> class SilentAssertionError(AssertionError):
    ...     silent_variable_failure = True
    >>> class PersonClass4:
    ...     def first_name(self):
    ...         raise SilentAssertionError
    >>> p = PersonClass4()
    >>> t.render(Context({"person": p}))
    u'Mam na imię .'
    
  • Wywołanie metody zadziała tylko w przypadku, gdy metoda nie wymaga parametrów. W przeciwnym razie system automatycznie przejdzie do następnego typu wyszukiwania (indeksu listy).

  • Oczywiście niektóre metody mają efekty uboczne. Byłoby niemądrym, a czasem wręcz niebezpiecznym z punktu widzenia bezpieczeństwa, by system szablonów mógł z nich korzystać.

    Przypuśćmy, że obiekt BankAccount zawiera metodę delete(). Szablon nie powinien mieć możliwości dołączenia zmiennej o wartości {{ account.delete }}.

    By temu zapobiec, ustaw dla funkcji właściwość alters_data:

    def delete(self):
        # Usuwa konto
    delete.alters_data = True
    

    System szablonów nie wykona żadnej metody oznaczonej w ten sposób. Innymi słowy, jeśli szablon będzie zawierał element {{ account.delete }}, system nie wykona metody delete(). W cichy sposób ją pominie.

Obsługa nieprawidłowych zmiennych

Domyślnie, jeśli zmienna nie istnieje, szablon renderuje ją jako pusty tekst bez zgłaszania błędu. Oto przykład:

>>> from django.template import Template, Context
>>> t = Template('Twoje imię to {{ name }}.')
>>> t.render(Context())
u'Twoje imię to .'
>>> t.render(Context({'var': 'hello'}))
u'Twoje imię to .'

>>> t.render(Context({'NAME': 'hello'}))
u'Twoje imię to .'
>>> t.render(Context({'Name': 'hello'}))
u'Twoje imię to .'

System pomija błąd po chichu zamiast zgłaszać wyjątek, ponieważ ma być nieczuły na ludzkie błędy, które się czasem zdarzają. W tym przypadku wszystkie wyszukania nie powiodły się z powodu ich braku lub złej wielkości liter. W rzeczywistym świecie jest niedopuszczalne, by witryna przestała być dostępna z powodu tak niewielkiej literówki.

Pamiętaj, że można zmienić domyślne zachowanie Django w kwestii błędów w szablonie, modyfikując ustawienia konfiguracyjne. Więcej informacji na ten temat pojawi się w rozdziale 10.

Zabawa z obiektami kontekstu

W większości sytuacji obiekty Context tworzy się, przekazując w pełni wypełniony słownik w wywołaniu konstruktora. Nic jednak nie stoi na przeszkodzie, by usuwać lub dodawać elementy do kontekstu już po jego utworzeniu, wykorzystując w tym celu standardową składnię słownikową Pythona:

>>> from django.template import Context
>>> c = Context({"foo": "bar"})
>>> c['foo']
'bar'
>>> del c['foo']
>>> c['foo']
Traceback (most recent call last):
  ...
KeyError: 'foo'
>>> c['newvariable'] = 'witaj'

>>> c['newvariable']
'witaj'

Podstawowe filtry i znaczniki szablonów

Jak wspomnieliśmy wcześniej, system szablonów zawiera wbudowany zestaw znaczników i filtrów. Ta część rozdziału zawiera krótki opis najpopularniejszych znaczników i filtrów.

Znaczniki

if/else

Znacznik {% if %} analizuje zmienną i jeśli jej wartość odpowiada wartości prawdy (czyli istnieje, nie jest pusta i nie jest fałszem), system wyświetli wszystko między {% if %} i {% endif %}. Oto przykład:

{% if today_is_weekend %}
    <p>Ciesz się weekendem!</p>
{% endif %}

Znacznik {% else %} jest opcjonalny:

{% if today_is_weekend %}
    <p>Ciesz się weekendem!</p>
{% else %}
    <p>Wracaj do pracy.</p>
{% endif %}

"Prawda" w Pythonie

W Pythonie oraz w szablonach Django poniższe obiekty odpowiadają fałszowi w kontekście wartości logicznych:

  • pusta lista ([]),
  • pusta krotka (()),
  • pusty słownik ({}),
  • pusty tekst (''),
  • wartość zero (0),
  • specjalny obiekt None,
  • obiekt False,
  • inne obiekty definiujące własne zachowanie w kontekście logicznym (stosowane w zaawansowanych zagadnieniach języka Python).

Wszystko inne traktowane jest jako wartość logiczna prawdy.

Znacznik {% if %} obsługuje operatory and, or lub not w celu testowania kilku zmiennych lub zanegowania wartości zmiennej. Przykłady:

{% if athlete_list and coach_list %}
    Dostępni są atleci i trenerzy.
{% endif %}

{% if not athlete_list %}
    Nie ma atletów.
{% endif %}

{% if athlete_list or coach_list %}
    Są jacyś atleci lub trenerzy.
{% endif %}

{% if not athlete_list or coach_list %}
    Nie ma atletów lub są jacyś trenerzy.
{% endif %}

{% if athlete_list and not coach_list %}
    Są jacyś atleci i nie ma trenerów.
{% endif %}

Znacznik {% if %} nie dopuszcza jednoczesnego użycia operatorów and i or, ponieważ w takiej sytuacji logika działania byłaby niejednoznaczna. Przykładowo poniższy zapis nie jest poprawny:

{% if athlete_list and coach_list or cheerleader_list %}

Użycie nawiasów do sterowania kolejnością operacji nie jest obsługiwane. Jeśli czujesz, że musiałbyś zastosować nawiasy, zastanów się nad obsługą logiki poza szablonami i przekaż wynik jako osobną zmienną szablonową. Ewentualnie użyj zagnieżdżonych znaczników {% if %} jak w przykładzie poniżej:

{% if athlete_list %}
    {% if coach_list or cheerleader_list %}
        Mamy atletów i trenerów lub cheerleaderki!
    {% endif %}
{% endif %}

Wielokrotne użycie tego samego operatora nie stanowi problemu. Nie można jedynie użyć różnych kombinacji operatorów. Poniższy kod jest poprawny:

{% if athlete_list or coach_list or parent_list or teacher_list %}

Nie ma znacznika {% elif %}. W takiej sytuacji użyj zagnieżdżonych znaczników {% if %}, by osiągnąć ten sam efekt:

{% if athlete_list %}
    <p>Oto atleci: {{ athlete_list }}.</p>
{% else %}
    <p>Brak atletów.</p>
    {% if coach_list %}
        <p>Oto trenerzy: {{ coach_list }}.</p>

    {% endif %}
{% endif %}

Pamiętaj o zamknięciu każdego {% if %} znacznikiem {% endif %}. W przeciwnym razie Django zgłosi wyjątek TemplateSyntaxError.

for

znacznik {% for %} umożliwia przejście przez każdy element pewnej sekwencji. Podobnie jak w Pythonie, stosowaną składnią jest for X in Y, gdzie ``Y to sekwencja do przeanalizowania, a X to nazwa zmiennej wykorzystywanej w konkretnym cyklu pętli. W każdym cyklu pętli system szablonów będzie renderował wszystko, co znajdzie się między znacznikami {% for %} i {% endfor %}.

Przykładowo można użyć poniższego kodu do wyświetlenia listy atletów zawartej w zmiennej athlete\_list:

<ul>
{% for athlete in athlete_list %}
    <li>{{ athlete.name }}</li>
{% endfor %}
</ul>

Dodanie słowa reversed do znacznika powoduje iterację przez elementy w odwrotnej kolejności:

{% for athlete in athlete_list reversed %}
...
{% endfor %}

Można zagnieżdżać znaczniki {% for %}:

{% for athlete in athlete_list %}
    <h1>{{ athlete.name }}</h1>

    <ul>
    {% for sport in athlete.sports_played %}
        <li>{{ sport }}</li>
    {% endfor %}
    </ul>
{% endfor %}

Częstym wzorcem jest sprawdzanie rozmiaru listy przed rozpoczęciem pętli oraz wyświetlenie tekstu, jeśli pętla jest pusta:

{% if athlete_list %}
    {% for athlete in athlete_list %}
        <p>{{ athlete.name }}</p>
    {% endfor %}
{% else %}
    <p>Nie ma atletów. Są tylko programiści komputerowi.</p>
{% endif %}

Ponieważ wzorzec ten jest tak często powtarzamy, znacznik for obsługuje opcjonalną klauzulę {% empty %}, która umożliwia określenie wyniku danego fragmentu w przypadku pustej pętli:

{% for athlete in athlete_list %}
    <p>{{ athlete.name }}</p>
{% empty %}
    <p>Nie ma atletów. Są tylko programiści komputerowi.</p>
{% endfor %}

System nie obsługuje wcześniejszego zakończenia pętli (przed przeanalizowaniem wszystkich elementów). Jeśli chcesz osiągnąć coś takiego, zmień zmienną przekazywaną do szablonu w taki sposób, by zawierała jedynie potrzebne wartości. Podobnie nie istnieje obsługa kontynuacji, która powodowałaby automatyczne przejście do następnego elementu pętli. (Powody tych decyzji zostały dokładnie opisane w dalszej części rozdziału w sekcji "Filozofie i ograniczenia").

Wewnątrz pętli {% for %} masz dostęp do zmiennej szablonowej nazwanej forloop. Zmienna zawiera kilka atrybutów informujących o postępach w wykonaniu pętli:

  • forloop.counter jest zawsze ustawione na wartość całkowitą reprezentującą liczbę wejść do pętli. Numeracja rozpoczyna się od wartości 1, więc przy pierwszej iteracji forloop.counter ma wartość 1. Oto przykład:

    {% for item in todo_list %}
        <p>{{ forloop.counter }}: {{ item }}</p>
    {% endfor %}
    
  • forloop.counter0 działa dokładnie tak samo jak forloop.counter, ale rozpoczyna odliczanie od wartości 0, czyli przy pierwszej iteracji zawiera wartość zero;

  • forloop.revcounter zawsze zawiera wartość całkowitą reprezentującą liczbę elementów pozostałych w pętli; w pierwszej iteracji będzie równy liczbie elementów w sekwencji, a w ostatniej iteracji będzie równy 1;

  • forloop.revcounter0 działa podobnie do forloop.revcounter, ale stosuje indeksowanie od zera; w pierwszej iteracji forloop.revcounter0 jest równe liczbie elementów minus 1, a w ostatniej iteracji jest równe 0;

  • forloop.first jest wartością logiczną ustawioną na True, jeśli jest to pierwsza iteracja pętli; stanowi dobry punkt zaczepienia dla specjalnych przypadków:

    {% for object in objects %}
        {% if forloop.first %}<li class="first">{% else %}<li>{% endif %}
        {{ object }}
        </li>
    
    {% endfor %}
    
  • forloop.last jest wartością logiczną ustawioną na True, jeśli jest to ostatnia iteracja pętli; często wykorzystuje się ją do wstawiania znaków pionowych kresek przy generowaniu listy łącz:

    {% for link in links %}{{ link }}{% if not forloop.last %} | {% endif %}{% endfor %}
    

    Powyższy szablon mógłby spowodować wygenerowanie następującego kodu:

    Link1 | Link2 | Link3 | Link4
    

    Innym typowym użyciem tego rozwiązania jest dodawanie przecinków w listach słów:

    Ulubione miejsca:
    {% for p in places %}{{ p }}{% if not forloop.last %}, {% endif %}{% endfor %}
    
  • forloop.parentloop zawiera referencję do nadrzędnego obiektu forloop, jeśli pętla jest zagnieżdżona. Oto przykład takiej sytuacji:

    {% for country in countries %}
        <table>
    
        {% for city in country.city_list %}
            <tr>
            <td>Kraj #{{ forloop.parentloop.counter }}</td>
            <td>Miasto #{{ forloop.counter }}</td>
            <td>{{ city }}</td>
    
            </tr>
        {% endfor %}
        </table>
    {% endfor %}
    

Magiczna zmienna forloop jest dostępna tylko wewnątrz pętli. Po osiągnięciu znacznika {% endfor %} znika.

Kontekst i zmienna forloop

Wewnątrz bloku {% for %} istniejąca zmienna jest usuwana, by zrobić miejsce dla zmiennej forloop. Django istniejącą zmienną o tej nazwie udostępnia jako forloop.parentloop. Najczęściej nie trzeba się tym przejmować, ale gdybyśmy sami przekazali do szablonu zmienną o nazwie forloop (choć tego nie zalecamy), wewnątrz bloku {% for %} będzie ona nosiła nazwę forloop.parentloop.

ifequal/ifnotequal

System szablonów Django celowo nie jest pełnym językiem programowania i nie pozwala na wykonywanie dowolnych poleceń języka Python. (Więcej na ten temat w części "Filozofie i ograniczenia"). Często zdarza się, że chcemy porównać dwie wartości i wyświetlić coś, jeśli są one równe — Django na te okoliczność udostępnia znacznik {% ifequal %}.

Znacznik {% ifequal %} porównuje dwie wartości i wyświetla wszystko między {% ifequal %} i {% endifequal %}, jeśli wartości są sobie równe.

Poniższy przykład porównuje zmienne szablonu user i currentuser:

{% ifequal user currentuser %}
    <h1>Witaj!</h1>
{% endifequal %}

Argumenty mogą być stałymi tekstami umieszczonymi w apostrofach lub cudzysłowach, więc poprawne są poniższe wartości:

{% ifequal section 'sitenews' %}
    <h1>Wiadomości</h1>
{% endifequal %}

{% ifequal section "community" %}
    <h1>Społeczność</h1>
{% endifequal %}

Podobnie jak znacznik {% if %}, także znacznik {% ifequal %} obsługuje opcjonalną klauzulę {% else %}:

{% ifequal section 'sitenews' %}
    <h1>Wiadomości</h1>
{% else %}
    <h1>Tutaj nie ma wiadomości</h1>
{% endifequal %}

Jako argumenty znacznika {% ifequal %} dopuszcza się tylko zmienne szablonów, teksty, liczby całkowite i zmiennoprzecinkowe. Oto poprawne przykłady użycia:

{% ifequal variable 1 %}
{% ifequal variable 1.23 %}
{% ifequal variable 'foo' %}
{% ifequal variable "foo" %}

Inne rodzaje zmiennych, na przykład słowniki, listy lub wartości logiczne nie mogą zostać użyte jako stałe w znaczniku {% ifequal %}. Oto niepoprawne przykłady użycia:

{% ifequal variable True %}
{% ifequal variable [1, 2, 3] %}
{% ifequal variable {'key': 'value'} %}

Jeśli chcesz sprawdzić, czy coś jest prawda lub fałszem, użyj znacznika {% if %} zamiast znacznika {% ifequal %}.

Komentarze

Podobnie jak w HTML lub Pythonie, system szablonów Django dopuszcza stosowanie komentarzy. Aby utworzyć komentarz, użyj konstrukcji {# #}:

{# To jest komentarz #}

Komentarz nie stanie się częścią wyniku zrenderowanego szablonu.

Komentarze stosujące powyższą składnię nie mogą obejmować wielu wierszy. To ograniczenie zwiększa wydajność analizatora składniowego. Poniższy szablon będzie wyglądał dokładnie tak samo niezależnie od tego, czy znajduje się w postaci oryginalnej, czy zrenderowanej (konstrukcja komentarza nie zostanie rozpoznana):

To jest {# to nie jest
komentarz #}
test.

Jeśli chcesz użyć komentarza wielowierszowego, zastosuj znacznik {% comment %} w następujący sposób:

{% comment %}
To jest komentarz
wielowierszowy.
{% endcomment %}

Filtry

Jak wyjaśniono we wcześniejszej części rozdziału, filtry szablonów to bardzo prosty sposób na zmianę zawartości zmiennej przez jej wyświetleniem. Filtry wykorzystują znak pionowej kreski:

{{ name|lower }}

Kod wyświetla zawartość {{ name }} po wcześniejszym przetworzeniu przez filtr lower, który konwertuje tekst na małe litery.

Można tworzyć łańcuchy filtrów — w ten sposób wynik działania jednego filtru trafia do następnego. Poniżej znajduje się przykład, który pobiera pierwszy element listy i konwertuje go na duże litery:

{{ my_list|first|upper }}

Niektóre filtry przyjmują argumenty. Argument filtru znajduje się po znaku dwukropka i zawsze wewnątrz cudzysłowów. Oto przykład:

{{ bio|truncatewords:"30" }}

Filtr powoduje wyświetlenie pierwszych 30 słów zmiennej bio.

Poniżej prezentujemy kilka najistotniejszych filtrów. Pełna lista znajduje się w dodatku F:

  • addslashes: dodaje znak lewego ukośnika po dowolnym lewym ukośniku, apostrofie lub cudzysłowie. Przydaje się do generowania tekstu dołączanego umieszczanego jako tekst JavaScript.

  • date: formatuje obiekty date oraz datetime zgodnie z formatem podanym jako parametr, na przykład:

    {{ pub_date|date:"j F Y" }}
    
    

    Dostępne znaki formatujące są opisane w dodatku F.

  • length: zwraca długość wartości. Dla listy zwraca liczbę elementów. Dla tekstu zwraca liczbę znaków. (Uwaga dla ekspertów: filtr działa dla dowolnego obiektu, który wie, jak określić własną długość, czyli obiektu definiującego metodę __len__().

Filozofie i ograniczenia

Po zapoznaniu się ze sposobem działania systemu szablonów Django, warto byłoby wyjaśnić kilka jego ograniczeń wraz z kilkoma filozofiami dotyczącymi zaimplementowania go w stanie, w którym istnieje.

Składnia szablonów, bardziej niż inne komponenty aplikacji internetowych, są bardzo subiektywne i różnie odbierane przez programistów. Fakt, że Python posiada dziesiątki jeśli nie setki udostępnionych publicznie implementacji różnych systemów szablonów stanowi tego ewidentny dowód. Każdy powstał zapewne z przeświadczenia, że wszystkie istniejące języki szablonowe są nieodpowiednie. (W zasadzie część programistów Pythona za punkt honoru przyjmuje napisanie własnego języka szablonowego! Jeśli jeszcze tego nie zrobiłeś, rozważ taką ewentualność. To zabawne ćwiczenie).

Pamiętaj, że Django nie wymaga stosowania wbudowanego języka szablonów. Ponieważ Django zostało napisane jako całościowy framework do produktywnego tworzenia aplikacji internetowych, często bardziej wygodnym okazuje się użycie systemu wbudowanego niż innych bibliotek systemów szablonów. Nie jest to jednak wymóg. Jak przekonasz się w dalszej części rozdziału, z Django można wykorzystać dowolny inny język szablonów Pythona.

Mamy bardzo silne przeświadczenie co do poprawności sposobu działania szablonów Django. System szablonów wywodzi się ze sposobu pracy w World Online i łączy w sobie doświadczenia twórców Django. Oto kilka tych filozofii:

  • Logikę biznesową należy rozdzielić od logiki prezentacyjnej. Programiści Django widzą system szablonów jako narzędzie sterujące prezentacją i związaną z tym logiką — to wszystko. System szablonów nie powinien obsługiwać funkcjonalności wychodzącej poza ten prosty cel.

    Z tego powodu nie można wywołać kodu Pythona bezpośrednio z szablonów. Całe "programowanie" ogranicza się w zasadzie do tego, co potrafią znaczniki szablonu. Choć można napisać własny znaczni, który wykonuje dowolne zadanie, domyślnie szablony Django nie dopuszczają wykonania dowolnego kodu Pythona.

  • Składnię należy oddzielić od HTML/XML. Choć system szablonów Django służy przede wszystkim do generowania kodu HTML, nic nie stoi na przeszkodzie, by stosować go dla dowolnych formatów innych niż HTML, na przykład zwykłego tekstu. Niektóre inne języki szablonów bazują na XML, umieszczając całą logikę szablonu w znacznikach i atrybutach XML, ale Django celowo unika tego ograniczenia. Wymuszenie korzystania z XML wprowadza cały wachlarz prostych, ludzkich pomyłek i trudnych w zrozumieniu komunikatów błędów. Stosowanie systemu przetwarzania XML wprowadza również zbyt duży narzut związany z obróbką i renderingiem szablonu.

  • Zakłada się, że projektanci znają kod HTML. System szablonów nie został zaprojektowany w taki sposób, by wyglądał ładnie w edytorach WYSIWYG takich jak Dreamweaver. To zbyt duże ograniczenie, które uniemożliwiłoby powstanie tak przyjaznej składni. Django oczekuje, że autorzy szablonów będą potrafili korzystać bezpośrednio z języka HTML.

  • Zakłada się, że projektanci nie są programistami języka Python. Autorzy systemu szablonów są przekonani, że większość szablonów stron WWW piszą projektanci, a nie programiści, więc nie należy zakładać znajomości języka Python.

    System stara się również zaspokoić sposób pracy małych zespołów, w których to szablony tworzące przez programistów języka Python. Oferuje sposoby rozszerzenia składni systemu przez pisanie czystego kodu Pythona (więcej na ten temat w rozdziale 10.).

  • Celem nie jest wymyślanie języka programowania. Celem jest zapewnienie wystarczającej swobody przez dostarczenie pętli i warunków, by możliwe było podejmowanie decyzji związanych z prezentacją danych.

Korzystanie z szablonów w widokach

Po zapoznaniu się z podstawami systemu szablonów wykorzystajmy zdobytą wiedzę do utworzenia widoku. Przypomnijmy sobie widok current_datetime z mysite.views przedstawiony w poprzednim rozdziale. Oto jaką miał postać:

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)

Zmieńmy widok tak, by używał szablonów Django. Zapewne początkowo chciałbyś użyć poniższego kodu:

from django.template import Template, Context from django.http import HttpResponse import datetime

def current_datetime(request):
now = datetime.datetime.now() t = Template("<html><body>Teraz jest {{ current_date }}.</body></html>") html = t.render(Context({'current_date': now})) return HttpResponse(html)

Oczywiście taki kod używa systemu szablonów, ale nie rozwiązuje problemów wskazanych na początku rozdziału. Szablon nadal znajduje się w kozie Pythona, więc trudno mówić o prawdziwej separacji danych i prezentacji. Rozwiążmy problem, umieszczając szablon w osobnym pliku, który widok wczyta.

Zastanówmy się najpierw nad umieszczeniem szablonu w systemie plików i użyciem funkcji wczytywania plików Pythona, by załadować zawartość szablonu. Oto, w jaki sposób mogłoby to wyglądać, jeśli założymy, że zawartość szablonu znajduje się w pliku /home/djangouser/templates/mytemplate.html:

from django.template import Template, Context
from django.http import HttpResponse
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    # Prosty sposób użycia szablonu z systemu plików.
    # To ZŁE rozwiązanie, bo nie bierze pod uwagę brakujących plików!
    fp = open('/home/djangouser/templates/mytemplate.html')
    t = Template(fp.read())
    fp.close()
    html = t.render(Context({'current_date': now}))
    return HttpResponse(html)

Rozwiązanie to nie jest eleganckie z kilku powodów:

  • Nie mierze pod uwagę brakujących plików. Jeśli plik mytemplate.html nie istnieje lub nie można go odczytać, wywołanie open() zgłosi wyjątek IOError.
  • Lokalizacja szablonu jest zaszyta w kodzie na stałe. Gdyby użyć tego samego rozwiązania dla każdej funkcji widoku, lokalizacja szablonów będzie powielona. Nie wspominamy nawet o ilości tekstu do napisania!
  • Zawiera mnóstwo zbędnego kodu. Najczęściej ma się znacznie lepsze rzeczy do roboty niż pisać wywołania open(), fp.read() i fp.close() przy każdym użyciu szablonu.

Aby rozwiązać te problemy, skorzystamy z wczytywania szablonów i folderów szablonów.

Wczytywanie szablonów

Django stosuje przyjazny i użyteczny interfejs do wczytywania szablonów z systemu plików, który ma za zadanie zmniejszyć redundancję zarówno wywołań wczytywania szablonów, jak i samych szablonów.

Aby użyć interfejsu wczytywania szablonów, trzeba poinformować system, gdzie znajdują się szablony. Miejscem na tego rodzaju dane jest plik ustawień — plik settings.py wspomniany w poprzednim rozdziale w trakcie omawiania konfiguracji ROOT_URLCONF.

Jeśli chcesz wykonać prezentowany przykład, otwórz plik settings.py i znajdź ustawienie TEMPLATE_DIRS. Domyślnie jest to pusta krotka wraz z automatycznie wygenerowanym komentarzem:

TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
)

Ustawienie to informuje system wczytywania szablonów Django, gdzie należy szukać szablonów. Wybierz folder, w którym chciałbyś przechowywać szablony i dodaj go do TEMPLATE_DIRS w następujący sposób:

TEMPLATE_DIRS = (
    '/home/django/mysite/templates',
)

Warto zwrócić uwagę na kilka kwestii:

  • Możesz wskazać dowolny folder, o ile on i szablony mogą być odczytywane przez użytkownika, z którego uprawnieniami działa proces serwera WWW. Jeśli nie możesz wymyślić odpowiedniego miejsca na szablony, zalecamy utworzenie folderu templates w projekcie (czyli w utworzonym w rozdziale 2. folderze mysite).

  • Jeśli zmienna TEMPLATE_DIRS zawiera tylko jeden folder, nie zapomnij o przecinku na końcu ciągu znaków z folderem!

    Źle:

    # Brakuje przecinka!
    TEMPLATE_DIRS = (
        '/home/django/mysite/templates'
    )
    

    Dobrze:

    # Przecinek we właściwym miejscu.
    TEMPLATE_DIRS = (
        '/home/django/mysite/templates',
    )
    

    Wynika to z faktu, iż Python wymaga przecinka w jednoelementowej krotce, by móc odróżnić ją od zwykłego wyrażenia z nawiasami. To częsty błąd początkujących programistów Pythona.

  • W systemie Windows dołącz literę napędu i zastosuj ukośniki w stylu Uniksowym, jak w poniższym przykładzie:

    TEMPLATE_DIRS = (
        'C:/www/django/templates',
    )
    
  • Stosowanie pełnych ścieżek (czyli ścieżek zaczynających się od korzenia systemu plików) jest najprostsze. Jeśli chcesz zapewnić sobie większą elastyczność, skorzystaj z faktu, iż plik konfiguracyjny Django to standardowy kod Pythona, więc zawartość TEMPLATE_DIRS można generować dynamicznie. Oto przykład:

    import os.path
    
    TEMPLATE_DIRS = (
        os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
    )
    

    Przykład używa "magicznej" zmiennej Pythona __file__, która zostaje automatycznie ustawiona na nazwę modułu Pythona, w którym znajduje się wykonywany kod. Z tej wartości wyłuskujemy nazwę folderu z plikiem settings.py (funkcja os.path.dirname()), a następnie łączymy folder z folderem szablonów w sposób niezależny od platformy (funkcja os.path.join()). Na końcu upewniamy się, że lewe ukośniki używane w systemie Windows do rozdzielania nazw folderów zamienia się na zwykłe ukośniki.

    Gdy już zahaczyliśmy o temat dynamicznego kodu Pythona w pliku ustawień, warto podkreślić potrzebę zwrócenia szczególnej uwagi, by nie wystąpiły tam błędy. Błąd składniowy lub błąd wykonania spowoduje, że witryna najprawdopodobniej przestanie działać.

Po ustawieniu folderu TEMPLATE_DIRS, następnym krokiem jest zmiana kodu widoku w taki sposób, by korzystał że ścieżek z pliku ustawień zamiast z zakodowanych na stałe wartości. Powróćmy do naszego przykładowego widoku current_datetime, modyfikując go nieznacznie:

from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponse
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    t = get_template('current_datetime.html')
    html = t.render(Context({'current_date': now}))
    return HttpResponse(html)

W tym przykładzie stosujemy funkcję django.template.loader.get_template() zamiast ręcznego wczytywania szablonu z systemu plików. Funkcja get_template() przyjmuje jako parametr nazwę szablonu, poszukuje go w systemie plików, otwiera szablon i zwraca skompilowany obiekt Template.

Naszym szablonem w tym przykładzie jest current_datetime.html, ale nie ma nic szczególnego w samym rozszerzeniu pliku .html. Szablony mogą mieć dowolne rozszerzenie, a nawet nie mieć go wcale.

Aby określić położenie szablonu w systemie plików, funkcja get_template() łączy foldery podane w TEMPLATE_DIRS z nazwą szablonu przekazaną do funkcji. Jeśli TEMPLATE_DIRS zawiera wartość '/home/django/mysite/templates', powyższe wywołanie get_template() szukałoby szablonu w lokalizacji /home/django/mysite/templates/current\_datetime.html.

Jeśli funkcja get_template() nie może odnaleźć szablonu o podanej nazwie zgłasza wyjątek TemplateDoesNotExist. Aby przekonać się, jak wygląda efekt zgłoszenia takiego wyjątku, uruchom serwer deweloperski poleceniem python manage.py runserver z poziomu projektu. Następnie w przeglądarce internetowej wskaż stronę aktywująca widok current_datetime (np. http://127.0.0.1:8000/time/). Jeśli DEBUG jest ustawione na wartość True i nie utworzyłeś jeszcze pliku current_datetime.html, zobaczysz stronę błędu Django wskazującą na wyjątek TemplateDoesNotExist.

Zrzut ekranu błędu "TemplateDoesNotExist". Rysunek 4-1. Strona błędu wskazująca niemożność odnalezienia pliku szablonu

Strona błędu podobna jest do strony przedstawionej w rozdziale 3., ale zawiera dodatkowy materiał. Materiał ten wskazuje, jakie pliki próbował wczytać szablon i co było powodem niepowodzenia (np. brak pliku). Tego rodzaju pomoc okazuje się nieoceniona, gdy starasz się poznać przyczyny problemów z szablonami.

Utwórz plik current_datetime.html w folderze template, stosując poniższy kod jako treść szablonu:

<html><body>Teraz jest {{ current_date }}.</body></html>

Odśwież stronę w przeglądarce internetowej. Powinieneś zobaczyć w pełni zrenderowaną stronę.

render_to_response()

Pokazaliśmy, w jaki sposób wczytać szablon, wypełnić kontekst i zwrócić obiekt HttpResponse zawierający zrenderowany szablon. Zastosowaliśmy funkcję get_template() zamiast na sztywno zakodowanej ścieżki i logiki pobierania pliku. Nadal jednak wysłanie wyniku wymaga wielu kroków. Ponieważ zaprezentowana logika dotyczy niemal każdego widoku, Django zapewnia skrót, który pozwala wczytać szablon, zrenderować go i zwrócić obiekt HTTPResponse — wszystko to w jednym wierszu kodu.

Skrótem tym jest funkcja o nazwie render_to_response() znajdującą się w module django.shortcuts. W większości przypadków będziesz chciał korzystać z tej funkcji zamiast ręcznie wczytywać szablony, tworzyć kontekst i generować obiekt HttpResponse — wyjątkiem może być sytuacja, gdy jesteś rozliczany z liczby napisanych wierszy kodu.

Poniżej znajduje się funkcja current_datetime przepisana tak, by korzystała z funkcji render_to_response():

from django.shortcuts import render_to_response
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    return render_to_response('current_datetime.html', {'current_date': now})

Istotna różnica! Omówmy wprowadzone zmiany:

  • Nie musimy już ręcznie importować get_template, Template, Context i HttpResponse. Zamiast tego importujemy django.shortcuts.render_to_response. Import modułu datetime pozostał.
  • W funkcji current_datetime nadal wyliczamy aktualną datę, ale wczytywanie szablonu, tworzenie kontekstu, rendering szablonu i tworzenie obiektu HttpResponse zostało przekazane do realizacji wywołaniu render_to_response(). Ponieważ render_to_response() zwraca obiekt HttpResponse, możemy zwrócić wynik funkcji jako wynik całego widoku.

Pierwszym argumentem funkcji render_to_response() jest nazwa szablonu. Drugim argumentem jest słownik używany do utworzenia obiektu Context dla szablonu. Jeśli drugi argument nie zostanie przekazany, funkcja użyje pustego słownika.

Sztuczka z locals()

Przyjrzyjmy się raz jeszcze naszej ostatniej wersji widoku current_datetime:

def current_datetime(request):
    now = datetime.datetime.now()
    return render_to_response('current_datetime.html', {'current_date': now})

Wielokrotnie znajdziesz się w sytuacji podobnej do tej w przykładzie: wyliczysz pewne wartości, zapamiętasz je w zmiennej i następnie wyślesz do szablonu. Szczególnie leniwi programiści zauważyliby, że stosowanie nazw zmiennych i zmiennych szablonu to redundancja. Co więcej, to dodatkowe znaki.

Jeśli jesteś jednym z tych leniwych programistów i chciałbyś uzyskać bardzo zwięzły kod, możesz skorzystać z wbudowanej w język Python funkcji locals(). Zwraca ona słownik odwzorowujący wszystkie lokalne nazwy zmiennych na ich wartości. W tym przypadku "lokalne" oznacza zmienne zdefiniowane w aktualnym zasięgu. Poprzedni kod można by zapisać następująco:

def current_datetime(request):
    current_date = datetime.datetime.now()
    return render_to_response('current_datetime.html', locals())

Tym razem zamiast ręcznie określać słownik kontekstu, przekazujemy wynik locals(), który zawiera wszystkie zmienne zdefiniowane do tej pory w funkcji. Zmieniliśmy nazwę definiowanej zmiennej na current_date, ponieważ takiej nazwy oczekuje szablon. W tym konkretnym przypadku użycie locals() nie zaowocowało znaczącą poprawą, ale ta sztuczka potrafi zaoszczędzić nieco pisania przy większej liczbie zmiennych — szczególnie przy chronicznym lenistwie.

Stosując funkcję locals() warto pamiętać, że dołącza ona wszystkie zmienne lokalne, co może spowodować przekazywanie większej ilości danych niż potrzeba. W poprzednim przykładzie locals() zwróci również zmienną request. Czy jest to pożądane, zależy od aplikacji i założonego poziomu perfekcjonizmu.

Podfoldery w get_template()

Przechowywanie wszystkich szablonów w jednym folderze byłoby wysoce nieefektywne. Nic nie stoi na przeszkodzie, by zacząć przechowywać szablony w podfolderach głównego szablonu. W zasadzie zalecamy takie rozwiązanie. W zasadzie niektóre bardziej zaawansowane elementy Django (system uogólnionych widoków opisywany w rozdziale 9.) zakłada stosowanie przez szablony właśnie takiej konwencji.

Przechowywanie szablonów w podfolderach folderu template jest proste. W trakcie wywoływania funkcji get_template() wystarczy podać nazwę podfolderu i ukośnik przed nazwą szablonu:

t = get_template('dateapp/current_datetime.html')

Ponieważ render_to_response() stanowi tylko niewielką otoczkę wokół get_template(), można tę samą sztuczkę wykonać z pierwszym argumentem render_to_response():

return render_to_response('dateapp/current_datetime.html', {'current_date': now})

Nie istnieje żadne ograniczenie co do głębokości poddrzew. Stosuj dowolne podfoldery w dowolnej ilości.

Uwaga

Użytkownicy systemu Windows powinni pamiętać o stosowaniu ukośników zamiast lewych ukośników. Funkcja get_template() zakłada stosowanie separatorów w stylu Uniksowym.

Znacznik szablonowy include

Po omówieniu systemu wczytywania szablonów możemy wprowadzić wbudowany znacznik szablonowy, który z niego korzysta: znacznik {% include %}. Znacznik ten umożliwia dołączenie zawartości innego szablonu. Argumentem jest nazwa szablonu, który należy dołączyć. Argument może być stały (ciąg znaków w cudzysłowach lub apostrofach) lub być zmienną. Jeśli ten sam fragment kodu szablonu pojawia się w kilku szablonach, zastanów się nad użyciem ``{% include %} w celu pozbycia się duplikatów.

Dwa poniższe przykłady dołączają zawartość szablonu nav.html. Przykłady są sobie równoznaczne i pokazują, że rodzaj znaczników nie ma żadnego znaczenia:

{% include 'nav.html' %}
{% include "nav.html" %}

Poniższy przykład wstawia zawartość szablonu includes/nav.html:

{% include 'includes/nav.html' %}

Poniższy przykład wstawia zawartość szablonu, którego nazwa została umieszczona w zmiennej template_name:

{% include template_name %}

Podobnie jak w przypadku get_template() nazwa pliku z szablonem powstaje na podstawie połączenia folderu zawartego w TEMPLATE_DIRS i podanej jako parametr nazwy.

Dołączane szablony są wykonywane w kontekście szablonu, który je wstawił. Rozważmy te dwa przykłady:

# mypage.html

<html>
<body>
{% include "includes/nav.html" %}
<h1>{{ title }}</h1>
</body>
</html>

# includes/nav.html

<div id="nav">
    Znajdujesz się w {{ current_section }}
</div>

Jeśli zrenderujesz mypage.html z kontekstem zawierającym current_section, wtedy zmienna ta będzie dostępna w dołączonym szablonie tak, jakbyś tego oczekiwał.

Jeśli jednak szablon dołączany za pomocą {% include %} nie odnajdzie wymaganej zmiennej, Django wykona jedną z dwóch operacji:

  • jeśli DEBUG jest ustawione na True, zostanie zgłoszony wyjątek TemplateDoesNotExist,
  • jeśli DEBUG jest ustawione na False, Django po cichu pominie błąd, wstawiając pustą wartość.

Dziedziczenie szablonów

Do tej pory wszystkie przykłady były bardzo małe, ale w rzeczywistości system szablonów Django ma służyć do tworzenia bardzo rozbudowanych stron HTML. Prowadzi to do powstania typowego problemu: jak na poziomie całej witryny zredukować duplikację podobnych obszarów kodu, na przykład paska nawigacyjnego?

Klasycznym sposobem rozwiązania tego problemu jest użycie dołączeń na poziomie serwera, specjalnych dyrektyw powodujących dołączenie jednej strony wewnątrz innej. Django również obsługuje to podejście dzięki opisanemu wcześniej znacznikowi {% include %}. Jednak preferowanym w Django i bardziej eleganckim sposobem rozwiązania tego problemu jest dziedziczenie szablonów.

Dziedziczenie szablonów pozwala stworzyć bazowy "szkielet" szablonu, który zawiera wszystkie podstawowe elementy szablonu oraz dodatkowe bloki, w których swoją treść mogą umieszczać szablony potomne.

Sprawdźmy ten system w działaniu, tworząc bardziej złożony szablon dla widoku current_datetime, edytując plik current_datetime.html:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="pl">

<head>
    <title>Aktualny czas</title>
</head>
<body>
    <h1>Moja pomocna zegarynka</h1>
    <p>Teraz jest {{ current_date }}.</p>

    <hr>
    <p>Dziękuję za odwiedziny.</p>
</body>
</html>

Wszystko wygląda poprawnie, ale co się stanie, jeśli będziemy chcieli utworzyć szablon dla innego widoku — na przykład widoku hours_ahead z rozdziału 3. Gdyby nowy szablon również miał być w pełni poprawnym szablonem HTML, napisalibyśmy następujący kod:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="pl">
<head>
    <title>Czas przyszły</title>
</head>
<body>

    <h1>Moja pomocna zegarynka</h1>
    <p>Za {{ hour_offset }} godzin będzie {{ next_time }}.</p>

    <hr>
    <p>Dziękuję za odwiedziny.</p>

</body>
</html>

Widać wyraźnie, że powieliliśmy mnóstwo kodu HTML. Wyobraź sobie typową witrynę z paskiem nawigacyjnym, kilkoma arkuszami stylów, skryptami JavaScript — ilość powielanego kodu byłaby w tym przypadku ogromna.

W rozwiązaniu stosującym dołączenia po stronie serwera wydobywa się podobne fragmenty z obu szablonów i umieszcza w osobnych plikach dołączanych później do obu szablonów. Być może nagłówek umieścilibyśmy w pliku o nazwie header.html:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="pl">

<head>

Stopkę zapewne umieścilibyśmy w pliku footer.html:

    <hr>
    <p>Dziękuję za odwiedziny.</p>
</body>

</html>

W systemie bazującym na dołączeniach nagłówek i stopka są proste. To reszta wygląda mniej przyjaźnie. Obie strony zawierają znacznik tytułu — <h1>Moja pomocna zegarynka</h1> — ale nie można go umieścić w pliku header.html, ponieważ znacznik <title> zawiera inną treść. Gdybyśmy chcieli umieścić <h1> musimy również umieścić <title> co uniemożliwiłoby stosowanie różnych tytułów na poszczególnych stronach. Czy widzisz dokąd zmierzamy?

System dziedziczenia szablonów rozwiązuje ten problem. Można go traktować jako "odwrócony" system dołączeń po stronie serwera. Zamiast definiować fragmenty, które są wspólne, definiuje się fragmenty, które są różne.

Pierwszy krok polega na definicji szablonu bazowego — szkieletu strony, który później wypełnią szablony potomne. Oto szablon bazowy dla naszego przykładu:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="pl">
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>

    <h1>Moja pomocna zegarynka</h1>
    {% block content %}{% endblock %}
    {% block footer %}
    <hr>
    <p>Dziękuję za odwiedziny.</p>
    {% endblock %}
</body>

</html>

Ten podstawowy szablon, który nazwiemy base.html, definiuje prosty szablon HTML wykorzystywany dla wszystkich stron witryny. To szablony potomne odpowiadają za nadpisanie, dodanie lub pozostawienie zawartości aktualnych bloków. (Jeśli wykonujesz ten przykład, zapisz szablon w pliku base.html w folderze template).

W przykładzie użyliśmy nieopisywany do tej pory znacznik: {% block %}. Wszystko, co czyni ten znacznik, to informowanie systemu szablonów, że szablony potomne mogą nadpisać wskazaną zawartość.

Skoro mamy już szablon bazowy, możemy zmodyfikować istniejący szablon current_datetime.html, by z niego korzystał:

{% extends "base.html" %}

{% block title %}Aktualny czas{% endblock %}

{% block content %}
<p>Teraz jest {{ current_date }}.</p>
{% endblock %}

Dodatkowo wykonajmy szablon dla widoku hours_ahead z rozdziału 3. (Zmianę kodu widoku, by używał szablonu zamiast zakodowanej na stałe treści pozostawiamy jako ćwiczenie). Oto jak mógłby wyglądać taki szablon:

{% extends "base.html" %}

{% block title %}Czas przyszły{% endblock %}

{% block content %}
<p>Za {{ hour_offset }} godzin będzie {{ next_time }}.</p>
{% endblock %}

Czy to nie piękne? Każdy szablon zawiera tylko kod, który jest unikatowy dla niego. Nie ma żadnej redundancji. Jeśli konieczne są zmiany na poziomie całej witryny, wystarczy zmienić plik base.html. Wszystkie pozostałe szablony automatycznie zauważą zmianę.

Oto jak działa cały system. W momencie wczytywania szablonu current_datetime.html system widzi znacznik {% extends %} informujący, że jest to szablon potomny. System automatycznie wczytuje szablon bazowy — w tym przypadku base.html.

W tym momencie system szablonów zauważa trzy znaczniki {% block %} w pliku base.html i zastępuje je zawartością, która pojawiła się w szablonie potomnym. Oznacza to, że użyje zawartości bloków {% block title %} i {% block content %} z szablonu potomnego.

Zauważ, że szablon potomny nie przedefiniował bloku stopki, więc wynikową wartością pozostała wartość zdefiniowana w szablonie bazowym. Zawartość znacznika {% block %} z szablonu bazowego zawsze stanowi wartość rezerwową.

Dziedziczenie nie wpływa na kontekst szablonu. Innymi słowy, dowolny szablo z drzewa dziedziczenia będzie miał dostęp do wszystkich zmiennych kontekstu.

Można stosować dowolną liczbę poziomów dziedziczenia. Najczęściej jednak stosuje się podejście trójpoziomowe:

  1. Utwórz szablon base.html, który definiuje podstawowy wygląd witryny. To część, która zmienia się najrzadziej, jeśli w ogóle się zmienia.
  2. Utwórz szablon base_SEKCJA.html dla każdej "sekcji" witryny (np. base_photos.html lub base_forum.html). Szablony te rozszerzają szablon base.html i dodają style lub design specyficzny dla sekcji.
  3. Utwórz poszczególne szablony dla każdego rodzaju strony, na przykład strony forum lub galerii zdjęć. Szablony te dziedziczą po szablonie sekcji.

To podejście maksymalizuje ponowne użycie kodu i ułatwia dodanie elementów do współdzielonych obszarów, na przykład nawigacji dotyczącej określonej sekcji.

Oto kilka dodatkowych rad dotyczących korzystania z dziedziczenia szablonów:

  • Jeśli szablon używa {% extends %}, musi to być pierwszy znacznik w szablonie. W przeciwnym razie dziedziczenie nie zadziała.
  • Ogólnie, im więcej znaczników {% block %} zawiera szablon bazowy, tym lepiej. Pamiętaj, że szablony potomne nie muszą definiować wszystkich bloków rodzica, więc w rodzicu można umieścić sensowne wartości domyślne. Lepiej mieć więcej punktów zaczepienia niż mniej.
  • Jeśli przyłapiesz się na duplikacji kodu w kilku szablonach, najprawdopodobniej oznacza to, że powinieneś dodać nowy znacznik {% block %} do szablonu rodzica.
  • Jeśli musisz pobrać zawartość bloku z szablonu rodzica, użyj {{ block.super }}, która jest "magiczną" zmienną zapewniającą zrenderowany tekst bloku z szablonu rodzica. Ten element przydaje się, gdy chcesz dodać nową treść do bloku, zamiast ją zastępować.
  • W tym samym szablonie nie może pojawić się kilka znaczników {% block %} dotyczących tej samej nazwy. Wynika to z faktu, iż blok działa w dwóch kierunkach. Nie określa tylko i wyłącznie dziury do wypełnienia, ale również definiuje zawartość, która ma tę lukę wypełnić w szablonie rodzica. Gdyby pojawiły się dwa bloki o tej samej nazwie, system nie potrafiłby rozstrzygnąć, którego z nich użyć w szablonie rodzica.
  • Szablon, którego nazwa została przekazana do {% extends %} stosuje tę samą metodę wczytywania co funkcja get_template(), czyli jest dodawana do ścieżek wskazanych w ustawieniu TEMPLATE_DIRS.
  • W większości sytuacji argumentem {% extends %} jest stała tekstowa, ale nic nie stoi na przeszkodzie, by była to zmienna, jeśli szablon rodzica wybiera się dopiero w trakcie działania programu. Pozwala to osiągnąć ciekawe efekty.

Co dalej?

Gdy poznałeś już podstawy systemu szablonów Django, co dalej?

Wiele nowoczesnych witryn internetowych korzysta z baz danych: zawartość witryny znajduje się relacyjnej bazie danych. Umożliwia to dobrą separację danych i logiki (podobnie jak widoki i szablony zapewniają separację logiki i prezentacji).

Następny rozdział omawia narzędzia Django związane z obsługa baz danych.