Paginando con Django

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

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.

Crear e imprimir datos en estructura de árbol con Django

Cómo crear e imprimir por pantalla los datos contenidos en una estructura de datos de árbol. Ejemplo aplicado a mostrar carpetas.

En este post voy a explicar cómo crear y mostrar por pantalla un árbol de carpetas con Django, ya que es un problema que me he encontrado en un proyecto reciente, y puede que a alguien le sirva de ayuda.

Este ejemplo es aplicable a cualquier estructura de árbol, aunque yo lo voy a aplicar a una estructura de carpetas.

El primer paso es crear el modelo:

models.py

from django.db import models
 
class Folder(models.Model):
   name = models.CharField(max_length=128)
   parent = models.ForeignKey("self",null=True,related_name='children')
 
   class Meta:
        verbose_name = "Carpeta"
        verbose_name_plural = "Carpetas"

El atributo parent es el que utilizo como su propio nombre indica para indicar que carpeta es la carpeta padre.
Esta clave foránea se referencia a su propio modelo, que se indica con el valor “self” .
Como nombre relacionado pongo “children”, esto me servirá para obtener todas las carpetas que tengan como clave foránea la carpeta padre.
Esta clave foránea sólo será nula cuando nos refiramos a la carpeta raíz.

Una vez creado el modelo, vamos a la vista:

views.py

def folders(request):
    template = loader.get_template('website/folders.html')
    folders = Folder.objects.filter(parent = None)
 
    context = RequestContext(request, {
        'folders':folders,
    })
 
    return HttpResponse(template.render(context))

En la vista cargamos en la variable folders todas las carpetas que sean raíz y las pasamos a la plantilla.

Os añado una función inicializar para probarlo con un ejemplo:

views.py

def _inicializar():
    Folder.objects.all().delete()
    folder_raiz = Folder(
        name = 'Raiz'
    )
    folder_raiz.save()
    folder = Folder(
        name = 'Docs',
        parent=folder_raiz
    )
    folder.save()
    folder_trab = Folder(
        name = 'Trabajo',
        parent=folder
    )
    folder_trab.save()
 
    folder = Folder(
        name = 'Música',
        parent=folder_raiz
    )
    folder.save()
    folder_photos = Folder(
        name = 'Fotos',
        parent=folder_raiz
    )
    folder_photos.save()
    folder = Folder(
        name = 'Vacaciones',
        parent=folder_photos
    )
    folder.save()
    folder = Folder(
        name = 'Trabajo',
        parent=folder_photos
    )
    folder.save()
 
def folders(request):
    _inicializar()
    template = loader.get_template('website/folders.html')
    folders = Folder.objects.filter(parent = None)
 
 
    context = RequestContext(request, {
        'folders':folders,
    })
 
    return HttpResponse(template.render(context))

Finalmente nos falta la plantilla.

Lo que he hecho básicamente es ejecución recursiva con la propia plantilla.

La plantilla folders.html:

<div id="jstree_cats">
    {% include "website/parts/tree_cats.html" %}
</div>

Y una plantilla en /templates/parts/folders.html:

<ul>
    {% for folder in folders %}
        <li id="child_node-{{ folder.pk }}">{{ folder.name }}
        {% if folder.children.exists %}
            {% with folder.children.all as folders %}
                {% include "website/parts/tree_cats.html" %}
            {% endwith %}
        {% endif %}
        </li>
    {% endfor %}
</ul>

Lo que hacemos aquí es recorrer la lista de carpetas a partir de las carpetas raíz.

Para cada carpeta, consulta las carpetas hijas, y si existen hijas, recorre la lista de carpetas y repite el proceso para cada una.

Gracias a que habíamos especificado en el campo “parent” de la carpeta el atributo “related-name” como children, podemos hacer la consulta directamente con:

folder.children.all

(sinó también podemos buscar los relacionados con “folder.parent_set.all()”, pero me parece más claro hacerlo de esta forma)

El resultado que obtenemos es el siguiente:

Lista de carpetas

Finalmente, vamos a darle un poco de formato y la capacidad de navegar por las carpetas (desplegar, seleccionar carpeta).

He utilizado esta librería (requiere jquery):
https://www.jstree.com/

Así pues, le añado al <head> jquery, y jstree de para que me cargue estas librerías:

base.html

{% load staticfiles %}
<!DOCTYPE html>
<html lang="es_ES">
<head>

    <title>Tuto | Programante.com </title>
    <meta charset="UTF-8">


    {% block headcontent %}
        <link rel="stylesheet" type="text/css" href="{% static 'js/libs/vakata-jstree/dist/themes/default/style.min.css' %}">
        <script src='{% static 'js/libs/jquery/jquery-2.0.3.min.js' %}'></script>
        <script src='{% static 'js/libs/vakata-jstree/dist/jstree.min.js' %}'></script>


        <script type="text/javascript">
            $(function () {
                $('#jstree_cats').jstree();
            });
        </script>
    {% endblock %}
</head>
<body>
<div id="wrapper_page">
    {% block content %}
    {% endblock %}
</div>
</body>
</html>

y hago que la plantilla folders.html herede de base.html, para añadir el contenido del <head>:

{% extends "base.html" %}
{% block content %}
<div id="jstree_cats">
    {% include "website/parts/tree_cats.html" %}
</div>
{% endblock %}

El resultado final debería ser este:

crearcarpetas_django2

Junto con la librería “jstree” podríamos cargar estas carpetas con JSON, borrar carpetas, mostrar archivos de las carpetas seleccionadas…

Pero bueno, esto ha sido un introducción para poder resolver el problema de mostrar elementos con almacenados en una estructura de árbol.

Espero que a alguien le sirva de ayuda.

Aquí dejo los archivos: