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