Django

View

Views

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")
Všimněme si, že před názvem šablony je i název "aplikace". Aby se předešlo možnému konfiltu jmen šablon napříč různými aplikacemi (která aplikace nemá index.html, že?), je zvykem nedávat do adresáře templates šablony přímo, ale vložit ještě podadresář jehož název je shodný s názvem aplikace: 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() či filter().

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