View
Views
Obsah stránky (hide)
1. Přehled
Views jsou vrstvou Djanga, kde se odehrává logika aplikace. Stojí mezi daty (model) a vyobrazením webových stránek v šablonách (templates).
Typicky jsou uloženy v souboru views.py
v adresáři aplikace. Tento soubor (mimo další) vznikne při vytváření aplikace uvnitř projetku příkazem:
$ django-admin startapp my_app
Soubor views.py
obsahuje části naší aplikace ve formě pythonovských funkcí. Tyto funkce jsou volány z URL dispatcheru (soubor urls.py
). Ten do funkce vždy předává argument request
a případně i další argumenty. Viz kapitola o URL.
View funkce pak typicky vrací (return
) odkaz na šablonu, do které pošle data, nebo vrací přesměrování na jinou view funkci, případně může vyvolat výjimku.
Typcké view může vypadat nějak takto:
from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
To je ale spíš "školní" ukázka. V praxi nabízí Django spoustu "zkratek", které psaní kódu usnadňují. V této ukázce navíc vracíme html kód, který jsme spáchali přímo ve view. Ve skutečném životě zavoláme šablonu a data jí předáme. Šablona obsahuje html kód a data do něj vloží.
Další ukázka už je reálnější:
from django.http import Http404 from django.shortcuts import render_to_response def detail(request, poll_id): try: p = Poll.objects.get(pk=poll_id) except Poll.DoesNotExist: raise Http404 return render_to_response('polls/detail.html', {'poll': p})
View funkce detail obdrží http request a nějakou hodnotu z URL (zde nejspíše primární klíč k čemusi). Následně se pokusí načíst z databáze z modelu Poll řádek s primárním klíčem shodným s tím, co přišlo z URL. Pokud takový klíč neexistuje, vyhodí výjimku. Pokud existuje, vrátí šablonu detail.html a předá jí slovník s očekávanými daty - zde nalezený řádek z databáze předá jako proměnnou "poll". Zbytek už je na šabloně, co s tím udělá.
Je-li zavolána výjimka (zde Http404), je na nás, abychom vytvořili odpovídající šablonu. Ta se musí jmenovat 404.html a musí být v kořenovén adresáři šablon s názvem templates (app_name/templates/404.html
). Pokud šablona neexistuje, Django zobrazí svoji standardní.
Django umožňuje přizpůsobit si view pro obsluhu chyb 404, 500 a 403.
2. Zkratky pro generování odpovědi (response)
Django nabízí řadu funkcí pro generování výsledného kódu. Jejich použití ale bývá poněkud hardcore. Většinou se používá jen část z nich a ne všemi možnými způsoby. Pro obvyklé situace nabízí Django zjednodušené funkce, které jsou v modulu django.shortcuts
.
- render
- render_to_response
- redirect
- get_object_or_404
- get_list_or_404
První dvě slouží k volání šablony a předání dat do ní. Funkce redirect
umožňuje přesměrovat pokračování jinam (obvykle nějaké jiné view). Další dvě funkce zjednodušují obsluhu získávání dat z modelů při použití funkce get
, která při neúspěchu vyvolává výjimku.
2.1 render
render(request, template_name [, dictionary] [, context_instance] [, content_type] [, status] [, current_app])
- request
- objekt použitý k vytvoření této odpovědi (obvykle to, co dostává view funkce z URL dispatcheru).
- template_name
- jméno šablony, kterou chceme zobrazit
- dictionary
- slovník s daty, která se mají v šabloně zobrazit.
- context_instance
- content_type
- typ MIME. Default "text/html", jak je nastaveno v settings.py parametru DEFAULT_CONTENT_TYPE.
- status
- http stavový kód odpovědi. Default 200 (O.K.).
- current_app
- v které aplikaci se aktuální view nachází (viz kapitola o URL a jmenných prostorech).
Příklad:
from django.shortcuts import render def my_view(request): # View code here... return render(request, 'myapp/index.html', {"foo": "bar"}, content_type="application/xhtml+xml")
my_app/templates/my_app/sablona.html
.
2.2 render_to_response
Kratší verze předchozí funkce render
. Vyhoví ve většině případů jako základní nástroj pro zobrazení dat v šabloně.
render_to_response(template_name[, dictionary][, context_instance][, content_type])
Význam parametrů je stejný jako u render
.
Příklad (stejný jako výše):
from django.shortcuts import render_to_response def my_view(request): # View code here... return render_to_response('myapp/index.html', {"foo": "bar"}, mimetype="application/xhtml+xml")
Jen podotýkám, že dávat mimetype do každého render by bylo na zabití (to bychom raději využili výchozí hodnotu v settings.py). V těchto příkladech převzatých z originální dokumentace je to asi proto, aby bylo vidět, že ten parametr lze opravdu zapsat. Jinak nevím :-)
V "syrovém" Djangu bez využití zkratek by to vypadalo takto:
from django.http import HttpResponse from django.template import Context, loader def my_view(request): # View code here... t = loader.get_template('myapp/template.html') c = Context({'foo': 'bar'}) return HttpResponse(t.render(c), content_type="application/xhtml+xml")
(No, taky by se to dalo naučit...) :-p
2.3 redirect
redirect(to[, permanent=False], *args, **kwargs)
Vrací HttpResponseRedirect na odpovídající URL zadanou v parametru to
. Tím může být:
- model - pak se zavolá funkce get_absolute_url() definovaná v onom modelu.
- jméno view
- URL
Příklady:
from django.shortcuts import redirect def my_view(request): ... object = MyModel.objects.get(...) return redirect(object)
Aby se zjistilo URL, volá se funkce get_absolute_url() z MyModel.
V následujícícm příkladě přesměrujeme na jiné view a předáme mu dokonce nějakou hodnotu:
def my_view(request): ... return redirect('some-view-name', foo='bar')
Nebo předáme URL natvrdo:
def my_view(request): ... return redirect('/some/url/')
A dokonce s můžeme odpálkovat někam pryč:
def my_view(request): ... return redirect('http://example.com/')
2.4 get_object_or_404
Funguje jako normální model.objects.get()
s tím, že v případě neúspěchu místo výjimky DoesNotExitsts vyvolá výjimu Http404. Samozřejmě výjimka MultipleObjectsReturned je ve hře taky (vrátí-li se více než 1 záznam).
get_object_or_404(klass, *args, **kwargs)
- klass
- Model, Manager, nebo QuerySet
- **kwargs
- Vyhledávací parametry ve formátu akceptovaném v
get()
čifilter()
.
Příklad:
from django.shortcuts import get_object_or_404 def my_view(request): my_object = get_object_or_404(MyModel, pk=1)
Což je ekvivalent tohoto:
from django.http import Http404
def my_view(request): try: my_object = MyModel.objects.get(pk=1) except MyModel.DoesNotExist: raise Http404
2.5 get_list_or_404
Stejné jako get_object... s tím, že se použije funkce filter()
a vrácení více než jednoho záznamu je zcela v pořádku - vrací se seznam.
3. Dekorátory
Tohle téma je roztahané po mnoha částech originální dokumentace. Stručně řečeno, Django nabízí k view funcím řadu dekorátorů, které obvykle umožní nastavit podmínku, kdy se ona funkce provede (nebo neprovede). V dokumentaci ke view jsou uvedeny tyto dekorátory:
- require_http_methods
- require_GET
- require_POST
- require_safe
- condition + etag + last_modified
- gzip_page
- vary_on_cookie
- vary_on_headers
Další dekorátory najdeme třeba v kapitole o autentifikaci a autorizaci.
4. Class based views (CBV) - pohledy jako třídy
Je to relativně nová věc. Funkční veiw jsou jaksi tradičnější a jsou také bližší "skriptařům". U i CBV se jedná o očekávanou funcionalitu - předá se request a vytvoří se response, ale odehrává se to víc na pozadí. FBV jsou tedy dobré taky pro pochopení toho, co se ve view odehrává. Ale pak rychle šup k CBV. :-)
Výhody:
- Umožňují zvýšit znovupoužitelnost kódu díky dědičnosti, mixins apod.
- Django nabízí generické pohledy-view, které usnadní běžné rutinní činnosti.
Cesta filosofie Djanga se posouvá od funkčích view k class based (CBV).
Základní příklad využitelný přímo v urls.py
from django.conf.urls import patterns from django.views.generic import TemplateView urlpatterns = patterns('', (r'^about/', TemplateView.as_view(template_name="about.html")), )
Zadanými parametry (zde template_name
) přepisujeme atributy třídy (zde TemplateView
). Z jejího názvu je zřejmé, co má dělat - zobrazit šablonu.
Volání view je historicky voláním funkce, takže u class views je této funkčnosti dáno zadost tak, že třídy view mají metodu as_view()
.
Výše uvedený příklad je samozřejmě prasárna. Urls mají fungovat jen jako dispečer url -> view, ne aby se tu řešila funkčnost. Takže:
Správně se to dělá ve views.py
formou subclass generického view:
# some_app/views.py from django.views.generic import TemplateView class AboutView(TemplateView): template_name = "about.html"
A do urls.py vložíme:
from some_app.views import AboutView ... (r'^about/', AboutView.as_view()),
4.1 Dekorátory u CBV
Metoda třídy není totéž, co běžná funkce. Proto je tu trochu rozdíl, na který má Django řešení: Dekorátor method_decorator
. Dekoruje se metoda dispatch():
from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.views.generic import TemplateView class ProtectedView(TemplateView): template_name = 'secret.html' @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super(ProtectedView, self).dispatch(*args, **kwargs)
Druhá možnost je spáchat to přímo v URL dispatcheru:
(r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())),
Dle dokumentace je rozdíl i v tom, že první možnost dekoruje každou instanci, druhá možnost ne.
4.2 Zobrazovací Class Based Views (CBV)
Ta zkratka je dost zažitá. Je dobré si na ní zvyknout. Gnererické CBV vycházejí z nejobvyklejších situací, které při programování řešíme. Výše uvedené TemplateView
pouze zobrazí zadanou šablonu - tedy statickou stránku.
ListView
Předá data z modelu do šablony, kde jsou přístupná jako query s názvem "object_list".
# app/urls.py from .views import PersonList ... url(r'persons/$', PersonList.as_view()), # app/views.py from django.views.generic import ListView class PersonList(ListView): model = Person # app/templates/app/person_list.htm. {% for person in object_list %} {{ person.surname }} {% endfor %}
ListView umožní zobrazit data z modelu. V základním nasazení je to fakt na jeden řádek.
Název šablony je automaticky generován spojením jména modelu (Person) a generické třídy (List). Je možné zvolit i jiný název pro šablonu pomocí atributu template_name = 'jmeno_sablony.html'
. CBV samozřejmě nabízejí řadu dalších atributů a metod.
Query object_list
samozřejmě nabízí vše co běžné query. Má-li třeba Person klíč do modelu Tool (nářadí, které si půjčí), pak samozřejmě můžeme přistupovat k object_list.tool_set.all
. To nás posouvá výrazně dál než k pouhému výpisu obsahu modelu. Na druhou stranu je to spíš téma pro DetailView.
DetailView
Zpřístupní záznam z modelu vybraný dle primárního klíče poslaným skrz url. Do šablony záznam vstoupí coby "object
".
# app/urls.py ... url(r'^persons/(?P<pk>\d+)/$', PersonDetail.as_view()), # app/views.py form django.veiw.gereric import DetailView class PersonDetail(DetailView): model = Person # app/templates/app/person_detail.html <h1>{{ object.name }}</h1> vypíšeme detaily dané osoby,...
Oproti ListView je změna v urls.py
, kde navíc naplníme proměnnou pk
(primární klíč), podle kterého je daná položka v modelu nalezena (proměnná se musí jmenovat pk
! Obvykle je tato url volána z například seznamu osob (ListView), kde každá osoba je v html tagu <a href="person/{{ person.pk }}"
, nebo lépe <a href="{% url 'namespace:name' person.pk %}"
.
Hodnotu pk
ve view ani v šabloně řešit nemusíme. Od toho je to Detail view, aby se s tímto údajem počítalo jaksi automaticky :-)
Propojení List a Detail View
V šabloně object_list.html vypíšeme položky jako klikací odkaz s tagem <a>
:
# app/person_list.html {% for object in object_list %} <li> <a href=" {% url 'namespace:person_detail' object.pk %}> {{ object.get_full_name }} </a> </li> ...
V ulrs.py
url(r'^persons/(?P<pk>\d+)/$' PersonDetail.as_view(), name='person_detail'),
A po kliknutí na jméno v ListView se dostaneme na DeailView. Klasický příklad využití.
Užitečné atributy a metody u ListView a DetailView
context_object_name
Pokud se název object_list
nebo object
(u DetailView) v šabloně jeví poněkud sterilní, můžeme ho přejmenovat pomocí tohoto atributu.
queryset
Použijeme místo atributu model
, chceme-li vypisovat jen podmnožinu (filter), jiné řazení (order),... Lze využít i u DetailView (tam mi to ale nedává smysl - pracuji jen s jednou položkou).
class NecoList(ListView): queryset = Neco.objects.filter(sex__exact='f') context_object_name = 'women_list' template_name = 'women_list.html''
Defaultní šablona se jmenuje stále neco_list.html
- dle jména modelu. Proto se nám tu může hodit její přejmenování. Nicméně, neco_list.html
, které jsme nejspíš použili už pro celý výpis modelu by se dal využít i tady - vypsaly by se opravdu jen ženy. Problémem by bylo, že by šablona nevěděla, že dostala jen něco. Takže do šablony bude potřeba ještě předat nějakou informaci - například název výběru. No a k women_list.html
- to by fakt byla pakárna mít pro každou podmnožinu samostatnou šablonu. (prostě tu jen trochu přemýšlím nahlas:)
get_context_data
Metoda umožňující přidat položky do contextu (slovník, který se předává do šablony).
class NecoDetail(DetailView): model = Neco context_object_name = 'neco_detail' def get_context_data(self, **kwargs): context = super(NecoDetail, self).get_context_data(**kwargs) context['now'] = timezone.now() return context
Samozřejmě kouknout do oficiální dokumentace, kde je těch vychytávek o něco více. Je tu třeba dynamické filtrování, nebo jak ve view udělat nějakou práci navíc - třeba někam něco zapsat - takový jako trigger (přes metodu get_object). I když dopuštět se toho v DetailView mi přijde dost zatemňující styl. Spíš to je asi ukázka možností...
4.3 Formulářové CBV
Práce s formuláři zahrnuje 3 možnosti:
- GET - prázdný formulář k vyplnění (můžeme nastavit výchozí hodnoty)
- POST neplatný - formulář se zobrazí znovu s chybovými hlášeními.
- POST platý - data zapíšeme a provede se redirect na
success_url
.
Upozornění předem: Atribut action=
v html formluáři je ponechán prázdný. (co já se napřemýšlel, co by se tam mělo napsat:) Viz příklady šablon dále.
Příklad urls.py z dokumentace:
urlpatterns = patterns('', # ... url(r'author/add/$', AuthorCreate.as_view(), name='author_add'), url(r'author/(?P<pk>\d+)/$', AuthorUpdate.as_view(), name='author_update'), url(r'author/(?P<pk>\d+)/delete/$', AuthorDelete.as_view(), name='author_delete'), )
FormView
Slouží ke zobrazení a zpracování formuláře, který není svázán z daty (modelem). Potřebujeme třídu formuláře coby potomka form.Form
, a dále view coby dítko FormView a s parametrem from_class
odkazujícím na název formuláře. Pak zmíněnou success_url
a případně jméno šablony. A taky tu předefinujeme metodu form_valid
, aby náš view udělal potřebnou práci (třeba odeslala data z formuláře e-mailem).
CreateView
Zobrazí formulář pro přidání nových dat (tedy prázdný) a provede validaci. V případě úspěchu data zapíše do modelu, v případě neúspěchu znovu zobrazí formulář s daty a chybovými hláškami. Pro svoji práci si Django dynamicky vytvoří ModelForm z příslušného modelu - nemusíme tedy žádný Form vymýšlet.
Atributy:
- model
- jasné, ne? Povinné.
- fields
- Seznam polí z modelu chceme ve formuláři zobrazovat. Pokud všechna, můžeme tohle vynechat.
- success_url
- Url kam přesměrovat po zapsání dat.
- initial
- Úvodní data která se mají zobrazit v čerstvém formuláři - slovník.
- template_name_suffix
- Místo výchozího '_form' připojí k názvu modelu, co sem zadáme. Výchozí název šablony je tedy 'model_form.html'
- template_name
- Umožní zadat celé jméno šablony, ne jen suffix.
- object
- Přístup ke zřizovanému objektu samotnému. Neby-li ještě zřízen, má hodnotu None.
- ...a další
- viz dokumentace
Metody - v základu není nutné žádnou přetěžovat. Jsou tu get, post, form_valid, ...
Example myapp/views.py:
from django.views.generic.edit import CreateView from myapp.models import Author class AuthorCreate(CreateView): model = Author fields = ['name']
Example myapp/author_form.html:
<form action="" method="post">{% csrf_token %} {{ form.as_p }} <input type="submit" value="Create" /> </form>
UpdateView
Oproti CreateView přijdou do formuláře data z databázového záznamu, aby je uživatel mohl upravit. Primární klíč vybíraného záznamu přijde u urls.py (viz výše příklad). Vše ostatní je stejné jako u CreateView. Pochopitelně.
DeleteView
Při prvním zavolání (get) zobrazí potvrzovací dotaz a po odsouhlasení uživatelem (post) provede výmaz z databáze a přesměrování na success_url
(ta se dává jako reverse_lazy() a ne jen reverse(), protože urls nejsou ještě načtena, když je soubor importován - vysvětlení v dokumentaci). Výchozí název šablony je 'model_confirm_delete.html'. Příklad z dokumentace:
from django.views.generic.edit import DeleteView from django.core.urlresolvers import reverse_lazy from myapp.models import Author class AuthorDelete(DeleteView): model = Author success_url = reverse_lazy('author-list')
Example myapp/author_confirm_delete.html:
<form action="" method="post">{% csrf_token %} <p>Are you sure you want to delete "{{ object }}"?</p> <input type="submit" value="Confirm" /> </form>
4.4 Předání proměnné z url do CBV
# urls.py url(r'^novinky/iframe/(?P<skolka>\w+)/$', NovinkyIframeView.as_view(), name='novinky_iframe'),
a
# views.py class NovinkyIframeView(ListView): model = Novinky def get_queryset(self): queryset = Novinky.objects.filter(skolka__user__username=self.kwargs['skolka']) return queryset