Generate PDF from generated HTML in Django

Not as easy as you think. There’re quite a few packages available for that, and yet!

Install packages:

pip install pdfkit
sudo apt install wkhtmltopdf

Say, you want to print a listing of holidays local to Moscow and give out to people on the street. Say, there’s a model.py like that:

class Holiday(models.Model):
    summary = models.TextField()
    date = models.DateField()

class City(models.Model):
    name = models.CharField(max_length=100)
    holidays = models.ManyToManyField(Holiday)

Let’s be clear, this is not an ideal structure, it highly depends on what your app is about. But for the sake of a simple example, let’s proceed.

Then, you will need a view to generate HTML and get PDF. In views.py craft a view function for that:

import pdfkit
from io import BytesIO

from django.template.loader import get_template
from django.http import HttpResponse

city_name = 'Moscow'
template_src = 'report/test.html'

context = {
    'city': city_name,
    'holidays': [
        {'summary': x.summary, 'date': x.date} for x in
        City.objects.get(name=city_name).order_by('date')
    ]
}

def get_pdf(request):
    template = get_template(template)
    html = template.render(context)
    options = {
        'page-size': 'A4',
        'margin-top': '0in',
        'margin-right': '0in',
        'margin-bottom': '0in',
        'margin-left': '0in',
        'encoding': "UTF-8",
        'no-outline': None
    }
    pdf = pdfkit.from_string(html, False, options)
    response = HttpResponse(pdf, content_type='application/pdf')
    return response

Register it somewhere in urls.py and enjoy. You may want to have the page itself to change styles in browser’s Developer Mode. So make a view, add it to the bottom of views.py and and register it as well:

from django.shortcuts import render

def listing(request):
    return render(request, template, context)

It is a standard view. For HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Holidys for {{ name }} city</title>
</head>
<body>
<div>
    <h2>Holidys for {{ name }} city</h2>
    <p>Be prepared for this festive season.</p></div>
<div
        {% for x in holidays %}
            <p class="inner">{{ forloop.counter }}. <b>{{ x.date }}.</b> {{ x.summary }}</p>
        {% endfor %}
</div>
</body>
</html>

To sum up, get_pdf() will generate you a pdf of a page and listing() is a live version of that page – use it to see changes you do to the styles and layout.

This code is available at my GitHub if you are interested.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.