Es común cuando se desarrollan aplicaciones web que se siga un patrón donde el usuario accede a una lista general de objetos para luego ver uno por uno en detalle. Un ejemplo claro son los blogs (como éste). El usuario accede a una lista general de entradas recientes donde puede ver el título, fecha de publicación y una pequeña descripción de cada una. Cuando hace clic sobre una de ellas, es llevado a la vista detallada donde tiene acceso a los detalles completos de un objeto a la vez. Otros ejemplos pueden ser: tiendas en línea, álbumes de fotos, portafolios, etc. Django provee *[class based generic views](https://docs.djangoproject.com/en/1.5/topics/class-based-views/generic-display/)* (vistas genéricas basadas en clases, que abreviaremos como *CBGV* de ahora en adelante) para hacer el desarrollo de aplicaciones que sigan este y otros patrones más rápido y menos monótono. Una de las *CBGV* que uso con mayor frecuencia es *[ListView](https://docs.djangoproject.com/en/1.5/ref/class-based-views/generic-display/#listview)*, que se encarga de mostrar una lista de objetos pertinentes a un *model* en concreto. La configuración más básica requiere únicamente definir el *model* que se quiere mostrar como un atributo de la *CBGV*. Digamos que queremos escribir una *view* para un *model* llamado `Book`, podemos definirla así: :::python #views.py from django.views.generic import ListView from .models import Book class BookList(ListView): model = Book Sin embargo, a medida que la lista de objetos crece, resulta ineficiente mostrar una lista de todos ellos a la vez. Por ejemplo, si `BookList` devuelve 500 `Book`, no queremos mostrarlos todas de una vez. Es allí donde entra el concepto general de [paginación](http://en.wikipedia.org/wiki/Pagination). Así, en vez de mostrar 500 entradas a la vez, podemos mostrar páginas de 10 entradas (puede ser cualquier número), y proveer enlaces para que el usuario vea más entradas si lo desea. Las *CBGV* soportan la paginación por defecto, y en nuestro caso solo requieren definir el número de objetos que queremos mostrar por página. :::python class Book(ListView): model = Book paginate_by = 10 Ahora cada vez que llamemos a la *view* nos devolverá 10 `Book` a la vez. Esto podría ser suficiente para algunos, pero si seguimos con nuestro ejemplo, nos damos cuenta que no se escala muy bien. 500 entradas repartidas en páginas de 10 entradas cada una, resulta en 50 paginas. Significa que el usuario deben dar clic decenas de veces en "siguiente página" para navegar por las entradas si quisiera llegar a la página 40, por ejemplo. O si queremos que nuestro sitio muestre enlaces a todas las páginas, debemos mostrar una numeración del 1 al 50; lo cual francamente se ve horrible. [Mezzanine](http://mezzanine.jupo.org) incluye una función en sus utilidades que solo muestra cierta cantidad de enlaces de paginación. Esta cantidad es configurable a través de `settings.MAX_PAGING_LINKS`. Por ejemplo, con `MAX_PAGING_LINKS = 10`, se mostrarán hasta 10 enlaces de paginación, por lo que el usuario tendría siempre accesible 5 páginas hacia adelante y 5 hacia atrás. Este es el caso por defecto, y el que usa Mezzanine en su aplicación de blogs. La cuestión ahora es usar la paginación de Mezzanine con nuestras propias *CBGV*. Para ello tenemos a nuestra disposición el método `paginate_queryset()`, que está disponible en todas las clases que heredan de `MultipleObjectMixin`. La [documentación](https://docs.djangoproject.com/en/1.5/ref/class-based-views/mixins-multiple-object/#django.views.generic.list.MultipleObjectMixin.paginate_queryset) nos dice que recibe como argumentos `queryset` y `page_size`. El método debe retornar un *tuple* de cuatro elementos: `(paginator, page, object_list, is_paginated)`. Analicemos todos estos parámetros - `queryset` es el *queryset* que queremos paginar. En este caso `ListView` lo computa a partir del atributo `model`, por lo que es `Book.objects.all()`. - `page_size` indica el número de objetos que queremos en cada página. Esto es igual a lo que definimos en `paginate_by`, es decir 10. - `paginator` debe ser una instancia de la clase `Paginator` de Django. - `page` debe ser una instancia de la clase `Page` de Django. - `object_list` debe ser una lista con los objetos que se van a mostrar. En nuestro caso, 10 instancias de `Book`. - `is_paginated` es un *boolean* que indica si la paginación es necesaria. Si la base de datos solo contiene 5 `Book` no será necesario usar paginación, porque son menos de 10 y todos caben en una sola página. Ahora lo único que debemos hacer es llamar a la función `paginate()` de Mezzanine para que pueble los parámetros de retorno de `paginate_queryset()`. Una mirada rápida al [código fuente](https://github.com/stephenmcd/mezzanine/blob/master/mezzanine/utils/views.py#L120) de `paginate()` nos indica qué argumentos debemos pasar al llamar la función: `paginate(objects, page_num, per_page, max_paging_links)`. - `objects` son los objetos que queremos paginar. Le pasamos directamente `queryset`. - `page_num` indica el número de página a mostrar. Este se obtiene como un parámetro `GET` de la URL. Le pasamos `request.GET.get("page", 1)`. Esto le dice a la función que si el parámetro `page` no está presente, nos devuelva la página 1. - `per_page` indica cuantos objetos deben incluirse por página. Le pasamos directamente `page_size`. - `max_paging_links` indica cuantos enlaces de paginación han de mostrarse a la vez. Como dijimos antes, esto lo obtenemos de `settings.MAX_PAGING_LINKS`. `paginate()` devuelve una instancia de `Page`, por lo que nos provee del segundo elemento de nuestra *tuple* de respuesta. A partir de allí podemos obtener la instancia de `Paginator` como `page.paginator`; la lista de objetos paginados como `page.object_list` y el indicador de paginación como `page.has_other_pages`. Al final, nuestra *view* nos queda así: :::python #views.py from django.views.generic import ListView from mezzanine.conf import settings from mezzanine.utils.views import paginate from .models import Book class BookList(ListView): model = Book paginate_by = 10 settings.use_editable() def paginate_queryset(self, queryset, page_size): page = paginate(queryset, self.request.GET.get("page", 1), page_size, settings.MAX_PAGING_LINKS) return (page.paginator, page, page.object_list, page.has_other_pages) Lo único que resta es crear los enlaces de paginación en nuestro *template*. Mezzanine cuenta también con un *template tag* para eso: `pagination_for`. Esto genera los enlaces de paginación usando la plantilla `includes/pagination.html`. Hacemos la llamada pasándole como único parámetro una instancia de `Page`, que en nuestro *context* se llama `page_obj`. {% pagination_for page_obj %} Con eso hemos logrado implementar la paginación de Mezzanine con una *CBGV* escrita por nosotros.