Precaución

Este es un tema avanzado. Un conocimiento práctico de Vistas basadas en clases de Django se aconseja antes de explorar estas técnicas.

Las vistas integradas basadas en clases de Django proporcionan mucha funcionalidad, pero es posible que desee utilizar algunas de ellas por separado. Por ejemplo, es posible que desee escribir una vista que represente una plantilla para generar la respuesta HTTP, pero no puede usar TemplateView; tal vez necesite renderizar una plantilla solo en POST, con GET haciendo algo completamente diferente. Mientras puedas usar TemplateResponse directamente, esto probablemente resultará en un código duplicado.

Por esta razón, Django también proporciona una serie de mixins que proporcionan una funcionalidad más discreta. La representación de plantillas, por ejemplo, está encapsulada en el TemplateResponseMixin. La documentación de referencia de Django contiene documentación completa de todos los mixins.

Respuestas de contexto y plantilla

Se proporcionan dos mixins centrales que ayudan a proporcionar una interfaz coherente para trabajar con plantillas en vistas basadas en clases.

TemplateResponseMixin

Cada vista incorporada que devuelve un TemplateResponse llamará al render_to_response() método que TemplateResponseMixin proporciona. La mayoría de las veces, esto se llamará por usted (por ejemplo, lo llamará el get() método implementado por ambos TemplateView y DetailView); de manera similar, es poco probable que necesite anularlo, aunque si desea que su respuesta devuelva algo que no se representa a través de una plantilla de Django, entonces querrá hacerlo. Para ver un ejemplo de esto, vea el Ejemplo de JSONResponseMixin.

render_to_response() sí mismo llama get_template_names(), que de forma predeterminada buscará template_name en la vista basada en clases; otros dos mixinsSingleObjectTemplateResponseMixin y MultipleObjectTemplateResponseMixin) anula esto para proporcionar valores predeterminados más flexibles cuando se trata de objetos reales.

ContextMixin
Cada vista incorporada que necesita datos de contexto, como para renderizar una plantilla (incluyendo TemplateResponseMixin arriba), debería llamar get_context_data() pasando los datos que quieran asegurarse de que estén allí como argumentos de palabras clave. get_context_data() devuelve un diccionario; en ContextMixin devuelve sus argumentos de palabras clave, pero es común anular esto para agregar más miembros al diccionario. También puede utilizar el extra_context atributo.

Construyendo vistas genéricas basadas en clases de Django

Veamos cómo dos de las vistas genéricas basadas en clases de Django se construyen a partir de mixins que brindan una funcionalidad discreta. Consideraremos DetailView, que ofrece una vista de “detalle” de un objeto, y ListView, que representará una lista de objetos, normalmente de un conjunto de consultas, y los paginará opcionalmente. Esto nos presentará cuatro mixins que entre ellos brindan una funcionalidad útil cuando se trabaja con un solo objeto Django o con varios objetos.

También hay mixins involucrados en las vistas de edición genéricas (FormViewy las vistas específicas del modelo CreateView, UpdateView y DeleteView) y en las vistas genéricas basadas en fechas. Estos están cubiertos en el documentación de referencia mixin.

DetailView: trabajando con un solo objeto Django

Para mostrar el detalle de un objeto, básicamente necesitamos hacer dos cosas: necesitamos buscar el objeto y luego necesitamos hacer un TemplateResponse con una plantilla adecuada y ese objeto como contexto.

Para conseguir el objeto DetailView se basa en SingleObjectMixin, que proporciona una get_object() método que descubre el objeto en función de la URL de la solicitud (busca pk y slug argumentos de palabra clave declarados en URLConf, y busca el objeto desde el model atributo en la vista, o el queryset atributo si se proporciona). SingleObjectMixin también anula get_context_data(), que se utiliza en todas las vistas basadas en clases integradas de Django para proporcionar datos de contexto para las representaciones de plantillas.

Para luego hacer un TemplateResponse, DetailView usos SingleObjectTemplateResponseMixin, que se extiende TemplateResponseMixin, anulando get_template_names() como se discutió anteriormente. En realidad, proporciona un conjunto de opciones bastante sofisticado, pero la principal que la mayoría de la gente va a utilizar es <app_label>/<model_name>_detail.html. los _detail parte se puede cambiar configurando template_name_suffix en una subclase a otra cosa. (Por ejemplo, el vistas de edición genéricas usar _form para crear y actualizar vistas, y _confirm_delete para eliminar vistas.)

ListView: trabajando con muchos objetos Django

Las listas de objetos siguen aproximadamente el mismo patrón: necesitamos una lista de objetos (posiblemente paginada), típicamente una QuerySet, y luego necesitamos hacer un TemplateResponse con una plantilla adecuada utilizando esa lista de objetos.

Para conseguir los objetos ListView usos MultipleObjectMixin, que proporciona tanto get_queryset() y paginate_queryset(). A diferencia de SingleObjectMixin, no es necesario eliminar partes de la URL para determinar el conjunto de consultas con el que trabajar, por lo que el valor predeterminado usa el queryset o model atributo en la clase de vista. Una razón común para anular get_queryset() aquí sería variar dinámicamente los objetos, por ejemplo, dependiendo del usuario actual o excluir publicaciones en el futuro para un blog.

MultipleObjectMixin también anula get_context_data() para incluir variables de contexto apropiadas para la paginación (proporcionando dummies si la paginación está deshabilitada). Se basa en object_list se pasa como un argumento de palabra clave, que ListView lo arregla.

Hacer un TemplateResponse, ListView luego usa MultipleObjectTemplateResponseMixin; Al igual que con SingleObjectTemplateResponseMixin arriba, esto anula get_template_names() Para proveer a range of
options
, siendo el más utilizado <app_label>/<model_name>_list.html, con el _list parte de nuevo siendo tomada de la template_name_suffix atributo. (Las vistas genéricas basadas en fechas usan sufijos como _archive, _archive_year y así sucesivamente para usar diferentes plantillas para las diversas vistas de lista especializadas basadas en fechas).

Usando los mixins de vista basados ​​en clases de Django

Ahora que hemos visto cómo las vistas genéricas basadas en clases de Django usan los mixins proporcionados, veamos otras formas en que podemos combinarlos. Todavía los vamos a combinar con vistas integradas basadas en clases u otras vistas genéricas basadas en clases, pero hay una variedad de problemas más raros que puede resolver que los que proporciona Django de fábrica.

Advertencia

No todos los mixins se pueden usar juntos, y no todas las vistas genéricas basadas en clases se pueden usar con todos los demás mixins. Aquí presentamos algunos ejemplos que funcionan; Si desea reunir otras funciones, tendrá que considerar las interacciones entre los atributos y métodos que se superponen entre las diferentes clases que está utilizando y cómo orden de resolución del método afectará a qué versiones de los métodos se llamarán en qué orden.

La documentación de referencia para Django’s vistas basadas en clases y mixins de vista basados ​​en clases le ayudará a comprender qué atributos y métodos pueden causar conflictos entre diferentes clases y mixins.

En caso de duda, a menudo es mejor retroceder y basar su trabajo en View o TemplateView, tal vez con SingleObjectMixin y MultipleObjectMixin. Aunque probablemente terminará escribiendo más código, es más probable que alguien más lo entienda más tarde y, con menos interacciones de las que preocuparse, se ahorrará un poco de pensamiento. (Por supuesto, siempre puede echar mano de la implementación de Django de las vistas genéricas basadas en clases para inspirarse sobre cómo abordar los problemas).

Utilizando SingleObjectMixin con vista

Si queremos escribir una vista basada en clases que responda solo a POST, subclasificaremos View y escribe un post() método en la subclase. Sin embargo, si queremos que nuestro procesamiento funcione en un objeto en particular, identificado a partir de la URL, querremos la funcionalidad proporcionada por SingleObjectMixin.

Lo demostraremos con el Author modelo que usamos en el Introducción a las vistas genéricas basadas en clases.

views.py

from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.views import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author

class RecordInterestView(SingleObjectMixin, View):
    """Records the current user's interest in an author."""
    model = Author

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return HttpResponseForbidden()

        # Look up the author we're interested in.
        self.object = self.get_object()
        # Actually record interest somehow here!

        return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))

En la práctica, probablemente desee registrar el interés en un almacén de valores-clave en lugar de en una base de datos relacional, por lo que hemos dejado esa parte fuera. La única parte de la vista que debe preocuparse por usar SingleObjectMixin es donde queremos buscar el autor que nos interesa, lo que hace con una llamada a self.get_object(). El mixin se encarga de todo lo demás.

Podemos conectar esto en nuestras URL con bastante facilidad:

urls.py

from django.urls import path
from books.views import RecordInterestView

urlpatterns = [
    #...
    path('author/<int:pk>/interest/', RecordInterestView.as_view(), name='author-interest'),
]

Nota la pk grupo nombrado, que get_object() utiliza para buscar el Author ejemplo. También puede usar una babosa o cualquiera de las otras características de SingleObjectMixin.

Utilizando SingleObjectMixin con ListView

ListView proporciona paginación incorporada, pero es posible que desee paginar una lista de objetos que están todos vinculados (por una clave externa) a otro objeto. En nuestro ejemplo de publicación, es posible que desee paginar todos los libros de una editorial en particular.

Una forma de hacer esto es combinar ListView con SingleObjectMixin, de modo que el conjunto de consultas para la lista paginada de libros pueda colgarse del editor encontrado como único objeto. Para hacer esto, necesitamos tener dos conjuntos de consultas diferentes:

Book queryset for use by ListView
Dado que tenemos acceso a la Publisher cuyos libros queremos enumerar, anulamos get_queryset() y usa el Publisher‘s administrador de claves foráneas inversas.
Publisher queryset for use in get_object()
Confiaremos en la implementación predeterminada de get_object() para buscar el correcto Publisher objeto. Sin embargo, necesitamos pasar explícitamente un queryset argumento porque de lo contrario la implementación predeterminada de get_object() Llamaría get_queryset() que hemos anulado para devolver Book objetos en lugar de Publisher unos.

Nota

Tenemos que pensar detenidamente sobre get_context_data(). Ya que ambos SingleObjectMixin y ListView pondrá las cosas en los datos de contexto bajo el valor de context_object_name si está configurado, en su lugar nos aseguraremos explícitamente Publisher está en los datos de contexto. ListView agregará en el adecuado page_obj y paginator para nosotros siempre que recordemos llamar super().

Ahora podemos escribir un nuevo PublisherDetailView:

from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher

class PublisherDetailView(SingleObjectMixin, ListView):
    paginate_by = 2
    template_name = "books/publisher_detail.html"

    def get(self, request, *args, **kwargs):
        self.object = self.get_object(queryset=Publisher.objects.all())
        return super().get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['publisher'] = self.object
        return context

    def get_queryset(self):
        return self.object.book_set.all()

Observe cómo configuramos self.object dentro de get() para que podamos usarlo de nuevo más tarde en get_context_data() y get_queryset(). Si no configuras template_name, la plantilla estará por defecto en la normal ListView elección, que en este caso sería "books/book_list.html" porque es una lista de libros; ListView no sabe nada sobre SingleObjectMixin, por lo que no tiene ni idea de que esta vista tenga algo que ver con un Publisher.

los paginate_by es deliberadamente pequeño en el ejemplo, por lo que no es necesario crear muchos libros para ver cómo funciona la paginación. Aquí está la plantilla que le gustaría usar:

{% extends "base.html" %}

{% block content %}
    <h2>Publisher {{ publisher.name }}</h2>

    <ol>
      {% for book in page_obj %}
        <li>{{ book.title }}</li>
      {% endfor %}
    </ol>

    <div class="pagination">
        <span class="step-links">
            {% if page_obj.has_previous %}
                <a href="?page={{ page_obj.previous_page_number }}">previous</a>
            {% endif %}

            <span class="current">
                Page {{ page_obj.number }} of {{ paginator.num_pages }}.
            </span>

            {% if page_obj.has_next %}
                <a href="?page={{ page_obj.next_page_number }}">next</a>
            {% endif %}
        </span>
    </div>
{% endblock %}

Evita cualquier cosa más compleja

Generalmente puedes usar TemplateResponseMixin y SingleObjectMixin cuando necesite su funcionalidad. Como se muestra arriba, con un poco de cuidado puede incluso combinar SingleObjectMixin con ListView. Sin embargo, las cosas se vuelven cada vez más complejas a medida que intenta hacerlo, y una buena regla general es:

Insinuación

Cada una de sus vistas debe usar solo mixins o vistas de uno de los grupos de vistas genéricas basadas en clases: detalle, lista, edición y fecha. Por ejemplo, está bien combinar TemplateView (vista integrada) con MultipleObjectMixin (lista genérica), pero es probable que tenga problemas para combinar SingleObjectMixin (detalle genérico) con MultipleObjectMixin (lista genérica).

Para mostrar lo que sucede cuando intenta ser más sofisticado, mostramos un ejemplo que sacrifica la legibilidad y la facilidad de mantenimiento cuando hay una solución más simple. Primero, veamos un intento ingenuo de combinar DetailView con FormMixin para permitirnos POST un Django Form a la misma URL en la que mostramos un objeto usando DetailView.

Utilizando FormMixin con DetailView

Piense en nuestro ejemplo anterior de uso View y SingleObjectMixin juntos. Estábamos registrando el interés de un usuario en un autor en particular; diga ahora que queremos que deje un mensaje diciendo por qué les gusta. Nuevamente, supongamos que no vamos a almacenar esto en una base de datos relacional, sino en algo más esotérico de lo que no nos preocuparemos aquí.

En este punto es natural alcanzar un Form para encapsular la información enviada desde el navegador del usuario a Django. Diga también que invertimos mucho en DESCANSAR, por lo que queremos usar la misma URL para mostrar el autor que para capturar el mensaje del usuario. Reescribamos nuestro AuthorDetailView Para hacer eso.

Mantendremos el GET manejo desde DetailView, aunque tendremos que agregar un Form en los datos de contexto para que podamos representarlos en la plantilla. También queremos extraer el procesamiento de formularios de FormMixiny escriba un poco de código para que en POST el formulario se llama de forma adecuada.

Nota

Usamos FormMixin e implementar post() nosotros mismos en lugar de intentar mezclar DetailView con FormView (que proporciona un adecuado post() ya) porque ambas vistas implementan get(), y las cosas se volverían mucho más confusas.

Nuestro nuevo AuthorDetailView Se ve como esto:

# CAUTION: you almost certainly do not want to do this.
# It is provided as part of a discussion of problems you can
# run into when combining different generic class-based view
# functionality that is not designed to be used together.

from django import forms
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
from books.models import Author

class AuthorInterestForm(forms.Form):
    message = forms.CharField()

class AuthorDetailView(FormMixin, DetailView):
    model = Author
    form_class = AuthorInterestForm

    def get_success_url(self):
        return reverse('author-detail', kwargs={'pk': self.object.pk})

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return HttpResponseForbidden()
        self.object = self.get_object()
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        # Here, we would record the user's interest using the message
        # passed in form.cleaned_data['message']
        return super().form_valid(form)

get_success_url() proporciona un lugar para redirigir, que se utiliza en la implementación predeterminada de form_valid(). Tenemos que proporcionar los nuestros post() como se menciono anteriormente.

Una mejor solucion

El número de interacciones sutiles entre FormMixin y DetailView ya está poniendo a prueba nuestra capacidad para gestionar las cosas. Es poco probable que desee escribir este tipo de clase usted mismo.

En este caso, podría escribir el post() método usted mismo, manteniendo DetailView como la única funcionalidad genérica, aunque escribiendo Form el manejo de código implica mucha duplicación.

Alternativamente, aún sería menos trabajo que el enfoque anterior tener una vista separada para procesar el formulario, que podría usar FormView distinto de DetailView sin preocupaciones.

Una alternativa mejor solución

Lo que realmente estamos tratando de hacer aquí es usar dos vistas basadas en clases diferentes desde la misma URL. Entonces, ¿por qué no hacer eso? Tenemos una división muy clara aquí: GET las solicitudes deben obtener el DetailView (con el Form agregado a los datos de contexto), y POST las solicitudes deben obtener el FormView. Primero configuremos esas vistas.

los AuthorDetailView la vista es casi la misma que cuando presentamos AuthorDetailView por primera vez; tenemos que escribir el nuestro get_context_data() para hacer el AuthorInterestForm disponible para la plantilla. Saltaremos el get_object() anular de antes para mayor claridad:

from django import forms
from django.views.generic import DetailView
from books.models import Author

class AuthorInterestForm(forms.Form):
    message = forms.CharField()

class AuthorDetailView(DetailView):
    model = Author

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = AuthorInterestForm()
        return context

Entonces el AuthorInterestForm es un FormView, pero tenemos que traer SingleObjectMixin para que podamos encontrar el autor del que estamos hablando y debemos recordar configurar template_name para garantizar que los errores de formulario generen la misma plantilla que AuthorDetailView está usando en GET:

from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin

class AuthorInterestFormView(SingleObjectMixin, FormView):
    template_name = 'books/author_detail.html'
    form_class = AuthorInterestForm
    model = Author

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return HttpResponseForbidden()
        self.object = self.get_object()
        return super().post(request, *args, **kwargs)

    def get_success_url(self):
        return reverse('author-detail', kwargs={'pk': self.object.pk})

Finalmente reunimos esto en un nuevo AuthorView vista. Ya sabemos que llamar as_view() en una vista basada en clases nos da algo que se comporta exactamente como una vista basada en funciones, por lo que podemos hacerlo en el punto que elijamos entre las dos subvistas.

Puede pasar argumentos de palabras clave a as_view() de la misma manera que lo haría en su URLconf, como si quisiera el AuthorInterestFormView comportamiento para aparecer también en otra URL pero usando una plantilla diferente:

from django.views import View

class AuthorView(View):

    def get(self, request, *args, **kwargs):
        view = AuthorDetailView.as_view()
        return view(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        view = AuthorInterestFormView.as_view()
        return view(request, *args, **kwargs)

Este enfoque también se puede utilizar con cualquier otra vista genérica basada en clases o con sus propias vistas basadas en clases heredadas directamente de View o TemplateView, ya que mantiene las diferentes vistas lo más separadas posible.

Más que solo HTML

Donde brillan las vistas basadas en clases es cuando quieres hacer lo mismo muchas veces. Suponga que está escribiendo una API y que cada vista debe devolver JSON en lugar de HTML renderizado.

Podemos crear una clase mixin para usar en todas nuestras vistas, manejando la conversión a JSON una vez.

Por ejemplo, un mixin JSON podría verse así:

from django.http import JsonResponse

class JSONResponseMixin:
    """
    A mixin that can be used to render a JSON response.
    """
    def render_to_json_response(self, context, **response_kwargs):
        """
        Returns a JSON response, transforming 'context' to make the payload.
        """
        return JsonResponse(
            self.get_data(context),
            **response_kwargs
        )

    def get_data(self, context):
        """
        Returns an object that will be serialized as JSON by json.dumps().
        """
        # Note: This is *EXTREMELY* naive; in reality, you'll need
        # to do much more complex handling to ensure that arbitrary
        # objects -- such as Django model instances or querysets
        # -- can be serialized as JSON.
        return context

Nota

Revisar la Serializar objetos de Django documentación para obtener más información sobre cómo transformar correctamente los modelos y conjuntos de consultas de Django en JSON.

Este mixin proporciona un render_to_json_response() método con la misma firma que render_to_response(). Para usarlo, necesitamos mezclarlo en un TemplateView por ejemplo, y anular render_to_response() llamar render_to_json_response() en lugar de:

from django.views.generic import TemplateView

class JSONView(JSONResponseMixin, TemplateView):
    def render_to_response(self, context, **response_kwargs):
        return self.render_to_json_response(context, **response_kwargs)

Igualmente, podríamos usar nuestro mixin con una de las vistas genéricas. Podemos hacer nuestra propia versión de DetailView mezclando JSONResponseMixin con el BaseDetailView – (los DetailView antes de que se haya mezclado el comportamiento de representación de la plantilla):

from django.views.generic.detail import BaseDetailView

class JSONDetailView(JSONResponseMixin, BaseDetailView):
    def render_to_response(self, context, **response_kwargs):
        return self.render_to_json_response(context, **response_kwargs)

Esta vista se puede implementar de la misma manera que cualquier otra DetailView, con exactamente el mismo comportamiento, excepto por el formato de la respuesta.

Si quieres ser realmente aventurero, incluso puedes mezclar un DetailView subclase que puede regresar ambos Contenido HTML y JSON, según alguna propiedad de la solicitud HTTP, como un argumento de consulta o un encabezado HTTP. Mezcle ambos JSONResponseMixin y un SingleObjectTemplateResponseMixiny anular la implementación de render_to_response() para diferir al método de representación apropiado según el tipo de respuesta que solicitó el usuario:

from django.views.generic.detail import SingleObjectTemplateResponseMixin

class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
    def render_to_response(self, context):
        # Look for a 'format=json' GET argument
        if self.request.GET.get('format') == 'json':
            return self.render_to_json_response(context)
        else:
            return super().render_to_response(context)

Debido a la forma en que Python resuelve la sobrecarga de métodos, la llamada a super().render_to_response(context) termina llamando al render_to_response() implementación de TemplateResponseMixin.