DEV Community

Cover image for Crear PDF a partir de HTML y Python.

Crear PDF a partir de HTML y Python.

Lo admito. En mis más de 10 años en el rubro de la programación –bello y estresante a la vez-, si hay algo a lo que siempre le he buscado la vuelta es a construir documentos en PDF. Buscando alternativas en mis tiempos libres, encontré una forma más amigable, que parte de un archivo HTML, para crear dichos documentos, usando Flask, wkhtmltopdf y pdfkit en Python.

Python es uno de los lenguajes más usados en la actualidad, tanto por su performance, como su facilidad de uso y abundantes librerías. Aun así, para crear PDF, me encontraba con la misma dificultad de otros lenguajes Open Source como php: había que hacer todo a mano. Claro, aparte de ser un trabajo de largo aliento, los resultados de por sí, no eran del todo satisfactorios.

Sin embargo, gracias a la magia de Internet y la infinidad de recursos que puedes encontrar, podemos crear PDF a partir de un HTML, el cual, se genera mediante linea de comandos con el paquete estándar wkhtmltopdf. Para ellos hay que instalarlo de la siguiente manera:

# En Windows, usamos chocolatey
 choco install wkhtmltopdf
 # EN Linux, el gesto de paquetes correspondiente a la distro. En este caso Ubuntu
 apt install wkhtmltopdf
Enter fullscreen mode Exit fullscreen mode

En python Flask, micro framework para el desarrollo de aplicaciones web, incluiremos el paquete pdfkit, el cual servirá de “puente” entre wkhtmltopdf y la aplicación.

 pip install pdfkit
Enter fullscreen mode Exit fullscreen mode

La vista.

Creamos un vista html, usando los marcadores de Jinja, para el paso de los parámetros.

<div class="mb-10">
        <div class="p-4 text-center" style="float: right; border:#ff0000 solid 6px">
            <h1 style="text-align: center;"><span style="color: #ff0000; font-weight: bold;">{{ documento }}</span></h2>
            <h1 style="text-align: center;"><span style="color: #ff0000; font-weight: bold;">N&deg; {{ numdocu }}</span></h2>
        </div>
        <div>
            {% if logo %}
                <img src="{{ request.url_root }}static/img/logos/{{ logo }}" alt="logo">
            {% else %}
                <img src="{{ request.url_root }}static/img/simplerp/loader.png" sizes="50" alt="logo">
            {% endif %}
        </div>
        <div class="mt-3">
            <strong>{{ rutemisor }}</strong><br>
            <strong>{{ razonsocial }}</strong><br>
            <strong>{{ direccion }}</strong><br>
            <strong>{{ telefono }}</strong></p>
        </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

Como se puede ver, los valores encerrados en {{}} serán los parámetros que el controlador le pasará a la vista una vez procesados los datos desde el Modelo.

if(e.target.closest('.download')) {
            let el = e.target.closest('.download')
            let tipo = el.getAttribute('data-tipo')
            let emisor = el.getAttribute('data-empresa')
            let id = el.getAttribute('data-id')

            let params = JSON.stringify({tipo : tipo, empresa : emisor, idocu : id})
            cargaLoader()

            fetch('/generarpdf', {
                method : 'POST',
                headers : {
                    'Content-type' : 'application/json',
                    'Accept' : 'application/json'
                },
                body : params
            })
            .then(response => {
                cierraLoader()
                if (response.ok) {
                    return response.blob();
                } else {
                    return response.json().then(data => {
                        throw new Error(data.msg);
                    })
                }
            })
            .then(blob => {
                cierraLoader()
                const url = window.URL.createObjectURL(blob)
                document.getElementById('verpdf').src = url
                modalPdf.show()
            })
            .catch(error => {
                cierraLoader()
                console.log(error.message)
                alerta(3, 'Ocurrio un error al intentar generar el documento.')
            })
        }
Enter fullscreen mode Exit fullscreen mode

La función Javascript descrita aquí está en un botón de la lista, que genera el documento. Aquí interactuamos con con el controlador, usando Fetch API, para procesar de forma asíncrona la información.

Controlador

En el controlador, se procesan los datos que el response del Modelo entrega. Me centraré en la generación del documento.

data = {
                'documento' : documento,
                'numdocu' : numdocu,
                'rutemisor' : rutemisor, 
                'razonsocial' : razon_social, 
                'direccion' : direccion, 
                'telefono' : telefono, 
                'rutreceptor' : rutreceptor, 
                'razonreceptor' : razonreceptor, 
                'direceptor' : direceptor, 
                'telreceptor' : telreceptor,
                'fecha' : fecha,
                'nota' : nota,
                'items' : items,
                'neto' : Utils.agregaPuntos(sumatotal),
                'iva' : Utils.agregaPuntos(iva),
                'total' : Utils.agregaPuntos( total)                 
            }

            renderizado = render_template('empresa/documentos/plantilladocu.html', **data)

            opciones = {
                'page-size' : 'A4',
                'encoding' : 'UTF-8',
                'user-style-sheet' : 'static/css/pdf.css'
            }
           return response
Enter fullscreen mode Exit fullscreen mode

El diccionario data, asocia a una clave, al valor que viene desde el Modelo ya procesado. En la siguiente línea, renderizamos el template de ejemplo y le entregamos las opciones a usar, en este caso, un tamaño de papel A4 y codificado en UTF-8, que es el estándar universal.

pdf = pdfkit.from_string(renderizado, False, options=opciones)
            salida = uploads['UPLOAD_DOCUMENTOS'] + '/' + str(documento) + '-' + str(numdocu) + '.pdf'

            response = make_response(pdf)
            response.headers['Content-type'] = 'application/pdf'
            response.headers['Content-Disposition'] = f'inline; filename={salida}'
Enter fullscreen mode Exit fullscreen mode

En las siguientes líneas, generamos el pdf. El método from_string, toma el HTML renderizado con los parámetros ya pasados a la vista y las opciones generales. El valor False, va por defecto.

Al final en el último headers del response, agregamos inline, para que no fuerce la descarga.
El resultado es el siguiente.

Resumiendo: si bien crear documentos PDF con librerías tipo FPDF y otras, es una buena alternativa, a veces hay que ahorrar tiempo, y si tienes herramientas que permitan trabajar de forma óptima y los resultados son ídem, bien vale conocerlas. Un saludo a todos.

Top comments (0)