How to Create PDF Documents with Django in 2019
Tags: pdf, python, django, intermediate
Published: 2019-01-12 22:15:02

---
title: How to Create PDF Documents with Django in 2019
published: true
tags: PDF,Python,Django,Intermediate
canonical_url: http://djangotricks.blogspot.com/2019/01/how-to-create-pdf--documents-with-django-in-2019.html
---

 ![](https://1.bp.blogspot.com/-7ryrK96vtEc/XDgT8U3TaSI/AAAAAAAAB7k/IfjBWM8qyk8Q-hpRRTgW9Upq4EvT3DJcgCLcBGAs/s1600/how-to-create-pdf--documents-with-django-in-2019.png) 

If you've read my __Web Development with Django Cookbook__, you might remember a recipe for creating PDF documents using __Pisa xhtml2pdf__. Well, this library does its job, but it supports only a subset of HTML and CSS features. For example, for multi-column layouts, you have to use tables, like it's 1994.

I needed some fresh and flexible option to generate donation receipts for the [donation platform __www.make-impact.org__](http://www.make-impact.org/) and reports for [the strategic planner __1st things 1st__](https://www.1st-things-1st.com) I have been building. After a quick research I found another much more suitable library. It's called __[WeasyPrint](http://weasyprint.org/)__. In this article, I will tell you how to use it with Django and what's valuable in it.

## Features

__WeasyPrint__ uses HTML and CSS 2.1 to create pixel-perfect, or let's rather say point-perfect, PDF documents. __WeasyPrint__ doesn't use WebKit or Gecko but has its own rendering engine. As a proof that it works correctly, it passes the famous among web developers Acid2 test which was created back in the days before HTML5 to check how compatible browsers are with CSS 2 standards.

All supported features (and unsupported exceptions) are listed in [the documentation](http://weasyprint.readthedocs.io/en/latest/features.html). But my absolute favorites are these:

- __Layouts with floated elements.__ You don't have to use tables anymore if you want to have the recipient address on the left side and the sender information on the right side in a letter, or if you want to have the main content and the side notes in an exercise book. Just use floated elements.
- __Working links.__ The generated document can have clickable links to external URLs and internal anchors. You can straightforwardly create a clickable table of contents or a banner that leads back to your website.
- __Support for web fonts.__ With the wide variety of embeddable web fonts, your documents don't need to look boring anymore. Why not write titles in elegant cursive or in bold western letters?
- __Background images.__ By default, when you print an HTML page, all foreground images get printed, but the backgrounds are skipped. When you generate a PDF document for printing, you can show background images anywhere, even in the margins of the printed page.
- __SVG kept as vector images.__ When you have diagrams and graphics in a PDF document, you usually want to preserve the quality of the lines. Even if they look good on the screen, raster images might be not what you want, because on a printed page the resolution will differ and the quality can be lost. WeasyPrint keeps SVG images as vector images, so you have the highest possible quality in the prints.

## Important Notes

WeasyPrint needs __Python 3.4__ or newer. That's great for new Django projects, but might be an obstacle if you want to integrate it into an existing website running on Python 2.7. Can it be the main argumentation for you to upgrade your old Django projects to the new Python version?

WeasyPrint is __dependent on several OS libraries__: Pango, GdkPixbuf, Cairo, and Libffi. In the documentation, there are [understandable one-line instructions](http://weasyprint.readthedocs.io/en/latest/install.html) how to install them on different operating systems. You can have a problem only if you don't have full control of the server where you are going to deploy your project.

If you need some __basic headers and footers__ for all pages, you can use `@page` CSS selector for that. If you need __extended headers and footers__ for each page, it's best to combine the PDF document out of separate HTML documents for each page. Examples follow below.

The fun fact, __Emojis__ are drawn using some weird raster single-color font. I don't recommend using them in your PDFs unless you replace them with SVG images.

## Show Me the Code

A technical article is always more valuable when it has some quick code snippets to copy and paste. Here you go!

### Simple PDF View

This snippet generates a donation receipt and shows it directly in the browser. Should the PDF be downloadable immediately, change content disposition from `inline` to `attachment`.

```python
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals

from django.http import HttpResponse
from django.template.loader import render_to_string
from django.utils.text import slugify
from django.contrib.auth.decorators import login_required

from weasyprint import HTML
from weasyprint.fonts import FontConfiguration

from .models import Donation

@login_required
def donation_receipt(request, donation_id):
    donation = get_object_or_404(Donation, pk=donation_id, user=request.user)
    response = HttpResponse(content_type="application/pdf")
    response['Content-Disposition'] = "inline; filename={date}-{name}-donation-receipt.pdf".format(
        date=donation.created.strftime('%Y-%m-%d'),
        name=slugify(donation.donor_name),
    )
    html = render_to_string("donations/receipt_pdf.html", {
        'donation': donation,
    })

    font_config = FontConfiguration()
    HTML(string=html).write_pdf(response, font_config=font_config)
    return response
```

### Page Configuration Using CSS

Your PDF document can have a footer with an image and text on every page, using `background-image` and `content` properties:

```html
{% load staticfiles i18n %}
<link href="https://fonts.googleapis.com/css?family=Playfair+Display:400,400i,700,700i,900" rel="stylesheet" />
<style>
@page {
    size: "A4";
    margin: 2.5cm 1.5cm 3.5cm 1.5cm;
    @bottom-center {
        background: url({% static 'site/img/logo-pdf.svg' %}) no-repeat center top;
        background-size: auto 1.5cm;
        padding-top: 1.8cm;
        content: "{% trans "Donation made via www.make-impact.org" %}";
        font: 10pt "Playfair Display";
        text-align: center;
        vertical-align: top;
    }
}
</style>
```

### Pagination

You can show page numbers in the footer using CSS as follows.

```css
@page {
    margin: 3cm 2cm;
    @top-center {
        content: "Documentation";
    }
    @bottom-right {
        content: "Page " counter(page) " of " counter(pages); 
    }
}
```

### Horizontal Page Layout

You can rotate the page to horizontal layout with `size: landscape`.

```css
@page {
    size: landscape;
}
```

### HTML-based Footer

Another option to show an image and text in the header or footer on every page is to use an HTML element with `position: fixed`. This way you have more flexibility about formatting, but the element on all your pages will have the same content.

```html
<style>
footer {
    position: fixed;
    bottom: -2.5cm;
    width: 100%;
    text-align: center;
    font-size: 10pt;
}
footer img {
    height: 1.5cm;
}
</style>
```

```html
<footer>
    {% with website_url="https://www.make-impact.org" %}
        <a href="{{ website_url }}">
            <img alt="" src="{% static 'site/img/logo-contoured.svg' %}" />
        </a><br />
        {% blocktrans %}Donation made via <a href="{{ website_url }}">www.make-impact.org</a>{% endblocktrans %}
    {% endwith %}
</footer>
```

### Document Rendering from Page to Page

When you need to have a document with complex unique headers and footers, it is best to render each page as a separate HTML document and then to combine them into one. This is how to do that:

```python
def letter_pdf(request, letter_id):
    letter = get_object_or_404(Letter, pk=letter_id)
    response = HttpResponse(content_type='application/pdf')
    response['Content-Disposition'] = (
        'inline; '
        f'filename={letter.created:%Y-%m-%d}-letter.pdf'
    )
    COMPONENTS = [
        'letters/pdf/cover.html',
        'letters/pdf/page01.html',
        'letters/pdf/page02.html',
        'letters/pdf/page03.html',
    ]
    documents = []
    font_config = FontConfiguration()
    for template_name in COMPONENTS:
        html = render_to_string(template_name, {
            'letter': letter,
        })
        document = HTML(string=html).render(font_config=font_config)
        documents.append(document)

    all_pages = [page for document in documents for page in document.pages]
    documents[0].copy(all_pages).write_pdf(response)

    return response
```

## Final Thoughts

I believe that __WeasyPrint__ could be used not only for invoices, tickets, or booking confirmations but also for online magazines and small booklets. If you want to see PDF rendering with WeasyPrint in action, make a donation to your chosen organization at [www.make-impact.org](http://www.make-impact.org) (when it's ready) and download the donation receipt. Or check the demo account at [my.1st-things-1st.com](https://my.1st-things-1st.com) and find the button to download the results of a prioritization project as PDF document.

---
Cover photo by [Daniel Korpai](https://unsplash.com/photos/FgK0yuynevY).