26 de Febrero de 2012 · 5 min de lectura
Con Django 1.3 se introducen las "Class based views", una funcionalidad que nos permite modelar nuestras vistas como clases y que además intenta solucionar el no tener que escribir siempre el mismo tipo de código cuando mostramos una página web o hacemos o hacemos un mantenimiento ligado a un modelo de datos.
Las class based views nos permiten un nivel más alto de reutilización de nuestro código y permitiéndonos manter la cohesión de nuestras clasas. Hasta ahora o bien teníamos que ir creando las funciones dentro de la misma vista, dentro de la misma función o ir creando módulos independientes. Con las class based views podemos ir creando funciones para nuestra vista, de forma que después sea más fácil reaprovecharlas, extiendiendo o sobreescribiendo la clase.
En pocas palabras, las class based views requieren un tiempo de adaptación, algunos dicen que aprender un nuevo DSL (Domain Specific Language). Quizás sí, pero os aseguro que el resultado merece el esfuerzo.
A lo largo de siete apuntes iremos desgranando los entresijos de las class based views de Django, si alguno no puede esperar, os remito al artículo original que publiqué en su día en mi blog personal.
Aunque podemos utilizar nuestras propias class based views, Django nos da ya hechas las más frecuentes y un conjunto de clases que podemos usar en caso de que ninguna nos encaje. Es en estas clases donde hay gran parte de la potencia de las class based views. Para usarlas tenemos que entender el concepto de Mixin, puesto que eso son estas clases: Python mixins.
Un mixin no es nada más que una clase que no está concebida para tener entidad por sí misma, sino por extender la funcionalidad otras clases usando la herencia múltiple de Python. Un mixin, por lo tanto, añade funcionalidad a las clases. Podemos crear Mixins cómo si de módulos Python se tratara y aplicarlos a nuestras clases para dotarlas de más funcionalidad.
Tenemos que entender también como funciona la herencia múltiple en Python, de manera resumida: se ejecutará el primer método que se encuentre yendo de izquierda a derecha en la definición de las clases. Es decir, si tenemos:
class Test(OneMixin, AnotherMixin): pass
a la hora de ejecutar un método, Python primero buscará a OneMixin y si no lo encuentra irá a AnotherMixin. Así pues, al tanto que la orden en que ponemos las clases es importante. A diferencia de los módulos los mixins tienen acceso a la instancia self
de la clases, y por lo tanto pueden usar información asociada al objeto y otros métodos.
Esta clase es el núcleo de todo el montaje, y si ninguno de las funcionalidades que nos proporciona Django nos sirve, casi seguro que empezaremos a crear nuestra propia classe extendiendo de ésta.
Vamos a hacer un ejemplo muy sencillo, supongamos que queremos mostrar una página web, que denominaremos index.html
y que pondré dentro de la carpeta main
. Dado que empezamos con esto de las generic class views, y vamos leyendo, nos damos cuenta que la clase View lo que hace es capturar los nombres de los métodos http y ejecutar una función asociada con el mismo nombre.
Ya lo tenemos, nos decimos, lo que tenemos que hacer se crear una clase que herede de View y escribir un método que responda a la llamada GET y que nos renderice la página.. Dicho y hecho, creamos la aplicación main
y la registramos en settins.py.
En el módulo 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) )
y en urls.py de la aplicación
from django.conf.urls.defaults import * from views import TestView urlpatterns = patterns('', url(r'^test2/$', TestView.as_view(), name="test-class-view"), )
Ejecutamos y efectivamente esto funciona. Nos muestra la página. Pero la cosa no nos acaba de convencer, ¿no?, si damos un vistazo la manera anterior de hacer las cosas, esto muy bien se podría haber escrito como:
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) )
Hemos escrito incluso más que antes. ¿Dónde está la ganancia? os preguntaréis. La respuesta inmediate está en la reusabilidad.
El patrón de mostrar una página web sencilla se repide en la mayoría de las aplicaciones web, de fomra que los desarrolladores de Django ya han creado una clase que hereda de Views y que hace precisamente esto y de una manera más genérica, la clase TemplateView
.
Esta clase hereda de TemplateResponseMixin y de View. TemplateResponseMixin es un mixin que define dos métodos: render_to_response
y get_templates_names
y dos atributos: template_name
i response_class.
Lo que nos interesa en estos momentos es que TemplateResponseMixin
nos permite añadir a nuestra clase la renderización de la plantilla que le pasemos dentro de template_name
o bien que devolvamos a partir de la sobreescritura del método get_templates_names
Si utilizamos la clase TemplateView nuestro código queda
from django.views.generic.base import TemplateView class IndexView(TemplateView): template_name = 'main/index.html'
y en urls.py
from django.conf.urls.defaults import * from views import IndexView urlpatterns = patterns('', url(r'^$', IndexView.as_view(), name='main_index'), )
Incluso podríamos prescindir del codigo de view.py
y escribir:
from django.conf.urls.defaults import * from django.views.generic.base import TemplateView urlpatterns = patterns('', url(r'^$', IndexView.as_view(template_name='main/index.html'), name='main_index'), )
Si volvemos a comparar podemos ir viendo que además de la reusabilidad, ya tenemos una diferencia de código notable. Es importante darse cuenta de cómo podemos extender la funcionadidad de Views y ver que es muy sencillo separar la funcionalidad ligada a una petición GET de la que se corresponde a una llamada POST o PUT, o a cualquier otra llamada HTTP. Tan solo hemos de crear una función con el nombre que se corresponda a la llamada y Django la ejecutará siempre y cuando nuestra clase herede de View.
Supongamos entonces que lo que queremos es mostrar una página web diferente si alguien intenta hace un POST contra una url concreta. Django tiene un mecanismos llamado CSRF o Cross Site Request Forgery protection para protegernos de POSTs indesados, pero ahora lo que queremos es hacer algo diferente, más instructivo, y mostrar una página de aviso. Si utilizásemos el método clásico haríamso:
if request.method=='POST': #ha esto else: #haz esto otro
pero con las Class Based Views nos queda mucha más elegante:
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')
Como que nos queremos saltar la protección CSRF hemos de decirle a la vista que no la queremos y sobreescribir el método dispatch
que se encarga de la parte de fontaneria necesaria para verificar de qué método HTTP proviena la llamada y qué función debe ejecutar
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)
Además esto nos sirve de ejemplo para ver cómo podemos aplicar un decorador a una Class Based View.
Hasta el momento hemos visto como utilizar View y TemplateView, volveremos pronto con un nuevo apunte que hará más incapié sobre esta última clase y cómo podemos utilizarla.