DEV Community

David López
David López

Posted on

Django: Render HTML as PDF to the browser

The main part of every business application or CRM is to print out some documents like reports, invoices, etc and PDF is the most popular format. As this being something that usual, it is also surprising how complicated can be sometimes to perform such a task.

I have been involved in a small Django project, where definitely printing PDF documents was required as basic functionality. After a little of search, I found a Python adaption of an already known library from my days in PHP world,FPDF , and in this case, PyFDPF is the one that I used.

But let us don't miss more time and get our hands dirty so to produce a PDF file in Django and display it in the browser.

First let's install the library, this can we done using pip:

pip install fpdf

but it will be better including it the requirements.txt, as recommended by the Best Practices for Dependency Management:

fpdf==1.7.2

We will create a template file, which it will contain the HTML to be rendered in the pdf. I like to place these files in a specific folder and therefore keep my templates organized.

.
└── main
|    └── ...
|    └── urls.py
└── my_app
     └── ...
     └── pdfs
     └── templates
          └── __init__.py
          └── pdf
               └── example.html

Below you can find an example of HTML code that I picked up from the document of the library and to test the elements. Including the Django built-in static template tag allows us to include pictures from your static folder in the HTML.

{% load static %}

<H1 align="center">Example</H1>
<h2>Basic usage</h2>
<p>You can now easily print text mixing different
    styles : <B>bold</B>, <I>italic</I>, <U>underlined</U>, or
    <B><I><U>all at once</U></I></B>!<BR>You can also insert links
    on text, such as <A HREF="http://www.fpdf.org">www.fpdf.org</A>,
    or on an image: click on the logo.<br>
    <h3>Sample List</h3>
    <ul>
        <li>option 1</li>
        <ol>
            <li>option 2</li>
        </ol>
        <li>option 3</li>
    </ul>

    <table border="0" align="center" width="50%">
        <thead>
            <tr>
                <th width="30%">Header 1</th>
                <th width="70%">header 2</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>cell 1</td>
                <td>cell 2</td>
            </tr>
            <tr>
                <td>cell 2</td>
                <td>cell 3</td>
            </tr>
        </tbody>
    </table>
</p>

So we are ready to create the method to render that HTML in a PDF document and deliverit to the browser for visualization, wrapping it into an HTTPResponse.

from django.shortcuts import HttpResponse
from django.template.loader import get_template, render_to_string

from fpdf import FPDF, HTMLMixin


class HtmlPdf(FPDF, HTMLMixin):
    pass


def print_pdf(request):    
    pdf = HtmlPdf()
    pdf.add_page()
    pdf.write_html(render_to_string('pdf/example.html'))

    response = HttpResponse(pdf.output(dest='S').encode('latin-1'))
    response['Content-Type'] = 'application/pdf'

    return response

Creating a class with the objects FPDF and HTMLMixin will make the trick so that the write_html method can be used within the FPDF logic. This method requires a string containing the HTML code, therefore, we use Django's render_to_string to convert into a string the Template object open by the loader.

As the documentation states, when using Python 3.x you need to add

.encode('latin-1')

to the method output in order to render correctly.

We are just missing a way to call print_pdf and we can do that through a new entry in the urls.py

from django.urls import path

from myapp import views as web_views

urlpatterns = [
    path(
        "pdf/example",
        web_views.print_pdf,
        name="print_pdf",
    ),
]

Well if you have arrived this far and have a running Django instance, for example using Django's functionality to run a server, you will be able to access the pdf calling:

http://127.0.0.1:8000/pdf/example

As said before this is a basic example that can used to render a HTML into a PDF and display it in the browser, however, it is possible to save the file locally, just changing the pdf.output syntaxis:

target = os.path.join(BASE_DIR, 'my_app', 'pdfs')
pdf.output(target + '/example.pdf', 'F')

Notes:

You still have a long journey to create a full pdf file that can be presented as a report from your application, for that reason I would suggest you to dive into the documentation of PyFDPF to discover more functionality.

Top comments (0)