Introducción a las vistas basadas en clases

Las vistas basadas en clases proporcionan una forma alternativa de implementar vistas como objetos Python en lugar de funciones. No reemplazan a las vistas basadas en funciones, pero tienen ciertas diferencias y ventajas cuando se comparan con las vistas basadas en funciones:

  • La organización del código relacionado con métodos HTTP específicos (GET, POST, etc.) se puede abordar mediante métodos separados en lugar de ramas condicionales.

  • Las técnicas orientadas a objetos como mixins (herencia múltiple) pueden utilizarse para factorizar el código en componentes reutilizables.

La relación y la historia de las vistas genéricas, las vistas basadas en clases y las vistas genéricas basadas en clases

Al principio solo había el contrato de función de vista. Django pasaba tu función un HttpRequest y esperaba a cambio un HttpResponse. Esto era todo lo que proporcionaba Django.

Pronto se reconoció que había idiosincrasias y patrones comunes en el desarrollo de vistas. Se introdujeron las vistas genéricas basadas en funciones para abstraer estos patrones y facilitar el desarrollo de vistas para los casos comunes.

El problema con las vistas genéricas basadas en funciones es que, si bien cubrían bien los casos simples, no había forma de extender o personalizarlos más allá de algunas opciones de configuración, lo que limitaba su utilidad en muchas aplicaciones del mundo real.

Se crearon las vistas genéricas basadas en clases con el mismo objetivo que las vistas genéricas basadas en funciones, para facilitar el desarrollo de vistas. Sin embargo, la forma en que se implementa, a través del uso de mixins, proporciona una herramienta que resulta en vistas genéricas basadas en clases más extensibles y flexibles que sus contrapartes basadas en funciones.

Si has intentado vistas genéricas basadas en funciones en el pasado y las encontraste insuficientes, no debes considerar a las vistas genéricas basadas en clases como una equivalente de clase, sino más bien como un enfoque fresco para resolver los problemas originales que las vistas genéricas estaban destinadas a solucionar.

La traducción de los textos es la siguiente:

Usando vistas basadas en clases

En su núcleo, una vista basada en clase te permite responder a diferentes métodos de solicitud HTTP con diferentes métodos de instancia de la clase, en lugar de con código condicional dentro de una sola función de vista.

Entonces, donde el código para manejar HTTP GET en una función de vista se vería algo así:

from django.http import HttpResponse


def my_view(request):
    if request.method == "GET":
        # <view logic>
        return HttpResponse("result")

En una vista basada en clases, esto se convertiría en:

from django.http import HttpResponse
from django.views import View


class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse("result")

Dado que la resolución de URL de Django espera enviar la solicitud y los argumentos asociados a una función callable, no a una clase, las vistas basadas en clases tienen un método de clase as_view() que devuelve una función que se puede llamar cuando llega una solicitud para una URL que coincide con el patrón asociado. La función crea una instancia de la clase, llama a setup() para inicializar sus atributos y luego llama a su método dispatch(). dispatch mira la solicitud para determinar si es un GET, POST, etc., y reenvía la solicitud al método correspondiente si se define, o lanza HttpResponseNotAllowed si no:

# urls.py
from django.urls import path
from myapp.views import MyView

urlpatterns = [
    path("about/", MyView.as_view()),
]

Es importante destacar que lo que devuelve tu método es idéntico a lo que devuelves desde una función de vista basada en funciones, a saber, alguna forma de HttpResponse. Esto significa que objetos atajos HTTP o TemplateResponse son válidos para usar dentro de una vista basada en clases.

Si bien una vista basada en clases minimalista no requiere ningún atributo de clase para realizar su trabajo, los atributos de clase son útiles en muchos diseños basados en clases, y hay dos formas de configurar o establecer atributos de clase.

La primera es la forma estándar de Python de sobreescribir y sobrecargar atributos y métodos en una subclase. Así que si tu clase base tenía un atributo greeting como este:

from django.http import HttpResponse
from django.views import View


class GreetingView(View):
    greeting = "Good Day"

    def get(self, request):
        return HttpResponse(self.greeting)

Puedes sobrescribir eso en una subclase:

class MorningGreetingView(GreetingView):
    greeting = "Morning to ya"

Otra opción es configurar atributos de clase como argumentos clave en la llamada a as_view() en el archivo urls.py.

urlpatterns = [
    path("about/", GreetingView.as_view(greeting="G'day")),
]

Nota

Mientras que tu clase se instancia para cada solicitud enviada a ella, las atributos de la clase configurados mediante el punto de entrada as_view() se configuran solo una vez cuando se importan tus URL.

Usando mezclas

Las mixins son una forma de herencia múltiple donde se pueden combinar comportamientos y atributos de varias clases padre.

Por ejemplo, en las vistas basadas en clases genéricas hay un mixin llamado ~django.views.generic.base.TemplateResponseMixin cuyo propósito principal es definir el método render_to_response. Cuando se combina con el comportamiento de la clase base View, el resultado es una clase TemplateView que enviará solicitudes a los métodos correspondientes (un comportamiento definido en la clase base View), y que tiene un método render_to_response que utiliza un atributo template_name para devolver un objeto ~django.template.response.TemplateResponse.

Los mixines son una excelente forma de reutilizar código en varias clases, pero vienen con un costo. Cuanto más disperso esté tu código entre mixines, más difícil será leer una clase hija y saber exactamente qué está haciendo, y más difícil será saber cuáles métodos de qué mixines sobrescribir si estás sobreescribiendo algo que tiene un árbol de herencia profundo.

También ten en cuenta que solo puedes heredar de una vista genérica - es decir, solo puede haber una clase padre que herede de View y el resto (si los hay) deberían ser mixins. Intentar heredar de más de una clase que hereda de View - por ejemplo, intentando usar un formulario en la parte superior de una lista y combinando ProcessFormView y ListView - no funcionará como se espera.

Tratamiento de formularios con vistas basadas en clases.

Una vista basada en funciones básica que maneja formularios puede verse así:

from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import MyForm


def myview(request):
    if request.method == "POST":
        form = MyForm(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect("/success/")
    else:
        form = MyForm(initial={"key": "value"})

    return render(request, "form_template.html", {"form": form})

Una vista basada en clases similar podría verse así:

from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View

from .forms import MyForm


class MyFormView(View):
    form_class = MyForm
    initial = {"key": "value"}
    template_name = "form_template.html"

    def get(self, request, *args, **kwargs):
        form = self.form_class(initial=self.initial)
        return render(request, self.template_name, {"form": form})

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect("/success/")

        return render(request, self.template_name, {"form": form})

Este es un caso mínimo, pero puedes ver que entonces tendrías la opción de personalizar esta vista sobrescribiendo cualquier uno de los atributos de clase, por ejemplo form_class, mediante la configuración del URLconf o sobrescribiendo y reemplazando uno o más de los métodos (o ambos!).

Decorar vistas basadas en clases

La extensión de las vistas basadas en clases no se limita a utilizar mixins. También puedes usar decoradores. Dado que las vistas basadas en clases no son funciones, decorarlas funciona de manera diferente dependiendo si estás utilizando as_view() o creando una subclase.

Decorar en el URLconf

Puedes ajustar las vistas basadas en clases decorando el resultado del método as_view(). El lugar más fácil para hacer esto es en el URLconf donde despliegues tu vista:

from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView

from .views import VoteView

urlpatterns = [
    path("about/", login_required(TemplateView.as_view(template_name="secret.html"))),
    path("vote/", permission_required("polls.can_vote")(VoteView.as_view())),
]

Esta aproximación aplica el decorador de manera instanciada. Si deseas que cada instancia de una vista esté decorada, necesitas tomar un enfoque diferente.

Decorar la clase

Para decorar cada instancia de una vista basada en clases, debes decorar la definición de la clase misma. Para hacer esto aplicas el decorador al método dispatch() de la clase.

Un método en una clase no es exactamente lo mismo que una función independiente, por lo que no puedes simplemente aplicar un decorador de funciones a el método – necesitas transformarlo primero en un decorador de métodos. El decorador method_decorator transforma un decorador de funciones en un decorador de métodos para que pueda usarse en un método instanciado. Por ejemplo:

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView


class ProtectedView(TemplateView):
    template_name = "secret.html"

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

O, más concisamente, puedes decorar la clase en lugar de eso y pasar el nombre del método a decorar como argumento de palabra clave name:

@method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
    template_name = "secret.html"

Si tienes un conjunto de decoradores comunes utilizados en varios lugares, puedes definir una lista o tupla de decoradores y utilizar esto en lugar de invocar method_decorator() múltiples veces. Estas dos clases son equivalentes:

decorators = [never_cache, login_required]


@method_decorator(decorators, name="dispatch")
class ProtectedView(TemplateView):
    template_name = "secret.html"


@method_decorator(never_cache, name="dispatch")
@method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
    template_name = "secret.html"

Los decoradores procesarán la solicitud en el orden en que se les pasa al decorador. En el ejemplo, never_cache() procesará la solicitud antes de login_required().

En este ejemplo, cada instancia de ProtectedView tendrá protección de inicio de sesión. Estos ejemplos utilizan login_required, sin embargo, el mismo comportamiento se puede obtener utilizando LoginRequiredMixin.

Nota

@method_decorator pasa *args y **kwargs como parámetros al método decorado en la clase. Si tu método no acepta un conjunto de parámetros compatible, levantará una excepción de tipo TypeError.