Escribir aplicaciones web puede resultar monótono, porque repetimos ciertos patrones una y otra vez. Django intenta quitar algo de esa monotonía en las capas de modelo y plantilla, pero los desarrolladores web también experimentan este aburrimiento a nivel de vista.

De Django vistas genéricas fueron desarrollados para aliviar ese dolor. Toman ciertos modismos y patrones comunes que se encuentran en el desarrollo de vistas y los abstraen para que pueda escribir rápidamente vistas comunes de datos sin tener que escribir demasiado código.

Podemos reconocer ciertas tareas comunes, como mostrar una lista de objetos y escribir código que muestre una lista de alguna objeto. Luego, el modelo en cuestión se puede pasar como un argumento adicional a la URLconf.

Django se envía con vistas genéricas para hacer lo siguiente:

  • Muestra la lista y las páginas de detalles de un solo objeto. Si estuviéramos creando una aplicación para administrar conferencias, entonces una TalkListView y un RegisteredUserListView serían ejemplos de vistas de lista. Una sola página de conversación es un ejemplo de lo que llamamos una vista de “detalle”.
  • Presentar objetos basados ​​en fechas en páginas de archivo de año / mes / día, detalles asociados y páginas “más recientes”.
  • Permita que los usuarios creen, actualicen y eliminen objetos, con o sin autorización.

En conjunto, estas vistas proporcionan interfaces para realizar las tareas más comunes que encuentran los desarrolladores.

Ampliación de vistas genéricas

No hay duda de que el uso de vistas genéricas puede acelerar sustancialmente el desarrollo. En la mayoría de los proyectos, sin embargo, llega un momento en que las vistas genéricas ya no son suficientes. De hecho, la pregunta más común que hacen los nuevos desarrolladores de Django es cómo hacer que las vistas genéricas manejen una gama más amplia de situaciones.

Esta es una de las razones por las que se rediseñaron las vistas genéricas para la versión 1.3; anteriormente, eran funciones de vista con una asombrosa variedad de opciones; ahora, en lugar de pasar una gran cantidad de configuración en la URLconf, la forma recomendada de extender las vistas genéricas es subclasificarlas y anular sus atributos o métodos.

Dicho esto, las vistas genéricas tendrán un límite. Si encuentra que tiene dificultades para implementar su vista como una subclase de una vista genérica, entonces puede resultarle más efectivo escribir solo el código que necesita, utilizando sus propias vistas funcionales o basadas en clases.

Hay más ejemplos de vistas genéricas disponibles en algunas aplicaciones de terceros, o puede escribir las suyas propias según sea necesario.

Vistas genéricas de objetos

TemplateView ciertamente es útil, pero las vistas genéricas de Django realmente brillan cuando se trata de presentar vistas del contenido de su base de datos. Debido a que es una tarea tan común, Django viene con un puñado de vistas genéricas integradas para ayudar a generar listas y vistas detalladas de objetos.

Comencemos mirando algunos ejemplos de mostrar una lista de objetos o un objeto individual.

Usaremos estos modelos:

# models.pyfrom django.db import models

classPublisher(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()classMeta:
        ordering =["-name"]def__str__(self):return self.name

classAuthor(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

classBook(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.pyfrom django.views.generic import ListView
from books.models import Publisher

classPublisherListView(ListView):
    model = Publisher

Finalmente, conecte esa vista a sus URL:

# urls.pyfrom django.urls import path
from books.views import PublisherListView

urlpatterns =[
    path('publishers/', PublisherListView.as_view()),]

Ese es todo el código Python que necesitamos escribir. Sin embargo, todavía necesitamos escribir una plantilla. Podríamos decirle explícitamente a la vista qué plantilla usar agregando un template_name atributo a la vista, pero en ausencia de una plantilla explícita, Django inferirá una del nombre del objeto. En este caso, la plantilla inferida será "books/publisher_list.html" – la parte “libros” proviene del nombre de la aplicación que define el modelo, mientras que el bit “editor” es la versión en minúsculas del nombre del modelo.

Nota

Así, cuando (por ejemplo) el APP_DIRS opción de un DjangoTemplates el backend se establece en True en TEMPLATES, una ubicación de plantilla podría ser: /path/to/project/books/templates/books/publisher_list.html

Esta plantilla se renderizará contra un contexto que contiene una variable llamada object_list que contiene todos los objetos del editor. Una plantilla podría verse así:

% extends "base.html" %

% block content %
    <h2>Publishersh2><ul>
        % for publisher in object_list %
            <li> publisher.name li>
        % endfor %
    ul>
% endblock %

Eso es realmente todo lo que hay que hacer. Todas las características interesantes de las vistas genéricas provienen de cambiar los atributos establecidos en la vista genérica. los referencia de vistas genéricas documenta todas las vistas genéricas y sus opciones en detalle; el resto de este documento considerará algunas de las formas comunes en las que puede personalizar y ampliar las vistas genéricas.

Crear contextos de plantilla “amigables”

Es posible que haya notado que nuestra plantilla de lista de editores de muestra almacena todos los editores en una variable denominada object_list. Si bien esto funciona bien, no es tan “amigable” para los autores de plantillas: tienen que “simplemente saber” que están tratando con editores aquí.

Bueno, si está tratando con un objeto modelo, esto ya está hecho. Cuando se trata de un objeto o conjunto de consultas, Django puede completar el contexto utilizando la versión en minúsculas del nombre de la clase del modelo. Esto se proporciona además del predeterminado object_list entrada, pero contiene exactamente los mismos datos, es decir publisher_list.

Si todavía no es una buena coincidencia, puede establecer manualmente el nombre de la variable de contexto. los context_object_name atributo en una vista genérica especifica la variable de contexto a utilizar:

# views.pyfrom django.views.generic import ListView
from books.models import Publisher

classPublisherListView(ListView):
    model = Publisher
    context_object_name ='my_favorite_publishers'

Proporcionando un útil context_object_name siempre es una buena idea. Tus compañeros de trabajo que diseñan plantillas te lo agradecerán.

Añadiendo contexto extra

A menudo es necesario presentar información adicional más allá de la proporcionada por la vista genérica. Por ejemplo, piense en mostrar una lista de todos los libros en la página de detalles de cada editor. los DetailView La vista genérica proporciona al editor el contexto, pero ¿cómo obtenemos información adicional en esa plantilla?

La respuesta es subclase DetailView y proporcione su propia implementación del get_context_data método. La implementación predeterminada agrega el objeto que se muestra a la plantilla, pero puede anularlo para enviar más:

from django.views.generic import DetailView
from books.models import Book, Publisher

classPublisherDetailView(DetailView):

    model = Publisher

    defget_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 principales con los de la clase actual. Para preservar este comportamiento en sus propias clases donde desea alterar el contexto, debe asegurarse de llamar get_context_data en la superclase. Cuando no hay dos clases que intenten definir la misma clave, esto dará los resultados esperados. Sin embargo, si alguna clase intenta anular una clave después de que las clases principales la hayan establecido (después de la llamada a super), los hijos de esa clase también deberán establecerla explícitamente después de super si quieren asegurarse de anular a todos los padres. Si tiene problemas, revise el orden de resolución del método de su vista.

Otra consideración es que los datos de contexto de las vistas genéricas basadas en clases anularán los datos proporcionados por los procesadores de contexto; ver get_context_data() para un ejemplo.

Ver subconjuntos de objetos

Ahora echemos un vistazo más de cerca al model argumento que hemos estado usando todo el tiempo. los model El argumento, que especifica el modelo de base de datos sobre el que operará la vista, está disponible en todas las vistas genéricas que operan en un solo objeto o una colección de objetos. sin embargo, el model El argumento no es la única forma de especificar los objetos sobre los que operará la vista; también puede especificar la lista de objetos utilizando el queryset argumento:

from django.views.generic import DetailView
from books.models import Publisher

classPublisherDetailView(DetailView):

    context_object_name ='publisher'
    queryset = Publisher.objects.all()

Especificando model = Publisher es una abreviatura de decir queryset =
Publisher.objects.all()
. Sin embargo, al usar queryset Para definir una lista filtrada de objetos, puede ser más específico sobre los objetos que serán visibles en la vista (consulte Realización de consultas para obtener más información sobre QuerySet objetos, y ver el referencia de vistas basadas en clases para los detalles completos).

Para elegir un ejemplo, podríamos querer ordenar una lista de libros por fecha de publicación, con el más reciente primero:

from django.views.generic import ListView
from books.models import Book

classBookListView(ListView):
    queryset = Book.objects.order_by('-publication_date')
    context_object_name ='book_list'

Ese es un ejemplo bastante mínimo, pero ilustra muy bien la idea. Por lo general, querrá hacer algo más que reordenar objetos. Si desea presentar una lista de libros de una editorial en particular, puede utilizar la misma técnica:

from django.views.generic import ListView
from books.models import Book

classAcmeBookListView(ListView):

    context_object_name ='book_list'
    queryset = Book.objects.filter(publisher__name='ACME Publishing')
    template_name ='books/acme_list.html'

Observe que junto con un filtrado queryset, también usamos un nombre de plantilla personalizado. Si no lo hiciéramos, la vista genérica usaría la misma plantilla que la lista de objetos “vainilla”, que podría no ser lo que queremos.

También tenga en cuenta que esta no es una forma muy elegante de hacer libros específicos para editoriales. Si queremos agregar otra página de editor, necesitaríamos otro puñado de líneas en la URLconf, y más de unos pocos editores se volverían irrazonables. Trataremos este problema en la siguiente sección.

Nota

Si obtiene un 404 al solicitar /books/acme/, verifique que realmente tenga un editor con el nombre ‘ACME Publishing’. Las vistas genéricas tienen un allow_empty parámetro para este caso. Ver el referencia de vistas basadas en clases para más detalles.

Filtrado dinámico

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 ¿y si quisiéramos escribir una vista que mostrara todos los libros de algún editor arbitrario?

Prácticamente, el ListView tiene un get_queryset() método que podemos anular. De forma predeterminada, devuelve el valor de la queryset atributo, pero podemos usarlo para agregar más lógica.

La parte clave para hacer que esto funcione es que cuando se llaman vistas basadas en clases, se almacenan varias cosas útiles en self; así como la solicitud (self.request) esto incluye el posicional (self.args) y basado en el nombre (self.kwargs) argumentos capturados de acuerdo con la URLconf.

Aquí, tenemos una URLconf con un solo grupo capturado:

# urls.pyfrom django.urls import path
from books.views import PublisherBookListView

urlpatterns =[
    path('books//', PublisherBookListView.as_view()),]

A continuación, escribiremos el PublisherBookListView verse a sí mismo:

# views.pyfrom django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher

classPublisherBookListView(ListView):

    template_name ='books/books_by_publisher.html'defget_queryset(self):
        self.publisher = get_object_or_404(Publisher, name=self.kwargs['publisher'])return Book.objects.filter(publisher=self.publisher)

Utilizando get_queryset agregar lógica a la selección del conjunto de consultas es tan conveniente como poderoso. Por ejemplo, si quisiéramos, podríamos usar self.request.user para filtrar usando el usuario actual u otra lógica más compleja.

También podemos agregar el editor en el contexto al mismo tiempo, por lo que podemos usarlo en la plantilla:

# ...defget_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

Realizar trabajo extra

El último patrón común que veremos implica hacer un trabajo adicional antes o después de llamar a la vista genérica.

Imagina que tuviéramos un last_accessed campo en nuestro Author modelo que estábamos usando para realizar un seguimiento de la última vez que alguien miró a ese autor:

# models.pyfrom django.db import models

classAuthor(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()

El genérico DetailView class no sabría nada sobre este campo, pero una vez más podríamos escribir una vista personalizada para mantener ese campo actualizado.

Primero, necesitaríamos agregar un bit de detalles del autor en la URLconf para apuntar a una vista personalizada:

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

urlpatterns =[#...
    path('authors//', AuthorDetailView.as_view(), name='author-detail'),]

Entonces escribimos nuestra nueva vista – get_object es el método que recupera el objeto, por lo que lo anulamos y ajustamos la llamada:

from django.utils import timezone
from django.views.generic import DetailView
from books.models import Author

classAuthorDetailView(DetailView):

    queryset = Author.objects.all()defget_object(self):
        obj =super().get_object()# Record the last accessed date
        obj.last_accessed = timezone.now()
        obj.save()return obj

Nota

La URLconf aquí usa el grupo nombrado pk – este nombre es el nombre predeterminado que DetailView utiliza para encontrar el valor de la clave principal utilizada para filtrar el conjunto de consultas.

Si desea llamar al grupo de otra manera, puede configurar pk_url_kwarg en la vista.