Ir al contenido principal

Reportes con Django y RerportLab

En este pequeño tutorial, voy a presentar como realizar un reporte en pdf utilizando ReportLab en Django, para ello vamos a empezar describiendo un pequeño caso de uso muy común en muchos proyectos, que es obtener un listado en forma de tabla de los registros de una base de datos.

Supongamos que tenemos una tabla Persona con los siguientes campos:

from django.db import models

class Persona(models.Model):
    apellido = models.CharField(
        max_length=50
    )
    nombre = models.CharField(
        max_length=50
    )
    dni = models.PositiveIntegerField()
    telefono = models.CharField(
        max_length=25
    )
    email = models.EmailField(
        max_length=75
    )

Guiándonos de la documentación oficial de Django creamos la siguiente vista la cual llama al método run de la clase ReportePersonas que es la que contiene la lógica que devuelve como respuesta a nuestra petición el archivo pdf.

Esta vista le asigna la directiva 'application/pdf' al objeto response que le indica al navegador que es un archivo pdf y el header 'Content-Disposition' con el nombre de tal.

referencia a Django Docs
from django.shortcuts import render
from django.http import HttpResponse

from .models import Persona
from .reportes import ReportePersona

def reporte_personas(request):
    response = HttpResponse(content_type='application/pdf')
    response['Content-Disposition'] = 'attachment; filename="personas.pdf"'
    r = ReportePersona()
    response.write(r.run())
    return response

A continuación se describe la clase que genera el reporte en pdf:

from io import BytesIO

from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, TA_CENTER
from reportlab.lib.units import inch, mm
from reportlab.lib import colors
from reportlab.platypus import (
        Paragraph, 
        Table, 
        SimpleDocTemplate, 
        Spacer, 
        TableStyle, 
        Paragraph)

from .models import Persona

class ReportePersona(object):

    def __init__(self):
        self.buf = BytesIO()

    def run(self):
        self.doc = SimpleDocTemplate(self.buf)
        self.story = []
        self.encabezado()
        self.crearTabla()
        self.doc.build(self.story, onFirstPage=self.numeroPagina, 
            onLaterPages=self.numeroPagina)
        pdf = self.buf.getvalue()
        self.buf.close()
        return pdf

    def encabezado(self):
        p = Paragraph("Reporte Personas", self.estiloPC())
        self.story.append(p)
        self.story.append(Spacer(1,0.5*inch))

    def crearTabla(self):
        data = [["Apellido","Nombre","DNI","Telefono","email"]] \
            +[[x.apellido, x.nombre, x.dni, x.telefono, x.email] 
                for x in Persona.objects.all()]

        style = TableStyle([
            ('GRID', (0,0), (-1,-1), 0.25, colors.black),
            ('ALIGN',(0,0),(-1,-1),'CENTER'),
            ('VALIGN',(0,0),(-1,-1),'MIDDLE')])

        t = Table(data)
        t.setStyle(style)
        self.story.append(t)

    def estiloPC(self):
        return ParagraphStyle(name="centrado", alignment=TA_CENTER)

    def numeroPagina(self,canvas,doc):
        num = canvas.getPageNumber()
        text = "Pagina %s" % num
        canvas.drawRightString(200*mm, 20*mm, text)

ReportePersona es una clase Python que contiene los métodos agrupados de tal manera de hacer el código mas desacoplado y legible, en el constructor __init__ se crea un buffer que servirá como archivo temporal durante el tiempo que se genera el documento pdf, esto es deseable debido a que nos encontramos en un entorno web donde la eficiencia del código debe ser un requisito fundamental. Ademas el uso del buffer nos permite que el documento se pueda descargar a nuestro sistema de archivos y no se almacene en el servidor, el cual es el comportamiento por defecto de la clase SimpleDocTemplate si se pasa como parámetro una cadena con el nombre del archivo.

El método encabezado agrega un párrafo centrado y a continuación un espaciador de 1.3cm.

El método crearTabla genera una lista de listas por comprensión que se utiliza para llenar la tabla.

Finalmente el método numeroPagina se encarga de agregar una cadena con el número de cada página.

Sin mas que mencionar espero que estos pequeños snippets les sean útiles.

Reportlab Docs

Comentarios