Django: Prezentace dat podruhé

Používání generických pohledů, které jsme si představili minule, je pohodlné, ale u složitějších prezentací dat si s nimi bohužel nevystačíme vždy. Proto se v dnešním díle podíváme na několik netriviálních příkladů pohledů a šablon.
Seriál: Hrajeme si s Djangem (16 dílů)
- Django: Úvod a instalace 14. 8. 2009
- Django: Nastavení projektu a první pokusy 21. 8. 2009
- Django: Databázový model 28. 8. 2009
- Django: Databázový model podruhé 4. 9. 2009
- Django: Administrace 11. 9. 2009
- Django: Prezentace dat 18. 9. 2009
- Django: Prezentace dat podruhé 25. 9. 2009
- Django: Zpracovávání formulářů 2. 10. 2009
- Django: Autentizace a autorizace 9. 10. 2009
- Django: Nahrávání souborů 16. 10. 2009
- Django: Zabudované aplikace 23. 10. 2009
- Django: Rozšiřování možností Djanga 30. 10. 2009
- Django: Internacionalizace 6. 11. 2009
- Django: Nasazování projektu 13. 11. 2009
- Django: Kešování a škálování 20. 11. 2009
- Django: Závěr 27. 11. 2009
Jak si jistě dobře vzpomínáte, náš ukázkový projekt obsahoval kromě jiného i tabulku s filmy. Katalog filmů je klíčovou součástí webových stránek videopůjčovny. Sestává se ze seznamu položek a detailů jednotlivých titulů.
Seznam položek
Idea pro vytvoření seznamu filmů je taková: protože by katalog výhledově obsahoval i tisíce položek, uživatel je musí mít možnost jednoduše vyhledávat podle názvů titulu a případně listovat v abecedním seznamu. Po zadání hledaného výrazu do formuláře nebo kliknutí na počáteční písmeno názvu se zobrazí odpovídající záznamy. Opět začneme nejprve šablonou, kterou uložíme do souboru templates/film_list.html
:
{% extends "base.html" %} {% block content %} <h1>{% block title %}Nabídka titulů{% endblock %}</h1> <div class="letters"> {% for letter in letters %} {% ifequal letter prefix|upper %} {{ letter }} {% else %} <a href="/nabidka/{{ letter|lower }}/">{{ letter }}</a> {% endifequal %} {% endfor %} </div> {% if films %} <table> {% for film in films %} <tr> <td><a href="/nabidka/detail/{{ film.id }}/">{{ film.name_czech }}</a></td> <td>{{ film.name_english }}</td> <td>({{ film.year }})</td> </tr> {% endfor %} </table> {% else %} <form action="/nabidka/" method="GET"> <input type="text" name="search" value="{% if search %}{{ search }}{% endif %}"> <input type="submit" value="Vyhledat"> </form> {% if prefix or search %} <p>Nenalezeno.</p> {% endif %} {% endif %} {% endblock %}
Prvním cyklem for (7. řádek) vypíšeme počáteční písmena názvů filmů a přidělíme jim příslušné odkazy. Podmínkou ifequal zajistíme, aby se odkaz nepřidal, pokud je uživatel na stránce s daným písmenem. Jsou zde použity filtry lower
a upper
, které z daného písmena vytvoří minusku či verzálku (tj. malé či velké písmeno). Jestliže našemu zadání odpovídají nějaké filmy, vypíšeme je druhým cyklem for (18. řádek). V opačném případě zobrazíme vyhledávací formulář (27. řádek) a případně chybovou hlášku. Mimochodem, v Djangu 1.1 a novějším by zde šel použít namísto kombinace if
a for
přehlednější zápis for-empty.
Pozornější z vás si mohli povšimnout, že do formuláře vyhledávání vypisujeme zadaný řetězec, takže by na tomto místě mohl nastat potenciální XSS útok. Bezpečnost je v Djangu brána velmi vážně a všechny proměnné v šablonách jsou automaticky escapovány. Významně se tak snižuje riziko tohoto typu útoku. V případě potřeby můžeme automatické escapování vypnout pomocí značky autoescape.
Kromě šablony potřebujeme ještě pohled, kterým předáme šabloně data. Pro zobrazení katalogu nepoužijeme generický pohled, ale napíšeme si svůj vlastní, který nám umožní vyhledávání a listování podle písmen. Otevřeme si soubor s pohledy video_store/views.py
a vložíme do něj následující kód:
from models import Film from django.shortcuts import render_to_response from django.template import RequestContext def film_list(request, prefix=None): search = request.GET.get('search') if prefix is not None: films = Film.objects.filter(name_czech__istartswith=prefix) elif search is not None: films = Film.objects.filter(name_czech__icontains=search) else: films = None letters = [chr(c) for c in range(ord('A'), ord('Z') + 1)] return render_to_response('film_list.html', {'search': search, 'prefix': prefix, 'films': films, 'letters': letters}, context_instance=RequestContext(request))
Námi definovaný pohled film_list
má kromě objektu request
další parametr, nazvaný prefix
. Pomocí něj můžeme pohledu předat počáteční písmeno názvu filmu. Pokud žádné písmeno nevybereme, použije se výchozí hodnota None
. Jak uvidíme za chvíli, předávání parametrů má na starosti mapování URL. Na dalším řádku z objektu request
vybíráme případný GET parametr search
, který nám posílá vyhledávací formulář. Tento parametr se také zobrazí v URL, ovšem až za otazníkem, tedy např. v URL http://127.0.0.1:8000/nabidka/?search=term
se přiřadí proměnné search
řetězec 'term'
. Metoda get zajistí, že pokud daný klíč ve slovníku neexistuje, místo vyhození výjimky je vrácena hodnota None
.
Poté z databáze vybíráme filmy podle zadaných kritérií. Opět někteří z vás zbystřili a ptají se, jestli je daný kód chráněn proti SQL injekci. Odpověď zní ano, ORM Djanga se o to postará a vstupní data automaticky ošetří. Dále jsme do proměnné letters
přiřadili pomocí generátoru seznamu písmena z rozsahu A až Z. Stejného výsledku bychom docílili použitím „obyčejného“ cyklu for nebo vypsáním seznamu písmen ručně, tohle je jenom úspornější zápis.
Na posledním řádku posíláme šabloně naše parametry. V případech jako je tento si můžeme ušetřit práci použitím funkce locals, která z lokálních proměnných sestaví slovník. Požadavkem je, že se proměnné budou jmenovat stejně jako klíče slovníku, což je tady splněno. Poslední řádek bychom tak mohli nahradit za:
return render_to_response('film_list.html', locals(), context_instance=RequestContext(request))
… a dosáhli bychom stejného výsledku. Teď nám zbývá správně nastavit URL. Otevřeme si soubor urls.py
v adresáři projektu a upravíme první výskyt proměnné urlpatterns
:
urlpatterns = patterns('hrajeme_si', (r'^time/$', 'pokus.views.current_time'), (r'^nabidka/$', 'video_store.views.film_list'), (r'^nabidka/([a-z])/$', 'video_store.views.film_list'), )
Vidíme zde dvojnásobné přiřazení URL k našemu pohledu. V prvním případě se pohledu nebude předávat žádný argument, ve druhém ano, podle použitého regulárního výrazu. Když do prohlížeče zadáme adresu http://127.0.0.1:8000/nabidka/, použije se první definice, proměnná prefix
bude mít hodnotu None
a nezobrazí se žádné záznamy.
V případě, kdy přejdeme např. na adresu http://127.0.0.1:8000/nabidka/h/, bude použita druhá definice, protože písmeno h
vyhovuje regulárnímu výrazu [a-z]
, takže bude předáno našemu pohledu a vypíšou se odpovídající záznamy.
([a-z])
napsali (?P<prefix>[a-z])
.Pokud zadaná adresa danému regulárnímu výrazu neodpovídá, zobrazí se chyba 404 (nenalezeno). Regulární výraz tak ošetří všechny nečekané vstupy. Zbývá poznamenat, že je tento příklad dost zjednodušený, názvy filmů obyčejně začínají i na jiné znaky, kupříkladu čísla.
Detail konkrétní položky
Když už máme hotové hledání a procházení seznamem filmů, podíváme se ještě, jak by vypadalo detailní zobrazení titulu. K jednotlivým záznamům se dostaneme pomocí sloupce id
, stačí správně namapovat URL a vybrat odpovídající položku z tabulky. Nejprve si však ukážeme odpovídající šablonu (templates/film_detail.html
):
{% extends "base.html" %} {% block content %} <h1>{% block title %}{{ film.name_czech }} ({{ film.year }}){% endblock %}</h1> <table> <tr> <th>Původní název</th> <td>{{ film.name_original }}</td> </tr> <tr> <th>Cena za půjčení</th> <td>{{ film.price }} Kč</td> </tr> <tr> <th>Formát</th> <td>{{ film.format }}</td> </tr> <tr> <th>Dostupné v provozovnách</th> <td> {% for store in film.store.all %} {{ store }}{% if not forloop.last %}, {% endif %} {% endfor %} </td> </tr> </table> <p>{{ film.description }}</p> <p><a href="/nabidka/{{ film.name_czech|first|lower }}/">Zpět do nabídky</a></p> {% endblock %}
Začátek šablony je celkem fádní. V nadpisu stránky zobrazíme název filmu a rok natočení, v tabulce pod tím další údaje. Mezi nimi je i atribut store
, který je ve vztahu many-to-many s modelem Store
, takže musíme pomocí cyklu for zobrazit jednotlivé navázané položky. Všimněte si, že v tomto cyklu data vybíráme z QuerySetu film.store.all
, ne z atributu film.store
. Při oddělování položek čárkou je využita zabudovaná proměnná forloop. Na předposledním řádku šablony se nachází odkaz pro návrat do seznamu titulů se stejným počátečním písmenem. Odkaz generujeme z názvu aktuálně vybraného filmu. Filtrem first
vybereme první písmeno z názvu, které pak převedeme na minusku pomocí dalšího filtru, lower
.
V tomto případě by se již dalo uvažovat o použití generického pohledu, konkrétně list_detail.object_detail. V jednom z dalších dílů však budeme šablonu upravovat a doplňovat další funkcionalitu. Takže si teď připíšeme na konec souboru video_store/views.py
vlastní pohled:
from models import FORMAT_CHOICES from django.http import HttpResponseRedirect def film_detail(request, film_id): try: film = Film.objects.filter(id=film_id)[0] except IndexError: return HttpResponseRedirect('/nabidka/') film.format = FORMAT_CHOICES[film.format][1] return render_to_response('film_detail.html', {'film': film}, context_instance=RequestContext(request))
Dodatečný parametr film_id
určuje požadované id
záznamu. Pomocí tohoto parametru budeme z databáze vybírat náš objekt (lze použít i metodu get). Tento výběr je celý obalený blokem try-except. Větev except IndexError
odchytí stav, kdy daný záznam nebude nalezen, a QuerySet bude tím pádem prázdný a dotaz bude přesměrován na stránku se seznamem filmů. Ještě bychom měli zajistit, aby se atribut format
nezobrazoval jako číslo, ale jako text. Tento text je uložen v n-tici FORMAT_CHOICES
(viz soubor video_store/models.py
), ze které jej jednoduše získáme podle indexu.
K dokončení stačí přidat další položku do souboru urls.py
a jsme hotovi:
urlpatterns = patterns('hrajeme_si', (r'^time/$', 'pokus.views.current_time'), (r'^nabidka/$', 'video_store.views.film_list'), (r'^nabidka/([a-z])/$', 'video_store.views.film_list'), (r'^nabidka/detail/(d+)/$', 'video_store.views.film_detail'), )
Regulární výraz d+
zastupuje posloupnosti jedné a více číslic, což odpovídá jakékoliv hodnotě id
. Přejdeme-li v prohlížeči na libovolný detail filmu, uvidíme očekávaný výpis.
Související odkazy
- Šablonovací systém a URL na Djangoproject.com
- Čtvrtá a osmá kapitola v The Definitive Guide to Django
- Ukázkový příklad ke stažení.
V následujícím díle zjistíme, jak v Djangu bezbolestně generovat a zpracovávat formuláře.
V případě nenalezení filmu by se měla zobrazit normální chybová stránka a ne provádět přesměrování.
Ano, to je dobrý postřeh. Vaše řešení je nejspíš správnější, já osobně preferuji uživatelsky přívětivější způsob. V každém případě přikládám kód, jak toho dosáhnout:
… nebo lépe, pomocí zkratky:
Rozhodně si to píšu, že se o tom ještě musím zmínit. Díky.
Prima, jen k té „uživatelské přívětivosti“ – chybová stránka samozřejmě může uživateli poskytnout všechny informace, které potřebuje, takže bude i přívětivější než obecná stránka, která neví o tom, že uživatel nenašel, co hledal.