Paginando con Django

En este tutorial se muestra cómo insertar paginación con Django y el framework Bootstrap mediante un sencillo ejemplo.

Paginación mostrando máximo de páginas

En este pequeño tutorial voy a explicar como hacer paginación con Django y Bootstrap.

La vista y el modelo

Por un lado vamos a crear un modelo de datos llamado items, para este ejemplo:

proyecto/website/models.py

class Item(models.Model):
   name = models.CharField(max_length=128)
 
   class Meta:
        verbose_name = "Item"
        verbose_name_plural = "Items"

Y la vista que cargará todos los ítems paginados:

proyecto/website/views.py

from website.models import *
from django.http import HttpResponse
from django.template import RequestContext, loader
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
 
def index(request):
    template = loader.get_template('website/index.html')
    #_inicializar_items()
    itemsquery = Item.objects.all()
 
    paginator = Paginator(itemsquery, 10)
    page = request.GET.get('page')
 
    try:
        items = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
        items = paginator.page(1)
    except EmptyPage:
        # If page is out of range (e.g. 9999), deliver last page of results.
        items= paginator.page(paginator.num_pages)
 
    context = RequestContext(request, {
        'meta_description': '',
        'meta_keywords': '',
        'items':items,
    })
 
    return HttpResponse(template.render(context))

Explico un poco lo que hace la vista.
Como podéis ver, se utiliza la clase paginator, que es la clase que nos ayuda a paginar datos. Podéis ver más detalles en la documentación oficial de Django.

Cuando creo el objeto Paginator le paso el resultado de la query y el número de elementos que quiero por página:

  paginator = Paginator(itemsquery, 10)

Capturo el parámetro page en la vista , que en nuestro caso lo enviamos y capturamos como parámetro GET.

page = request.GET.get('page')

Una vez lo tenemos, con la llamada a la función page dentro de la clase paginator, podemos cargar los datos de la página que indiquemos como parámetro de entrada:

items = paginator.page(page)

Esta línea , guarda los elementos de la página en el objeto items.paginator

A la plantilla le pasamos los ítems resultantes de la paginación.

   context = RequestContext(request, {
        'meta_description': '',
        'meta_keywords': '',
        'items':items,
    })

La plantilla para paginar

<ul>
    {% for item in items %}
        <li>{{ item.name }}</li>
    {% endfor %}
</ul>

{% if items.has_next or items.has_previous %}


    <ul class="pagination">
        {% if items.has_previous %}
            <li><a href="?page={{ items.previous_page_number }}" class="btn btnpages"><i
                    class="glyphicon glyphicon-chevron-left"></i></a></li>
        {% endif %}
        {% for page in items.paginator.page_range %}
            <li class="{% if items.number == page %}active{% endif %}">
                <a class="btn btnpages" href="?page={{ page }}">{{ page }}</a></li>
        {% endfor %}

        {% if items.has_next %}
            <li>
                <a href="?page={{ items.next_page_number }}" class="btn btnpages">
                    <i class="glyphicon glyphicon-chevron-right"></i>
                </a>
            </li>
        {% endif %}
    </ul>



{% endif %}

Como podéis ver, aquí lo único que hacemos es poner la típica flecha izquierda o derecha si hay página anterior o posterior,
y recorremos los elementos items.paginator , que es donde se almacenan los ítems de la página actual que cargamos en la vista :

items.paginator

Si cargáis el css de Bootstrap, el resultado debería ser el siguiente:

paginación con Django

Paso también un método que pongo en la vista para inicializar los items, tan sólo hay que llamarlo al principio de la vista index para inicializar estos valores de ejemplo:

proyecto/website/views.py

def _inicializar_items():
    Item.objects.all().delete()
    for n in range(100):
        item = Item(
            name='item_'+str(n)
        )
        item.save()

Bien, esta ha sido una primera aproximación a cómo funciona la paginación con Django.

Esto debería funcionar correctamente, pero hay un problema: si van creciendo el número de páginas, este proceso no se encarga de limitar el número de páginas que se muestra en el html.

Esto lo soluciono con los filters de Django, como explico en el siguiente punto.

Creando un filtro para limitar el número de páginas

En este punto, lo que vamos a hacer es crear un filtro que limite la lista de páginas de items.paginator.range.

Para ello, creamos un archivo de filtros en la carpeta donde tenemos nuestra aplicación llamada tamplatetags con el siguiente filtro:

proyecto/website/templatetags/navigation_filters.py

from django import template
register = template.Library()
@register.filter(name='page_filter')
def page_filter(self,items):
    current_value = items.number
    valmax = items.paginator.page_range[-1]
    gapval = 4
    valini = current_value-gapval

    if valini <=0:
        valini = 1
    
    valend = 1+current_value+gapval
    if valend > valmax:
        valend = valmax+1

    return range(valini, valend)

Este filtro hace algo muy simple. A partir de la lista de items:

  • Obtiene el valor de la página actual y le suma 4, y les resta 4 para encontrar el valor mayor y el menor del rango de páginas.
    (en nuestro caso lo ponemos como un valor entero en el filtro, también lo podríamos pasar como parámetro).
    4 por arriba+4 por abajo+página actual son los enlaces de páginas que queremos mostrar como máximo
  • Con los condicionales posteriores simplemente corregimos por si el valor menor nos ha resultado menor que 1, o por si el valor máximo sobrepasa el número de páginas (si es así le asignamos la última página)

Para aplicar el filtro a nuestro rango de páginas simplemente hacemos esto:

Cargamos el archivo de filtros en la plantilla:

{% load navigation_filters %}

Y limitamos el rango con el filtro page_filter al que le pasamos los items (y el iterador de paginación)

{% for page in items.paginator.page_range|page_filter:items %}

El resultado de la plantilla es:

proyecto/website/templates/index.html

{% block content %}
    {% load navigation_filters %}
    <ul>
        {% for item in items %}
            <li>{{ item.name }}</li>
        {% endfor %}
    </ul>

    {% if items.has_next or items.has_previous %}
        <ul class="pagination">
            {% if items.has_previous %}
                <li><a href="?page={{ items.previous_page_number }}" class="btn btnpages"><i
                        class="glyphicon glyphicon-chevron-left"></i></a></li>
            {% endif %}
            {% for page in items.paginator.page_range|page_filter:items %}
                <li class="{% if items.number == page %}active{% endif %}">
                    <a class="btn btnpages" href="?page={{ page }}">{{ page }}</a></li>
            {% endfor %}

            {% if items.has_next %}
                <li>
                    <a href="?page={{ items.next_page_number }}" class="btn btnpages">
                        <i class="glyphicon glyphicon-chevron-right"></i>
                    </a>
                </li>
            {% endif %}
        </ul>
    {% endif %}
{% endblock %}

Paginación mostrando máximo de páginas

Espero que os haya servido de ayuda.

Acerca del autor : Ivan Díaz

Ivan Díaz

Programador web afincado en Barcelona

2 opiniones en “Paginando con Django”

  1. perdone las molestias, si tengo un filtrador, y filtro mis productos, en el resultado del filtrado me trae algunos productos, y presiona el botón 2 del paginado, carga la pagina y el paginador se pierde como le podría hacer?

    1. En este caso deberías buscar una estrategia para que haya persistencia entre navegación de páginas, no solo para la paginación sinó también para los filtros.
      En las búsquedas, yo suelo utilizar parámetros GET,que cargo en la url en función de los filtros seleccionados, página en la que estás,… mediante Javascript (esto formaría parte de otro tutorial, aunque es un buen tema que me apunto para más adelante)
      En este caso, en el paginado, al hacer click sobre un elemento de página debería llamar a una función Javascript que agregase a tu_url…?pag=3 , si por ejemplo el usuario hace click sobre página 3. Deberías eliminar el enlace href=… de la paginación, para que no te salte a otra página, antes de que hayas formado la url con Javascript.
      Para el resto de filtros deberías hacer lo mismo, así obtendrías un resultado tipo htttp://tu_url?pag=3&filtro1=1&filtro2=2.
      Cuando el Javascript tiene cargados todos los parámetros en una url, tan sólo deberías recargar la página con esa url formada.
      La vista de Django, en este caso, deberá cargar los items en función de estos parámetros pasados: en función de los parámetros de filtros y de paginación.

Responder a cesar silva jimenez Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *