Escribir aplicaciones web puede ser monótono, ya que repetimos ciertos patrones una y otra vez. Django intenta quitar un poco de esa monotonía a los niveles del modelo y la plantilla, pero también los desarrolladores de web experimentan esta aburrimiento al nivel de vista.
Las vistas genéricas de Django se desarrollaron para aliviar ese dolor. Ellos toman ciertos idiosincrasias comunes y patrones encontrados en el desarrollo de vistas y los abstractan para que puedas escribir rápidamente vistas comunes de datos sin tener que escribir demasiado código.
We podemos reconocer ciertas tareas comunes, como mostrar una lista de objetos, y escribir código que muestre una lista de cualquier objeto. Luego el modelo en cuestión se puede pasar como un argumento adicional a la URLconf.
Django incluye vistas genéricas para realizar las siguientes tareas:
Mostrar páginas de lista y detalles para un solo objeto. Si estuviéramos creando una aplicación para gestionar conferencias, entonces TalkListView y RegisteredUserListView serían ejemplos de vistas de lista. Una página de detalles de un solo discurso es un ejemplo de lo que llamamos «vista de detalles».
Presentar objetos basados en fechas en páginas de archivo año/mes/día, detalles asociados y «últimas» páginas.
Permitir a los usuarios crear, actualizar y eliminar objetos – con o sin autorización.
Juntas, estas vistas proporcionan interfaces para realizar las tareas más comunes que enfrentan los desarrolladores.
No hay duda de que utilizar vistas genéricas puede acelerar significativamente el desarrollo. Sin embargo, en la mayoría de los proyectos, llega un momento en que las vistas genéricas ya no bastan. De hecho, la pregunta más común que se hace a los nuevos desarrolladores de Django es cómo hacer que las vistas genéricas manejen una amplia gama de situaciones.
Esto es uno de los motivos por los cuales se rediseñaron las vistas genéricas para la versión 1.3 - anteriormente, eran funciones de vista con una confusa variedad de opciones; ahora, en lugar de pasar una gran cantidad de configuración en la URLconf, el método recomendado para extender las vistas genéricas es heredarlas y sobrescribir sus atributos o métodos.
Dicho esto, las vistas genéricas tendrán un límite. Si encuentras que estás luchando para implementar tu vista como una clase derivada de una vista genérica, entonces puede ser más efectivo escribir solo el código que necesitas, utilizando tus propias clases basadas en vistas o vistas funcionales.
More examples of generic views are available in some third party applications, or you could write your own as needed.
TemplateView es ciertamente útil, pero las vistas genericas de Django realmente brillan cuando se trata de presentar vistas del contenido de la base de datos. Porque es una tarea tan común, Django viene con un puñado de vistas genericas integradas para ayudar a generar listas y detalles de vistas de objetos.
Vamos a empezar mirando algunos ejemplos de mostrar una lista de objetos o un objeto individual.
Usaremos estos modelos:
# models.py
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Meta:
ordering = ["-name"]
def __str__(self):
return self.name
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to="author_headshots")
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField("Author")
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publication_date = models.DateField()
Ahora necesitamos definir una vista:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherListView(ListView):
model = Publisher
Finalmente, conecta esa vista con tus urls:
# urls.py
from django.urls import path
from books.views import PublisherListView
urlpatterns = [
path("publishers/", PublisherListView.as_view()),
]
Eso es todo el código en Python que necesitamos escribir. Todavía necesitamos escribir un template, sin embargo. Podríamos decir explícitamente al view qué template usar por agregar una template_name atributo a la vista, pero en ausencia de un template explícito Django inferirá uno del nombre del objeto. En este caso, el template inferido será "libros/editor_list.html" – la parte «libros» viene del nombre de la aplicación que define el modelo, mientras que la «editor» parte es la versión lowercaser del nombre del modelo.
Nota
Por lo tanto, cuando (por ejemplo) la opción APP_DIRS de un backend DjangoTemplates está configurada a True en TEMPLATES, una ubicación de template podría ser: /path/to/project/libros/templates/libros/editor_list.html
Este template se renderizará contra un contexto que contiene una variable llamada object_list que contiene todos los objetos editor. Un template podría verse así:
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
Eso es todo lo que hay que hacer. Todas las características geniales de las vistas genericas provienen del cambio de los atributos establecidos en la vista generica. La referencia a vistas genericas documenta todas las vistas genericas y sus opciones con detalle; el resto de este documento considerará algunas de las formas comunes en que podrías personalizar y extender vistas genericas.
Puede haber notado que nuestra lista de plantillas de ejemplo del editor almacena a todos los editores en una variable llamada object_list. Si bien esto funciona perfectamente, no es muy «amigable» para autores de plantillas: ellos tienen que «solo saber» que están tratando con editores aquí.
Bueno, si estás tratando con un objeto de modelo, esto ya está hecho por ti. Cuando estés tratando con un objeto o conjunto de objetos, Django es capaz de poblar el contexto utilizando la versión en minúsculas del nombre de clase del modelo. Esto se proporciona además de la entrada predeterminada object_list, pero contiene exactamente los mismos datos, es decir publisher_list.
Si esto todavía no es una buena coincidencia, puedes establecer manualmente el nombre de la variable de contexto. El atributo context_object_name en una vista generica especifica la variable de contexto a utilizar:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherListView(ListView):
model = Publisher
context_object_name = "my_favorite_publishers"
Proporcionar un context_object_name útil siempre es una buena idea. Tus compañeros que diseñan plantillas te lo agradecerán.
Muchas veces necesitas presentar alguna información adicional más allá de la proporcionada por la vista generica. Por ejemplo, piensa en mostrar una lista de todos los libros en cada página de detalles del editor. La DetailView vista generica proporciona al contexto al editor, pero ¿cómo obtenemos información adicional en esa plantilla?
La respuesta es heredar de la DetailView y proporcionar tu propia implementación del método get_context_data. La implementación por defecto agrega el objeto que se está mostrando al contexto, pero puedes sobrescribirla para enviar más:
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetailView(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context["book_list"] = Book.objects.all()
return context
Nota
Generalmente, get_context_data fusionará los datos de contexto de todas las clases padres con los de la clase actual. Para preservar este comportamiento en tus propias clases donde quieres alterar el contexto, asegúrate de llamar a get_context_data en la superclase. Cuando no dos clases intenten definir la misma clave, esto dará los resultados esperados. Sin embargo si alguna clase intenta sobrescribir una clave después que las clases padres la hayan establecido (después del llamado a super), cualquier hijo de esa clase también necesitará establecerla explícitamente después de super si quiere asegurarse de sobreescribir a todos los padres. Si tienes problemas, revisa el orden de resolución de métodos de tu vista.
Otra consideración es que los datos de contexto desde vistas genéricas basadas en clases sobrescribirán los datos proporcionados por procesadores de contexto; consulte la get_context_data() para un ejemplo.
Ahora vamos a echar un vistazo más detallado al argumento model que hemos estado utilizando todo el tiempo. El argumento model, que especifica el modelo de base de datos sobre el que operará la vista, está disponible en todas las vistas genéricas que operan sobre un objeto único o una colección de objetos. Sin embargo, el argumento model no es el único modo de especificar los objetos sobre los que operará la vista – también puedes especificar la lista de objetos utilizando el argumento queryset:
from django.views.generic import DetailView
from books.models import Publisher
class PublisherDetailView(DetailView):
context_object_name = "publisher"
queryset = Publisher.objects.all()
Especificar model = Publisher es lo mismo que decir queryset = Publisher.objects.all(). Sin embargo, al utilizar queryset para definir una lista filtrada de objetos puedes ser más específico sobre los objetos que estarán visibles en la vista (consulte Haciendo consultas para obtener más información sobre objetos QuerySet, y consulte el referencia a vistas basadas en clases para obtener detalles completos).
Por ejemplo, podríamos querer ordenar una lista de libros por fecha de publicación, con los más recientes primero:
from django.views.generic import ListView
from books.models import Book
class BookListView(ListView):
queryset = Book.objects.order_by("-publication_date")
context_object_name = "book_list"
Eso es un ejemplo bastante minimalista, pero ilustra la idea bien. Normalmente querrás hacer algo más que simplemente reordenar objetos. Si quieres presentar una lista de libros de un determinado editor, puedes utilizar el mismo técnico:
from django.views.generic import ListView
from books.models import Book
class AcmeBookListView(ListView):
context_object_name = "book_list"
queryset = Book.objects.filter(publisher__name="ACME Publishing")
template_name = "books/acme_list.html"
Nota que junto con un queryset filtrado, también estamos utilizando un nombre de plantilla personalizado. Si no lo hiciéramos, la vista genérica usaría la misma plantilla que la lista de objetos «vanilla», lo que podría no ser lo que queremos.
También nota que esto no es una forma muy elegante de hacer libros específicos de un editor. Si queremos agregar otra página de editores, necesitaríamos otro puñado de líneas en el URLconf, y más de unos pocos editores serían demasiado razonables. Tratarémos con este problema en la siguiente sección.
Nota
Si obtienes un 404 al solicitar /books/acme/, asegúrate de que realmente tienes un Publisher con el nombre “ACME Publishing”. Las vistas genéricas tienen un parámetro allow_empty para este caso. Consulta la referencia a vistas basadas en clases para obtener más detalles.
Otra necesidad común es filtrar los objetos dados en una página de lista por alguna clave en la URL. Anteriormente, codificamos el nombre del editor en la URLconf, pero ¿qué pasa si queríamos escribir una vista que mostrara todos los libros de algún editor arbitrario?
Convenientemente, la ListView tiene un método get_queryset() que podemos sobrescribir. Por defecto, devuelve el valor de la propiedad queryset, pero podemos utilizarlo para agregar más lógica.
La parte clave para hacer esto funcionar es que cuando se llaman las vistas basadas en clase, se almacenan varias cosas útiles en self; además del request (self.request) incluye los argumentos posicionales (self.args) y basados en nombre (self.kwargs) capturados según la URLconf.
Aquí tenemos una URLconf con un solo grupo capturado:
# urls.py
from django.urls import path
from books.views import PublisherBookListView
urlpatterns = [
path("books/<publisher>/", PublisherBookListView.as_view()),
]
A continuación, escribiremos la vista PublisherBookListView misma:
# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher
class PublisherBookListView(ListView):
template_name = "books/books_by_publisher.html"
def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.kwargs["publisher"])
return Book.objects.filter(publisher=self.publisher)
Utilizar get_queryset para agregar lógica a la selección del conjunto de resultados es tan conveniente como poderoso. Por ejemplo, si queríamos, podríamos utilizar self.request.user para filtrar según el usuario actual o más lógica compleja.
También podemos agregar al editor en el contexto al mismo tiempo, así podremos usarlo en la plantilla:
# ...
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in the publisher
context["publisher"] = self.publisher
return context
El último patrón común que veremos implica realizar algún trabajo extra antes o después de llamar a la vista genérica.
Imagine que teníamos un campo last_accessed en nuestro modelo Author para mantener el registro del último momento en que alguien miró ese autor:
# models.py
from django.db import models
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to="author_headshots")
last_accessed = models.DateTimeField()
La traducción de los textos es la siguiente:
Primero, necesitaríamos agregar un bit de detalles del autor en el URLconf para apuntar a una vista personalizada:
from django.urls import path
from books.views import AuthorDetailView
urlpatterns = [
# ...
path("authors/<int:pk>/", AuthorDetailView.as_view(), name="author-detail"),
]
Luego escribiríamos nuestra nueva vista – get_object es el método que recupera el objeto – por lo tanto lo sobrescribimos y envolvemos la llamada:
from django.utils import timezone
from django.views.generic import DetailView
from books.models import Author
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
obj = super().get_object()
# Record the last accessed date
obj.last_accessed = timezone.now()
obj.save()
return obj
Nota
Aquí, el URLconf utiliza el grupo de nombre pk - este nombre es el nombre predeterminado que DetailView utiliza para encontrar el valor de la clave primaria utilizada para filtrar el conjunto de resultados.
Si deseas llamar al grupo con otro nombre, puedes establecer pk_url_kwarg en la vista.
may 31, 2026