Class Based Views
Jacek Bzdak
O mnie
- Jacek Bzdak
- Doktorant na
Wydziale Fizyki PW - Członek Wydziału Nowych
Technologii Głównej Kwatery
ZHP - Programuje w Pythonie,
C++, czasem Javie
Django
- Framework do tworzenia aplikacji WWW
- Na licencji BSD
- Zawiera wszystko co potrzebne
do stworzenia aplikacji
- Definitywnie nie jest mikroframeworkiem ;)
Filozofia Django
The Web framework for perfectionists
with deadlines
- django-project.org
- Don’t repeat yourself (DRY)
- Less code is better
- Explicit is better than implicit
- Loose coupling
- Quick development
Aplikacja WWW
- Co to jest aplikacja WWW?
- Coś co przyjmuje zapytania HTTP,
oraz generuje odpowiedzi HTTP.
Function based views
def show_all(request): return render_to_response(
"medic/kartoteka_list.html",
context_instance = RequestContext(request,
dict = {'historie_items' :models.MedicRecord.objects.all()}))
Urls.py
from django.conf.urls.defaults import *
from django.conf import settings
from views import *
urlpatterns = patterns('',
url(r'^index$', show_all , name = 'medic_index')
...
)
Uwagi
Zalety
- Tak proste, że nie da się prościej
- Zero magii!
- Funkcje nie mają stanu
Wady
- Kod szybko robi się trudny do ogarnięcia
- Nie da się (łatwo) dzielić kodu
- Spagetti code
- Słaba reużywalność
Generic Views
Pattern I A
def pacjent(request, patientid): patient = get_object_or_404(
Patient, pk = patientid) ctx_dict = {'pacjent' : patient} template = 'medic/pacjent.html' if request.method == 'GET': form = forms.KartotekaForm(
initial={'scout_id' : patient.id})
Pattern 1 B
(...)
else: form = forms.KartotekaForm(
data = request.POST) if form.is_valid(): record = form.save() messages.success(request, u'Zapisano!')
Pattern 1 C
return render_to_response(template, context_instance = RequestContext(request, dict = ctx_dict))
Uwagi
- Widać tutaj mnóstwo rzeczy,
które warto byłoby poprawić
(to była moja pierwsza webapka!) - Pisanie ładnego kodu
Django jednak wymaga wiedzy - A może by jakoś opakować te
"dobre praktyki"?
- Generyczne widoki!
Pattern II
def pacjent_add(request):
if request.method == 'GET':
form = forms.PacjentForm(initial={... })
else:
form = forms.PacjentForm(request.POST)
if form.is_valid():
patient = form.save()
return redirect('pacjent_wizyty',
kwargs={'patientid' : patient.id}))
return render_to_response('...',
context_instance =
RequestContext(request, {'form' : form}))
Pattern
if request.method == "GET":
do_something()
form = SomeForm()
if request.method == "POST":
form = SomeForm(request.POST)
if form.is_valid():
form.save()
do_something_else()
return redirect("somewhere")
return render_to_response("some_template", ctx=...)
Maybe DRY?
- Django lubi DRY
- Taki wzorzec fajnie
byłoby wyeksportować... - Zaznaczmy rzeczy które
można nadpisać
Pattern
@login_required
def my_view():
if request.method == "GET":
do_something()
form = SomeForm(initial=...)
if request.method == "POST":
form = SomeForm(request.POST, ...)
if form.is_valid():
form.save()
do_something_else()
return redirect ("somewhere")
return render_to_response("some_template", ctx=...)
Rozszerzalnosc
- Mam aplikację Open-Source,
implementuje ona jakiś proces
- Osoba korzystająca z niej
musi wprowadzić drobne zmiany
związane z danym procesem - Trudno jest określić z góry
co będzie chciała zmienić - Może to zrobić łatwo, oraz
w sposub future-proof - Np. django-registration
Extension points
- Punktów do których można
dokleić nowe zachowania
jest całkiem sporo... - Mimo to django miało
generyczne widoki - Pierwsza wersja generycznych
widoków nie była bardzo
re-używalna... (i nie jest dostępna
w repo)
Extensionability
def pacjent_add(request): if request.method == 'GET': form = forms.PacjentForm() else: form = forms.PacjentForm(request.POST) if form.is_valid(): patient = form.save() return HttpResponseRedirect(...)
return render_to_response(..., ctx=...)
Class Based Views
Klasa widoku
class ShowAll(View):
http_method_names = ['get']
def get(self):
return render_to_response(
"medic/kartoteka_list.html",
context_instance = RequestContext(
self.request,
dict = {'historie_items' : ...}))
Zmiany
- Działa metoda http OPTIONS
- Instancja widoku powstaje
per-request!
- Wszystkie parametry zapytania
dostępne przez self - self.kwargs
- self.request
Urls.py
from django.conf.urls.defaults import * from django.conf import settings from views import * urlpatterns = patterns('', url(r'^index$', ShowAll.as_view(), name = 'medic_index') ... )
as_view
def as_view(cls, **initkwargs):
(...)
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(
request, *args, **kwargs)
(...)
return view
Wojna o as_view
Problem
- Ciągle przykład nie jest rozszerzalny.
- Nie da się np. nadpisać szablonu bez
bez przepisywania całej funkcji.
Class Based Generic Views
Dla przypomnienia
class ShowAll(View):
http_method_names = ['get']
def get(self):
return render_to_response(
"medic/kartoteka_list.html",
context_instance = RequestContext(
self.request,
dict = {'historie_items' : ...}))
Przyklad
class ShowAll(TemplateView): http_method_names = ['get'] template_name = "medic/kartoteka_list.html"
def get_context_data(self, **kwargs): args = super().get_context_data(**kwargs) args['historie_items'] = ...
return args
Rozszerzalnosc:
- Można zmienić szablon
- Można zmenić typ klasy
Context - Łatwo można dodać
nowe context itemy
Problem
class TemplateView(TemplateResponseMixin,
ContextMixin, View):
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
What we had

http://commons.wikimedia.org/wiki/File:Spaghetti.jpg
What we got

By fugzu from Pistoia, Italia (ravioli di lattuga) [CC-BY-2.0 (http://creativecommons.org/licenses/by/2.0)], via Wikimedia Commons
Przewodnik
MRO
- Method Resulution Order
- Określa jak wyszukiwane
będą metody do wywołania
przy wielokrotnym
dziedziczeniu - Problem jest ciekawy
- Ludzie publikują
artykuły na ten temat
MRO
class A(object):
def __init__(self):
super(A, self).__init__()
def foo(self): pass
class C(A):
def __init__(self):
super(C, self).__init__()
print "MRO:", [x.__name__ for x in E.__mro__]
['C', 'A', 'object']
c = C()
c.foo()
c.__getattr__("a")
super
- Pozwala na wywołanie metody
z nadklasy zachowując takie
ograniczenia: - Metoda z każdej klasy jest
wywoływana raz - Metoda z każdej klasy będzie
wywołana - Super jest super/niebezpieczne
super
class A(object):
def __init__(self):
print "A"
super(A, self).__init__()
print "A post"
class B(A):
def __init__(self):
print "B"
super(B, self).__init__()
print "B post"
class C(A):
def __init__(self):
print "C"
super(C, self).__init__()
print "C post"
class D(B, C):
def __init__(self):
print "D"
super(D, self).__init__()
print "D post"
super
>>> d = D()
D B C A A post C post B post D post
Example
>>> [print(c.__name__) for c in CreateView.mro()]
CreateView
SingleObjectTemplateResponseMixin
TemplateResponseMixin
BaseCreateView
ModelFormMixin
FormMixin
SingleObjectMixin
ContextMixin
ProcessFormView
View
object
Examples
EditView -- before
@login_required
def edit_dynamic_form(request, dynamic_form_id):
dynamic_form = get_object_or_404(
DynamicForm, pk=dynamic_form_id)
return render_to_response(
"dynamic_forms/dynamic_form_edit.html",
{"dynamic_form": dynamic_form},
context_instance=RequestContext(request))
Edit View
class EditDynamicForm(
LoginRequiredMixin, DetailView):
http_method_names = ['get']
pk_url_kwarg = 'dynamic_form_id'
model = DynamicForm
template_name = "dynamic_forms/dynamic_form_edit.html"
context_object_name="dynamic_form"
CreateView
@login_required
def add_dynamic_form_field(request,
dynamic_form_id):
dynamic_form = get_object_or_404(
DynamicForm, pk=dynamic_form_id)
if request.method == 'POST':
form = AddDynamicFormField(request.POST)
if form.is_valid():
field = form.save(commit=False)
field.form = dynamic_form
field.save()
return reverse(...)
...
CreateView
else: form = AddDynamicFormField() dynamic_form_form =
BaseDynamicForm(dynamic_form) return render_to_response("", {"form": form, "dynamic_form": dynamic_form, "dynamic_form_form": dynamic_form_form}, context_instance=RequestContext(request))
ClassBasedView
class ProcessFormView(View):
def get(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(
self.get_context_data(form=form))
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Create View
class AddDynamicFormFieldView(
LoginRequiredMixin, CreateView):
template_name = "..."
form_class = AddDynamicFormField
def dispatch(self, request, *args, **kwargs):
self.dynamic_form = get_object_or_404(...)
return super().dispatch(...)
Create View
def get_context_data(self, **kwargs):
kwargs.update({
'dynamic_form': self.dynamic_form,
'dynamic_form_form':
BaseDynamicForm(self.dynamic_form)
})
return kwargs
CreateView
def get_success_url(self):
return reverse('...',
kwargs= {
"dynamic_form_id":
self.dynamic_form.id})
CreateView
def form_valid(self, form):
field = form.save(commit=False)
try:
self.dynamic_form.add_field_to_form(field)
field.save()
except FieldNameNotUnique:
return self.form_invalid(form)
return super().form_valid(form)
ListView
@login_required
def participants_list(request, dynamic_form_id):
dynamic_form = get_object_or_404(DynamicForm,
pk=dynamic_form_id)
participants =DynamicFormData.objects.all()
.filter(form=dynamic_form)
return render_to_response(
"dynamic_forms/list.html",
{"participants": participants},
context_instance=RequestContext(request))
ListView
class ParticipantList(
LoginRequiredMixin, ListView):
http_method_names = ['get']
template_name = "dynamic_forms/list.html"
context_object_name = "participants"
def dispatch(self, request, *args, **kwargs):
self.dynamic_form = get_object_or_404(...)
return super().dispatch(request,
*args, **kwargs)
def get_queryset(self):
return DynamicFormData.objects.filter(
form=self.dynamic_form)
Podsumowanie
- Kod nie jest krótszy!
- Jest bardziej
rozszerzalny - Może być łatwiejszy
do zrozumienia - Chyba że ktoś
nie zna CBV
Projekty
- Drigan https://bitbucket.org/zeroos/drigan
Generator rejestracji na imprezy
(razem z płatnościami) -
SILF : http://dokumentacja.fizyka.pw.edu.pl:888/silf/
Zdalne laboratorium
Pytania?
Dzieki za uwage!
Class Based Views
By Jacek Bzdak
Class Based Views
- 1,700