Ver índice de contenidos del libro

9.3. Extender las vistas genéricas

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

Afortunadamente, en casi cada uno de estos casos, hay maneras de simplemente extender las vistas genéricas para manejar un conjunto más amplio de casos de uso. Estas situaciones normalmente corresponden a varios patrones comunes que se explican en las próximas secciones.

9.3.1. Crear contextos de plantilla personalizados

Tal vez hayas notado que el ejemplo de la plantilla publisher list almacena todos los books en una variable llamada object_list. Aunque que esto funciona bien, no es algo sencillo para los autores de plantillas: ellos sólo tienen que "saber" aquí que están trabajando con books. Un nombre mejor para esa variable sería publisher_list; el contenido de esa variable es bastante obvio.

Podemos cambiar el nombre de esa variable fácilmente con el argumento template_object_name:

publisher_info = {
    "queryset" : Publisher.objects.all(),
    "template_object_name" : "publisher",
}
 
urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info)
)

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

9.3.2. Agregar un contexto extra

A menudo simplemente necesitas presentar alguna información extra aparte de la proporcionada por la vista genérica. Por ejemplo, piensa en mostrar una lista de todos los otros publisher en cada página de detalle de un publisher. La vista genérica object_detail provee el publisher al contexto, pero parece que no hay forma de obtener una lista de todos los publishers en esa plantilla.

Pero sí la hay: todas las vistas genéricas toman un parámetro opcional extra, extra_context. Este es un diccionario de objetos extra que serán agregados al contexto de la plantilla. Por lo tanto, para proporcionar la lista de todos los publishers en la vista de detalles, usamos un diccionario info como el que sigue:

publisher_info = {
    "queryset" : Publisher.objects.all(),
    "template_object_name" : "publisher",
    "extra_context" : {"book_list" : Book.objects.all()}
}

Esto llenaría una variable {{ book_list }} en el contexto de la plantilla. Este patrón puede ser usado para pasar cualquier información hacia la plantilla para la vista genérica. Es muy práctico.

Sin embargo, en realidad hay un error sutil aquí — ¿puedes detectarlo?

El problema aparece cuando las consultas en extra_context son evaluadas. Debido a que este ejemplo coloca Publisher.objects.all() en la URLconf, sólo se evaluará una vez (cuando la URLconf se cargue por primera vez). Una vez que agregues o elimines publishers, notarás que la vista genérica no refleja estos cambios hasta que reinicias el servidor Web (mira "Almacenamiento en caché y QuerySets" en el Apéndice C para mayor información sobre cuándo los QuerySets son almacenados en la cache y evaluados).

Este problema no se aplica al argumento queryset de las vistas genéricas. Ya que Django sabe que ese QuerySet en particular nunca debe ser almacenado en la caché, la vista genérica se hace cargo de limpiar la caché cuando cada vista es renderizada.

La solución es usar un callback en extra_context en vez de un valor. Cualquier callable (por ejemplo, una función) que sea pasado a extra_context será evaluado cuando su vista sea renderizada (en vez de sólo la primera vez). Puedes hacer esto con una función explícitamente definida:

def get_books():
    return Book.objects.all()
 
publisher_info = {
    "queryset" : Publisher.objects.all(),
    "template_object_name" : "publisher",
    "extra_context" : **{"book_list" : get_books}**
}

o puedes usar una versión menos obvia pero más corta que se basa en el hecho de que Publisher.objects.all es en sí un callable:

publisher_info = {p
    "queryset" : Publisher.objects.all(),
    "template_object_name" : "publisher",
    "extra_context" : **{"book_list" : Book.objects.all}**
}

Nota la falta de paréntesis después de Book.objects.all; esto hace referencia a la función sin invocarla realmente (cosa que hará la vista genérica luego).