Django: Rozšiřování možností Djanga

Holá instalace Djanga má mnoho různých funkcí. Určitě však časem zjistíme, že nám nějaká maličkost chybí. Proto se v tomto díle podíváme na několik ukázek možného rozšíření.
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
Přidání vlastního filtru do šablony
Určitě si vzpomínáte na šestý díl seriálu, kde jsme se učili prezentovat data pomocí generických pohledů. V příkladu jsme si vypsali mimo jiné e-mailovou adresu provozovny. Tyto adresy nejsou nijak chráněné, takže je může zaznamenat i ten nejhloupější spambot, což představuje problém. Proto si napíšeme jednoduchý filtr, pomocí kterého e-mailovou adresu transformujeme do zakódovaného řetězce v JavaScriptu.
Filtr je vlastně obyčejná funkce, které předáme nějaký vstup a ona vrátí odpovídající výstup. Každá aplikace, která je uvedená v konstantě INSTALLED_APPS v souboru settings.py, může mít svou sadu filtrů a značek. Stačí v adresáři aplikace (v našem případě video_store) vytvořit podadresář templatetags a umístit do něj dva soubory. První je potřeba pojmenovat __init__.py a bude stačit, když bude prázdný. Označuje totiž, že daný adresář obsahuje modul napsaný v Pythonu. Druhý soubor můžeme pojmenovat libovolně, já si zvolil název misc.py, protože do tohoto souboru potom možná časem přidám další různorodé funkce (anglicky miscellaneous). Obsah souboru vypadá takto:
# coding: utf-8
from base64 import b64encode
from django import template
from django.utils.safestring import mark_safe
from django.template.defaultfilters import stringfilter
register = template.Library()
@register.filter
@stringfilter
def email(value):
return mark_safe('<script type="text/javascript">display_email("%s");</script><noscript>(e-mail je chráněn JavaScriptem)</noscript>' % b64encode(value))
Na sedmém řádku se nachází přiřazení, které určuje, že se jedná o knihovnu filtrů a značek. Proměnná se musí jmenovat register a musí obsahovat objekt template.Library — takto to stanovuje konvence. Na devátém a desátém řádku můžeme vidět dva dekorátory. První z nich (@register.filter) registruje nový filtr. Kdybychom chtěli přidat novou značku, napíšeme místo toho @register.tag. Tento dekorátor má nepovinný parametr name, jež určuje název nového filtru či značky. My jsme ho vynechali, takže se název filtru odvodí z názvu funkce. Druhý dekorátor dbá na to, aby funkce dostala jako vstup řetězec. V případě, kdy by filtr byl připojen např. k číslu, by při zpracování šablony došlo k vyhození výjimky AttributeError. Tento dekorátor také převádí objekty na řetězce voláním metod __str__ nebo __unicode__.
Samotný filtr vezme hodnotu a vrátí HTML kód s JavaScriptem. Funkce mark_safe označí řetězec za bezpečný. Nedojde tak k automatickému escapování HTML značek a entit, takže se kupříkladu místo kódu <noscript> vypíše očekávaná hodnota <noscript>. Tuto vlastnost vypínejte s rozmyslem, hlavně při vypisování uživatelem zadaných dat. Pokud byste chtěli ošetřit uživatelem zadaná data sami, můžete k tomuto účelu použít funkci escape z modulu django.utils.html. V našem případě zaručíme bezpečnost vstupu tím, že ho zakódujeme pomocí kódování base64, které obsahuje pouze písmena abecedy, číslice a znaky plus, lomítko a rovnítko (viz RFC 3548). Takto zakódovaný e-mail spambot rozezná těžko.
Dále nesmíme zapomenout upravit šablonu s výpisem poboček (templates/stores.html):
{% extends "base.html" %}
{% load misc %}
{% block content %}
<h1>{% block title %}Provozovny{% endblock %}</h1>
{% for object in object_list %}
<h2>{{ object.store }}</h2>
<ul class="store">
<li>{{ object.address }}</li>
<li>{{ object.city }}</li>
<li>{{ object.postal_code }}</li>
{% if object.email %}
<li>{{ object.email|email }}</li>
{% endif %}
</ul>
{% if object.description %}
<p>{{ object.description }}</p>
{% endif %}
{% endfor %}
{% endblock %}
V tomto souboru přibyla na druhém řádku značka pro načtení našeho modulu s filtrem misc.py. Na patnáctém řádku se e-mail nevypisuje přímo, ale přes filtr email což zatím nebude fungovat, protože nemáme definovanou javascriptovou funkci display_email. Přesto se ale můžeme podívat na to, co uvidí uživatelé s vypnutým JavaScriptem.
Založíme si tedy někde v adresáři static javascriptový soubor, který poté načteme přes značku <script> v šabloně templates/base.html. Tento soubor bude obsahovat již zmiňovanou funkci pro výpis e-mailu:
function display_email(str) {
email = b64decode(str);
document.write('<a href="mailto:' + encodeURI(email) + '">' + email + '</a>');
}
Na druhém řádku se volá funkce b64decode pro převedení zakódovaného řetězce do původní podoby. Ta v JavaScriptu není standardně implementována, takže jsem ji si musel napsat sám. Protože je však celkem rozsáhlá a pro další vysvětlování není potřeba, případní zájemci se na ni mohou podívat v přiloženém ukázkovém příkladu (soubor static/js/scripts.js). Co se týče čtvrtého řádku této funkce, ten zobrazí uživateli klikatelný e-mail.
Přidání proměnné do kontextu
Opět navážeme na poznámku ze šestého dílu seriálu, kde jsme se zmínili o kontextových procesorech. Díky nim můžeme v šablonách používat společné proměnné (což je např. {{ MEDIA_URL }} nebo {{ user }}), stačí vždy při volání metody render_to_response uvést parametr context_instance=RequestContext(request). Konstanta TEMPLATE_CONTEXT_PROCESSORS určuje, které kontextové procesory se v projektu použijí. V dokumentaci Djanga je popsáno několik základních procesorů, my si napíšeme vlastní.
Zadavatel našeho projektu dostal šílený nápad, že by se v hlavičce webu měla zobrazovat náhodně některá z předem připravených marketingových hlášek. Protože se tato funkce týká všech pohledů, při implementaci přímo v pohledech, by došlo k zbytečnému duplikování kódu. K tomuto účelu je lepší vybranou hlášku předat šabloně jako globální kontext. Náhodné vypisování by se to sice dalo vyřešit jednoduchým kódem v JavaScriptu, ale myslím, že skriptování v prohlížeči už dnes bylo dost.
Soubor s funkcí, která přidává další kontext, si můžeme založit kdekoliv. Například přímo v adresáři projektu. Pojmenujeme ho jako context_processors.py:
# coding: utf-8
from random import choice
def nonsense(request):
quotes = [
'Jsme nejlepší!',
'Naše tituly do vašeho přehrávače.',
'Dobrý den je, když si pustíte film.',
'Je přece správné platit za půjčování.',
'Tohle u konkurence nenajdete.',
]
return {'NONSENSE': choice(quotes)}
Funkce pro přidání kontextu požaduje jako parametr objekt Request a vrací slovník s deklaracemi proměnných. Bývá zvykem proměnné z kontextového procesoru zapisovat verzálkami, aby se odlišily od těch z pohledů. V naší funkci náhodně vybíráme jeden z blábolů, ke kterému poté budeme mít možnost přistupovat pomocí proměnné {{ NONSENSE }}.
Tento kontextový procesor je potřeba přidat do nastavení. Otevřeme si tedy soubor settings.py a dopíšeme do něj následující řádky:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'hrajeme_si.context_processors.nonsense',
)
První čtyři procesory jsou výchozí, ten poslední je náš. Teď už stačí jenom upravit v hlavní šabloně templates/base.html horní část webu:
<div id="header">
Videopůjčovny s. r. o.
{% if NONSENSE %}
<div id="nonsense">{{ NONSENSE }}</div>
{% endif %}
</div>
Související odkazy
- Vlastní filtry a značky a šablony pro programátory na Djangoproject.com
- Devátá kapitola v The Definitive Guide to Django
- Ukázkový projekt ke stažení.
Příště se podíváme na tvorbu vícejazyčných webů.


