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


(django-project.org)

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


  • W django nie ma magii:
    "Explicit is better than implicit"
  • Bardzo jasno widać,
    że to kod webaplikacji

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

  • Zaraz po wprowadzeniu CBV
    wybuchła mała wojna o
    metodę as_view()
  • Historycznie jest to bardzo
    ciekawe, więcej tu i tu

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


Pytania? 

Dzieki za uwage!

Class Based Views

By Jacek Bzdak

Class Based Views

  • 1,700