Django — render HTML to PDF
Lets look through rendering our templates to a PDF. Not just some fixed values lets build a report pulling through some data from a database, and then send said PDF as an attachment to a user.
Changes
11/01/2018 — Published
13/01/2018 — Added Extending the Implementation
Note
As always the code provided is not production ready, it is an example show casing some techniques you can use to solve a particular problem or task allowing you to implement this into your own code and workflow. Always learn it first, don’t just copy and paste 😅 if you do, make sure you add test’s, error handling, etc
Test Application
Lets setup our initial application. Create a new Django application, setup virtualenv and create a couple of models and register them in our Admin.
The only not ordinary aspect her is that we override the save method of the sales model to calculate the price. Lets make and migrate these.
python manage.py makemigrations
python manage.py migrate
Our app is in its basic form, lets create some users of our app, we need a superuser, and a 2 more users for this example (be sure to give the first and last names)
We will be building a sales report, so lets populate our database. We dont want to manually go through the admin panel and add these records, so lets build a simple seeder, a method we can run which will populate our database for us.
This class just creates a list of product titles when instantiated and then in the method seed it will run 20 times and create us 20 products and 20 sales, we use random to generate some random attributes for us.
We call this by running it from our terminal, we could build a management command to run this if needed, for a walkthrough of management commands see here.
Our database has been populated now, so lets build our report, the first thing we need is to bring in the package we will use.
pip install xhtml2pdf # Python 2pip install --pre xhtml2pdf # Python 3
Lets setup our render class, we will be building a wrapper class that holds our logic for building our pdf. This file is render.py
We create a class called Render and build out a static method called render, this takes a string and dict as arguments.
We then use some django methods to get the template and render it using our dict argument.
The pisa.pisaDocument method accepts streams as parameters we need a source and destination. we encode our template to UTF-8 and parse that into a BytesIO stream and then use the reponse object we created to hold the output from pisa.
We then check for errors during the processing and return HttpResponse object, for success we specify we want the response.getValue() and set the content_type meta tag as ‘application/pdf’.
Lets build out our views.py
We implement a standard CBV, with just a get method, we build our params dictionary and return a Render.render() object. We don’t need to return a Httpresponse as this is handled by the Render.render method.
So now we need our template:
You may run into trouble pulling external stylesheets, ideally i would suggest you keep your styling inline.
Lets bind our view to a URL, in a real project i would create a urls.py as normal, however for this example i have just bound it to the main urls file.
path('render/pdf/', Pdf.as_view())
Lets give it a test, inside Chrome i get the following:

Emailing
So its as easy as that, now lets email it to the user who generated the PDF.
We are going to add a method onto the Render class called render_to_file, this method will render to a file and save that file and then will return the path and file name so we can open it and email it. (good if your using celery to process emails)
This method will look like this:
So it looks the same as before, however we are building a few path objects and using a context manager to write the file to a pdf file. We are returning a list of the file_name and file_path.
Our views.py file has been updated to
To simulate a queue system, i have implemented threading to handle the email sending, so the view is processed, we call our render_to_file method and then hand the returning list off to a thread to process the send_email function.
Lets give it a test. Our view will render out “Processed”, and off load the email to our thread. I can check my inbox and i have an email with an attachment.

So there we go, we have a functioning system which emails a user the PDF, and with a bit more work and test’s this would work well in production although i would recommend replacing the threads with Celery or some other manager.
Happy Coding! 👊
Any feedback is always welcomed!
Extending the Implementation — 13/01/2018
I was thinking that there are a 2 things off the top of my head i can change to improve this, the first is a making it a mixin for CBV and secondly storing a record of report requests.
Custom Mixin
As Django users we enjoy the speed and benefit of mixins, so to build a mixin to handle our render to pdf views wouldn’t be that hard, see below for a basic example (not full 😅):
So you can see in a relatively small amount of changes we can offload this to a mixin, so we don’t need to keep adding in the Render class, we just call the mixin set the properties and boom.

If you haven’t played with building your own mixins, i highly recommend it as Django is so flexible and extensible, you can make it do what you need. (most of the time 😉)
Record of PDF’s Generated
Depending on the application, project, etc you may let users build their own queries for reporting, so we can trouble shoot and also safeguard the data i.e no one accessing they shouldn’t we may want to keep a record of PDF requests or PDF’s generated (you can never have enough data on how your users use the system 🙈).
This would be as simple as creating your model to hold the data, for my application i would do:
Then in your view you would catch the data you want, build the object and save, you could pass this data off to a queue system such as celery, or even pass it to a separate thread for processing.
In this example i would take the request object and grab a number of fields which would be beneficial for reporting on, as well as the query being used, the user, if it is an email or web request and a success flag to see if the report was successful. All of this allows us to keep an eye on the reporting aspect of the application, to better understand our users, assist with report queries, etc.
As i said earlier you can never have enough data on how your users use the system 🙈.
A handy tip though, i put query in the model i want to catch the query being used for the report, we can access this by accessing the query property on our QuerySet:
s = Sales.objects.all()
print(s.query)
Happy Coding! 👊