Django class based views (I)
Class based views
Amb Django 1.3 s'introdueixen les "Class based views", una funcionalitat que ens permet modelar les nostres vistes com a classes i que a més intenta solucionar el no tenir que escriure sempre el mateix tipus de codi quan mostram una plana web o fem un manteniment lligat a un model de dades.
Les class based views ens permeten un nivell més alt de reutilització del nostre codi i a més ens permeten de mantenir-ne la cohesió. Fins ara o bé teníem que anar creant les funcions dins la mateixa vista, dins la mateixa funció o tirar de mòduls independents.
Amb les class based views pode anar creant funcions per a la nostra vista, de manera que després sigui més bo de fer reaprofitar-les extenent la classe.
En poques paraules, les class based views requereixen un temps d'adaptació i alguns diuen que aprendre un nou DSL (Domain Specific Language). Potser sí, però s'ho paga.
Abans de començar
Encara que podem utilitzar les nostres pròpies class based views, Django ens dóna ja fetes les més freqüents i un conjunt de classes que podem fer servir en cas que cap ens encaixi.
És en aquestes classes on hi ha gran part de la potència de les class based views. Per a fer-les servir hem d'entendre el concepte de Mixin, ja que això són aquestes classes: Python mixins.
Què és un mixin?
Un mixin no és res més que una classe que no està concebuda per tenir entitat per sí mateixa, sinó per extendra la funcionalitat d'altres classes fent servir l'herència múltiple de Python.
Un mixin, per tant, afegeix funcionalitat a les classes. Podem crear Mixins com si de mòduls Python es tractàs i aplicar-los a les nostres classes per dotar-les de més funcionalitat.
Hem d'entendre també com funciona l'herència múltiple en Python, de manera resumida: s'executarà el primer mètode que es trobi anant d'esquerra cap a dreta en la definició de les classes. És a dir, si tenim:
class Test(OneMixin, AnotherMixin):
pass
a l'hora d'executar un mètode, Python primer cercarà a OneMixin i si no ho troba anirà a AnotherMixin. Així doncs, al tanto que l'ordre en que posam les classes és important.
A diferència dels mòduls els mixins tenen accés a la instància self
de la clases, i per tant poden fer servir informació associada a l'objecte i altres mètodes.
La classe View
Aquesta classe és el bessó de tot el muntatge, i si cap de les funcionalitats que ens proporciona Django ens serveix gairebé segur que començarem a crear la nostra pròpia extenent aquesta classe.
Anem a fer un exemple molt senzill, suposem que volem mostrar una plana web, que anomenarem index.html
i que posaré dins la carpeta main
. Com que començam amb això de les generic class views, anam llegit i ens n'adonam que la classe View el que fa és capturar els noms dels mètodes http i executar-ne una funció associada amb el matix nom.
Ja està, ens deim, el que hem de fer es crear una classe que hereti de View i escriure'n un mètode que respongui a la creidada GET i que ens renderitzi la plana. Dit i fet, cream l'aplicació main
i la registram al settins.py
i anam per feina:
Al mòdul views.py
from django.shortcuts import render_to_response
from django.views.generic.base import View
from django.template import RequestContext
class TestView(View):
def get(self, request, *args, **kwargs):
return render_to_response('main/index.html', {}, context_instance=RequestContext(self.request))
i a urls.py
de l'aplicació main
from django.conf.urls.defaults import *
from views import TestView
urlpatterns = patterns('', url(r'^test2/$', TestView.as_view(), name="test-class-view"), )
Executam i efectivament això funciona. Ens mostra la plana. Però la cosa no ens acaba de convèncer, no?, ja que si fem una ullada a la manera anterior de fer les coses, això ben bé es podria fer com:
from django.shortcuts import render_to_response
from django.template import RequestContext
def test(request):
return render_to_response('main/index.html', {}, context_instance=RequestContext(request))
hem escrit fins i tot més, on està el guany, doncs? La resposta està en la reusabilitat.
Aquest patró de mostrar planes web senzilles es repeteix molt en les aplicacions web, de manera que la gent de Django ja ha creat una classe que hereta de Views i que fa precissament això i d'una manera més genèrica, la classe TemplateView
Aquesta classe hereta de TemplateResponseMixin i de View. TemplateResponseMixin és un mixin que té dos mètodes: render_to_response
i get_templates_names
i dos atributs: template_name
i response_class
. El que ens interessa ara és que TemplateResponseMixin
ens permet afegir a la nostra classe la renderització de la plantilla que passem dins template_name
o bé que tornem a partir de la sobreescriptura de get_templates_names
Si fem servir TemplateView el nostre codi queda com:
from django.views.generic.base import TemplateView
class IndexView(TemplateView):
template_name = 'main/index.html'
i a urls.py
from django.conf.urls.defaults import *
from views import IndexView
urlpatterns = patterns('', url(r'^$', IndexView.as_view(), name='main_index'), )
Fins i tot podríem prescindir del codi de view.py
i podríem fer:
from django.conf.urls.defaults import *
from django.views.generic.base import TemplateView
urlpatterns = patterns('', url(r'^$', TemplateView.as_view(template_name='main/index.html'), name='main_index'), )
Com podeu veure l'estalvi en línies de codi comença a ser notable.
L'important, a més de veure com es pot extendre la funcionalitat de Views
és que vegem què senzill és separar la funcionalitat lligada al GET de la funcionalitat lligada al POST, al PUT, o a qualsevol cridada HTTP. Sols hem de crear una funció amb aquest nom i Django la cridarà sempre que la nostra classe hereti de View.
Suposem doncs que el que volem ara és mostrar una plana diferent si algú ens intenta fer un POST contra la nostra plana. Django té un mecanisme per protegir-nos d'això és el CSRF o Cross Site Request Forgery protection, ara el que voldriem es desactivar aquest mecanisme per la nostra classe i mostrar una plana diferent d'avís.
Amb el mètode classis hauríem de fer un
if request.method=='POST':
#fes alguna cosa
else:
#fes una altra cosa
amb les Class Based Views la cosa queda molt més elegant:
class TestView(View):
def get(self, request, *args, **kwargs):
return render_to_response('main/index.html', {}, context_instance=RequestContext(self.request))
def post(self, request, *args, **kwargs):
return render_to_response('no_post_allowed.html')
Això tal com està no funcionaria, ja que tenim pel mig la protecció de Django.
Per saltar-nos aquesta protecció li hem de dir a la vista que no la volem i per fer-ho hem de sobreescriure el mètode dispatch
que se n'encarrega de la fontaneria de verificar quin mètode http ens ve i la funció que hem de cridar.
from django.views.generic.base import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
class TestView(View):
def get(self, request, *args, **kwargs):
return render_to_response('main/index.html', {}, context_instance=RequestContext(self.request))
def post(self, request, *args, **kwargs):
return render_to_response('no_post_allowed.html')
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(TestView, self).dispatch(*args, **kwargs)
Hem vist fins ara com fer servir la classe View i la classe TemplateView, hi tornarem en els propers apunts, estau atents.