Django class based views (I)
Escrit per Aaloy a 19 de February , 2012 a les 8:26 p.m.
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.
Enllaços citats
Traducciones/Translations by apertium
3 comentaris, 0 trackbacks (URL) , Tags: Django
Comentaris
1 Comentari de paurullan a les 10:02 del Sunday 19 Feb de 2012
Així que la idea general és tractar les vistes com a classes i explotar la reusabilitat mitjançant herència. (Irònicament els mixins en Ruby me fan por doncs en realitat es comporten com un «from M import *»)
Pregunta des de la falta d'experiència: i en el cas de vosaltres, on teniu varis projectes que poden tenir certa semblança, no significa això que al final acabau fent classes i generau planes web des d'allà?
És que me ve al cap com si fos una avaluació parcial «esquema_bbdd x vistes → plana» però si no sé fins a quin punt estic estirant massa la semblança.
2 Comentari de aaloy a les 11:02 del Sunday 19 Feb de 2012
Els mixin són classes sense entitat pròpia, si fas un import * és que no ho estàs fent bé.
Tenir una bona col·leció de mixins per a la teva aplicació t'ajuda a extendre la funcionalitat més enllà de la lògica de negoci.
La reusabilitat és més senzilla amb les classes que amb les funcions. Ara pots crear fins i tot una aplicació amb una classe base o un conjunt de mixins que encapsuli el que fas servir normalment i anar personalitzant des d'allà.
Però cada web normalment és única, i per tant el que no es reaprofita normalment és tot el relacionat amb la capa de presentació.
I encara que no reaprofitis res, poder tenir herència de classes és interessant per si vols afegir funcionalitat nova, amb un mixin o hertant d'una classe base posar-la pot ser cosa de minuts enlloc de d'hores o dies.
3 Comentari de paurullan a les 11:02 del Monday 20 Feb de 2012
Sobre els mixins no m'he explicat bé: _en_Ruby_ els mixins es comporten com un «import *»; he entès la diferència i l'ús en Python.
M'agrada idò la idea de poder reutilitzar dins la mateixa aplicació les vistes per ampliar ràpidament la funcionalitat. Tinc ganes de provar-les de bon de veres!
