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á alrender_to_response()
método queTemplateResponseMixin
proporciona. La mayoría de las veces, esto se llamará por usted (por ejemplo, lo llamará elget()
método implementado por ambosTemplateView
yDetailView
); 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 llamaget_template_names()
, que de forma predeterminada buscarátemplate_name
en la vista basada en clases; otros dos mixinsSingleObjectTemplateResponseMixin
yMultipleObjectTemplateResponseMixin
) 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 llamarget_context_data()
pasando los datos que quieran asegurarse de que estén allí como argumentos de palabras clave.get_context_data()
devuelve un diccionario; enContextMixin
devuelve sus argumentos de palabras clave, pero es común anular esto para agregar más miembros al diccionario. También puede utilizar elextra_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 (FormView
y 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
, siendo el más utilizado
options<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, anulamosget_queryset()
y usa elPublisher
‘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 correctoPublisher
objeto. Sin embargo, necesitamos pasar explícitamente unqueryset
argumento porque de lo contrario la implementación predeterminada deget_object()
Llamaríaget_queryset()
que hemos anulado para devolverBook
objetos en lugar dePublisher
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 FormMixin
y 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 SingleObjectTemplateResponseMixin
y 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
.