Django: Zpracovávání formulářů

Pomocí formulářů uživatelé zadávají data do aplikace. Manuální zpracovávání formulářů ale bývá pro programátory hodně otravné. Django naštěstí umožňuje formuláře generovat a zpracovávat, což programátorům usnadňuje život. V článku se na zpracování formulářů v Djangu podíváme podrobněji.
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
Práce s formulářem by měla být pro uživatele co nejpříjemnější. Doporučuje se proto nepřehánět stylování prvků formuláře, aby to nevedlo ke zmatení. Ideální je ponechat výchozí styl systému. Také by se uživateli při špatně zadaném vstupu mělo zobrazit, kde přesně udělal chybu a jak ji má napravit. V takovém případě nesmí dojít k vymazání zadaných hodnot z formuláře.
Kontaktní formulář
Po několika letech vytváření webů jsem si všiml, že zadavatelé firemních webových stránek mají v oblibě kontaktní formuláře. Do takového formuláře návštěvník vyplní své osobní údaje a požadavek. Po zpracování formuláře se odešle jeho obsah e-mailem. Osobně jsem smysl této funkce moc nepochopil, ale na ukázku práce s formuláři se nám to hodí.
Specifikace formulářů v Djangu se v mnohém podobá databázovým modelům. Později si předvedeme, jak z modelu automaticky generovat formuláře, ale zatím nám bude stačit vytvořit jednoduchý formulář. Vytvoříme si nový soubor video_store/forms.py
a do něj napíšeme následující kód:
# coding: utf-8 from django import forms class ContactForm(forms.Form): name = forms.CharField(label='Jméno', max_length=100, initial='Anonym') email = forms.EmailField(label='E-mail', required=False) phone = forms.CharField(label='Telefon', max_length=20, required=False) reaction = forms.BooleanField(label='Chci dostat odpověď', required=False) text = forms.CharField(label='Zpráva', widget=forms.Textarea, help_text='Sem napište svou připomínku nebo dotaz.')
Můžeme si všimnout, že jsme odvodili náš formulář z třídy django.forms.Form
. Atributy této nové třídy se podobají atributům databázových modelů, jenom používají trochu jiné parametry. Místo parametru verbose_name
, který určuje popisek atributu, tu máme parametr label
. Parametr max_length
má stejný význam jako u modelů — je to nejvyšší povolený počet znaků, který uživatel může zadat. Také parametr initial
se podobá modelovému parametru default
, jedná se tedy o předvyplněnou (výchozí) hodnotu atributu. Parametr required
má opačný význam než parametr blank
, vyskytující se u modelů. Pokud se mu přiřadí hodnota False
, znamená to, že je atribut nepovinný, což u modelů představuju hodnote True
. Parametr help_text
je nápovědný text, který se uživateli zobrazí u políčka formuláře. A parametr widget
určuje použitý formulářový prvek. V tomto případě se místo výchozí značky <input type="text">
při generování použije značka <textarea>
. Oficiální dokumentace obsahuje přehled všech formulářových prvků a všech druhů formulářových políček.
Když jsme si určili formulář, můžeme si zkusit z něj vygenerovat příslušný HTML kód. Existuje několik metod, které to zvládnou (z důvodů zachování přehlednosti je výpis zkrácen):
>>> from hrajeme_si.video_store.forms import ContactForm
>>> form = ContactForm()
>>> form.as_p()
u'<p><label for="id_name">Jméno:</label> <input id="id_name" type="text" name="name" value="Anonym" maxlength="100" /></p>…'
>>> form.as_ul()
u'<li><label for="id_name">Jméno:</label> <input id="id_name" type="text" name="name" value="Anonym" maxlength="100" /></li>…'
>>> form.as_table()
u'<tr><th><label for="id_name">Jméno:</label></th><td><input id="id_name" type="text" name="name" value="Anonym" maxlength="100" /></td></tr>…'
Tyto tři metody umí vytvořit formulář pomocí odstavců, seznamu a tabulky. Stačilo by příslušnou metodu zavolat na vhodném místě v šabloně (to se dělá např. použitím značky {{ form.as_table }}
). Ale co když požadujeme větší kontrolu nad výstupním kódem? Můžeme si vytvořit vlastní šablonu, která bude pomocí cyklu for postupně procházet jednotlivá formulářová políčka a určovat výstupní formát. Takovou šablonu si napíšeme a uložíme do souboru templates/form.html
:
{% for f in form %} {% if f.errors %} <tr> <td colspan="2">{{ f.errors }}</td> </tr> {% endif %} <tr> <th{% if f.field.required %} class="required"{% endif %}> <label for="id_{{ f.name }}">{{ f.label }}</label> </th> <td>{{ f }}</td> </tr> {% if f.help_text %} <tr> <td></td> <td class="help_text">{{ f.help_text }}</td> </tr> {% endif %} {% endfor %}
Výstup je podobný metodě as_table
, ale máme možnost ho v případě potřeby jednoduše měnit. Na druhém až šestém řádku zobrazíme uživateli chybové hlášky, pokud vyplnil do formuláře něco špatně. Osmý až třináctý řádek generuje samotnou kolonku formuláře i s popiskem. Jestliže je popisek potřeba vyplnit, aplikuje se na něj kaskádová třída required
, což uživateli může pomoct s orientací ve formuláři. Patnáctý až dvacátý řádek se postará o výpis případného nápovědného textu.
Tato šablona je univerzální, takže ji můžeme použít i v našem kontaktním formuláři. Je potřeba vytvořit další šablonu (templates/contact.html
), která bude pomocí značky {% include %}
využívat formulářovou šablonu:
{% extends "base.html" %} {% block content %} <h1>{% block title %}Kontakt{% endblock %}</h1> {% if sent %} <p>Vaše zpráva byla odeslána. Děkujeme!</p> {% else %} <form action="." method="POST"> <table> {% include "form.html" %} <tr> <td></td> <td><input type="submit" value="Odeslat"></td> </tr> </table> </form> {% endif %} {% endblock %}
V této šabloně se podle hodnoty proměnné sent
uživateli zobrazí potvrzující hláška nebo formulář. Hodnota se totiž v konstrukci if vyhodnotí jako True
, pokud obsahuje nějaký neprázdný (nenulový) objekt. Může to být například neprázdný textový řetězec.
Kromě šablon potřebujeme i příslušný pohled, který formulář zpracuje a pokusí se poslat e-mail. Přidáme ho na konec souboru video_store/views.py
:
from forms import ContactForm from django.core.mail import mail_managers def contact(request, sent): if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): message = u'Uživatel vyplnil následující údaje:nn' for item in form.fields.keyOrder: if len(unicode(form.cleaned_data[item])) > 0: message += form[item].label + u': ' + unicode(form.cleaned_data[item]) + u'n' mail_managers('Nová zpráva z kontaktního formuláře', message) return HttpResponseRedirect('/kontakt/odeslano/') else: form = ContactForm() return render_to_response('contact.html', {'form': form, 'sent': sent}, context_instance=RequestContext(request))
Druhým argumentem pohledu je proměnná sent
. Ta se pouze předá šabloně formuláře a podle jejího obsahu se určí, co se má zobrazit. Formulář je zasílán metodou POST, takže se dá lehce poznat, zda už byl vyplněn. To rozhodujeme podle objektu request
(řádek 5). Pokud nebyl vyplněn, vypíšeme pouze prázdný formulář (řádek 19). V opačném případě vložíme do formuláře zadaná data (řádek 6) a zjistíme, jestli je vstup validní (řádek 8). Kupříkladu jestli e-mail vyhovuje určitému regulárnímu výrazu nebo jestli jsou vyplněny všechny povinné položky. Pokud je všechno v pořádku, sestavíme text e-mailu a rozešleme jej lidem, kteří jsou zodpovědní za správu stránky. Po úspěšném odeslání e-mailu dotaz přesměrujeme na stránku s hláškou, abychom předešli případům, kdy se při náhodném obnovení stránky formulář zpracovává znovu.
u
), jinak může dojít při zadání diakritického textu k vyhození výjimky UnicodeError
(selhání při dekódování znakové sady).Ještě než nastavíme URL, měli bychom určit e-mailové adresy, na které se bude obsah kontaktního formuláře rozesílat. Stačí upravit konstantu MANAGERS
v souboru settings.py
například takto:
MANAGERS = ( ('Jan Novák', 'honza@example.cz'), ('Franta Uživatel', 'franta@example.cz'), )
Pokud se e-mailový server nenachází na počítači, na kterém aplikaci provozujeme, musíme ve stejném souboru přenastavit konstanty začínající prefixem EMAIL, jinak nám rozesílání e-mailů nebude fungovat. Bez e-mailového serveru lze skript také testovat, jenom je potřeba funkci mail_managers volat s parametrem fail_silently=True
, aby se potlačila chybová hláška.
A konečně, teď jenom stačí přidat správný řádek do proměnné urlpatterns
v souboru urls.py
(z důvodů přehlednosti opět zkráceno):
urlpatterns = patterns('hrajeme_si', # ... (r'^kontakt(|/odeslano)/$', 'video_store.views.contact'), )
Regulární výraz (|/odeslano)
přiřadí proměnné sent
buď prázdnou hodnotu nebo řetězec '/odeslano'
, který stačí k vyvolání hlášky o odeslání.
Související odkazy
- Formuláře na Djangoproject.com
- Sedmá kapitola v The Definitive Guide to Django
- Ukázkový příklad ke stažení.
Příště se dozvíme něco o autentizaci a autorizaci uživatelů v Djangu.
Prosim vyhnete se pouzivanim bytestringu (str()) v djangu, zejmena v kombinaci s unicode stringy. Vede to k osklivym errorum ktere jsou tezko odhalitelne protoze se vyskytuji jen nekdy, vsude pouzivejte unicode.
Rovnez opetovne scitani stringu (byte i unicode) je vhodne se vyhnout, je to neumerne draha operace:
Nehlede na to, ze pokud mym zamerem je jen odeslat data v emailu, neni vhodne pozadovat po Djangu aby vstupy z formulare prevadelo na datove typy pythonu (Boolean) jen proto abych je pak prevedl zpet, ChoiceField by v tomto pripade vyhovoval mnohem vic a usetril by dost kodu (vsechny prevody na unicode v zobrazenem view).
Máte naprostou pravdu, všude by měla být použita metoda
unicode
, nestr
. Omlouvám se za to, nestihl jsem kód dostatečně otestovat. (Hlavně že tam o tom píšu, že je na tom místě potřeba použít unicode řetězce…)Kod smycky pro vypis fieldu jde pouzit o neco obecneji, misto
je IMHO lepsi pouzit ponekud obecnejsi
Pak muzeme vesele pridavat dalsi fieldy pouze do modelu a view pritom zustane netknute…
Dobrý trik, díky.
Opravil jsem příklad podle komentářů Honzy Krále a Tomaashe. Nezahrnul jsem sice všechny připomínky (názornost je podle mě důležitější než efektivita), ale kód by teď měl být bez defektů. Ještě jednou díky oběma pozorným čtenářům.