Saltar al contenido
🐍 Curso de Python
/

Módulo 4 · Lección 22 de 45

Vistas basadas en clases

La otra forma de escribir vistas: un método por método HTTP, herencia en vez de copiar, y el precio de no ver el código que se ejecuta.

⏱ 15 min de lectura

Todas las vistas que llevas son funciones, y funcionan. Pero en la lección 16 apareció una estructura que se va a repetir en cada vista que modifique datos:

python
def nuevo(peticion):
        if peticion.method == 'POST':
                ...
    return render(...)

Ese if es siempre el mismo, y lo que hay a cada lado es casi siempre lo mismo también. Cuando una estructura se repite idéntica en veinte sitios, la respuesta del módulo 1 fue sacarla a una clase base y heredar. Eso es exactamente lo que hace la otra mitad de Django.

Una vista que es una clase#

contactos/views_clase.py
from django.http import HttpResponse
from django.views import View


class Saluda(View):
        def get(self, peticion):
                return HttpResponse('GET desde una clase')

    def post(self, peticion):
                return HttpResponse('POST desde una clase')
contactos/urls.py
path('saluda/', views_clase.Saluda.as_view(), name='saluda'),

El if peticion.method ha desaparecido. Cada método HTTP es un método de la clase, con su nombre en minúsculas, y Django llama al que toca.

Lo que hay en el fichero de rutas no es la clase, es Saluda.as_view(). Ese método devuelve una función, porque Django solo sabe llamar funciones. Esa función, cuando llega una petición, crea una instancia nueva de la clase y le pasa el control.

Eso último tiene una consecuencia práctica que evita un error desagradable: la instancia dura lo que dura la petición y no se comparte con nadie. Guardar algo en self es seguro. Guardarlo en un atributo de clase, no, porque eso sí se comparte entre peticiones.

Lo que sale gratis#

Salida en pantalla
GET      -> 200
POST     -> 200
PUT      -> 405  Allow: GET, POST, HEAD, OPTIONS
DELETE   -> 405  Allow: GET, POST, HEAD, OPTIONS
HEAD     -> 200
OPTIONS  -> 200  Allow: GET, POST, HEAD, OPTIONS

Esa tabla es buena parte del argumento a favor de las clases, y sale sin escribir nada.

PUT y DELETE dan 405 con su cabecera Allow, que es exactamente lo que pedía la lección 2, y la clase sabe qué poner ahí porque sabe qué métodos tiene definidos. Con una función, el 405 hay que ponerlo a mano y la cabecera casi nadie la pone.

OPTIONS se contesta solo, que es lo que preguntan los navegadores antes de una petición desde otro dominio.

Y HEAD funciona porque lo atiende el get: mismas cabeceras, sin cuerpo, tal como manda la especificación.

TemplateView#

Lo más habitual es una página que solo pinta una plantilla. Para eso hay una clase hecha:

contactos/views_clase.py
from django.views.generic import TemplateView


class Portada(TemplateView):
        template_name = 'contactos/portada.html'

    def get_context_data(self, **kwargs):
                contexto = super().get_context_data(**kwargs)
        contexto['total'] = Contacto.objects.count()
        return contexto
Salida en pantalla
total=5

No hay get, no hay render, no hay return. Solo dos cosas: qué plantilla y qué va en el contexto.

El super().get_context_data(**kwargs) de la primera línea no es opcional. Lo que devuelve trae cosas que otras piezas esperan encontrar, entre ellas view, que es la propia vista:

Salida en pantalla
view=<contactos.views_clase.Portada object at 0x10bb16510>

Saltarse esa llamada y devolver un diccionario propio funciona hasta que algo de más arriba deja de encontrar lo suyo, y entonces el fallo es raro y lejano. Se llama al padre, se añade lo tuyo, se devuelve. Ese patrón de tres líneas se repite en todo el bloque.

Que view esté en el contexto tiene un efecto secundario cómodo: desde la plantilla se puede llamar a cualquier método de la vista con {{ view.lo_que_sea }}.

La jerarquía#

Aquí está lo que hay que entender de verdad, porque es donde las vistas de clase se vuelven opacas. Esto es lo que hay detrás de ListView:

Salida en pantalla
ListView
    MultipleObjectTemplateResponseMixin
    TemplateResponseMixin
    BaseListView
    MultipleObjectMixin
    ContextMixin
    View
    object

Siete clases. Ese es el precio: el código que se ejecuta no está donde escribes, está repartido en una cadena de clases que no has abierto nunca.

El reparto, que una vez visto se reconoce en todas:

Trozo del nombreDe qué se ocupa
ViewRecibir la petición y llamar al método del verbo
...MixinUna pieza suelta: obtener los objetos, el contexto, el formulario
Base...La lógica sin decidir cómo se responde
TemplateResponseMixinResponder con una plantilla
El nombre completoJuntarlo todo

Los nombres se leen: MultipleObjectMixin sabe conseguir una lista de objetos; TemplateResponseMixin sabe pintar una plantilla; ListView es las dos cosas a la vez. Por eso existen las parejas BaseListView y ListView: la primera sirve para responder con JSON, por ejemplo.

Y una que importa por lo que permite:

Salida en pantalla
http_method_names: ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

Recortar esa lista en una clase limita los métodos que acepta, que es la versión de clase del require_POST de la lección 16.

Función o clase#

Las dos formas conviven en el mismo proyecto sin problema, así que no hay que elegir de una vez.

Una función se lee entera. Todo lo que pasa está delante. Para algo que no se parece a nada, para una vista con lógica rara, para un endpoint suelto, la función gana y no hay discusión.

Una clase se configura. Cuando la vista es una de las cuatro o cinco cosas de siempre —listar, ver, crear, editar, borrar—, la clase son tres líneas y la función son veinte, y esas veinte están copiadas de otra vista.

El criterio que funciona:

SituaciónQué elegir
Listar, ver, crear, editar o borrar, sin nada raroClase genérica
Lo mismo con un par de retoquesClase, sobrescribiendo un método
Lógica que no se parece a un CRUDFunción
Estás aprendiendo qué hace DjangoFunción
Sobrescribes cuatro métodos y ya no sabes qué correFunción, otra vez

Esa última fila es importante y a mucha gente le cuesta llegar a ella. Una vista genérica con seis métodos sobrescritos es peor que la función equivalente, porque tiene la longitud de la función y encima hay que saberse la jerarquía para leerla. Cuando llegues ahí, reescribirla como función no es retroceder.