Generating PDFs with Django
Raphael Kimmig, 29 Apr 2013
Last week I needed to render some PDFs from a Django template. In the past I've been using xhtml2pdf which is quite quirky. Getting a document to look right has always been quite challenging because you are constantly forced to work around xhtml2pdf's shortcomings. When I noticed that I wasn't able to make things work I started searching for an alternative.
Enter WeasyPrint
Creating PDFs with WeasyPrint has been nothing but a pleasure. It has great CSS support (including CSS3 Paged media), support for floats and it is incredibly straight forward to create great looking documents with it.
After installing the dependencies1 installing it with pip is as easy as typing pip install weasyprint
.
Rendering a PDF with WeasyPrint is straightforward - a simple view might look like this:
def pdf_view(request):
template = get_template("pdftemplate.html")
context = {"title": "A PDF"}
html = template.render(RequestContext(self.request, context))
response = HttpResponse(mimetype="application/pdf")
weasyprint.HTML(string=html, url_fetcher=url_fetcher).write_pdf(response)
return response
As you can see we are passing an argument url_fetcher=url_fetcher
to weasyprint.HTML
.
The url_fetcher
is a callback that allows WeasyPrint to fetch static media that you use in your template. Per default it understands common schemes like HTTP/HTTPS and file. Because I like to gather all assets that will not be publicly available in a single directory (settings.ASSETS_ROOT
) I use a custom url_fetcher
to fetch those. You can see the url fetcher and a usage example below.
<img src="assets://image/logo.svg"/>
def url_fetcher(url):
if url.startswith('assets://'):
url = url[len('assets://'):]
url = "file://" + safe_join(settings.ASSETS_ROOT, url)
return weasyprint.default_url_fetcher(url)
While WeasyPrint does not (yet?) implement @font-face
there is an easy way to use custom fonts. Just place them where cairo (the library that WeasyPrint uses for rendering) can find them.2
For footers and headers I recommend that you install a recent version of WeasyPrint. This will allow you to use position: fixed
for positioning with fixed elements being repeated on every page.
If you need to generate PDFs with Python I recommend giving WeasyPrint a try, it is truly awesome.
2014.08.07. - Updated url fetcher, thanks Leon for the Idea of using file://
-
On recent Debian and Ubuntu versions
apt-get install python-dev python-pip python-lxml libcairo2 libpango1.0-0 libgdk-pixbuf2.0-0 libffi-dev
↩ -
I put them in the system wide font directories which on Debian/Ubuntu means
/usr/local/share/fonts/type1
for OpenType and/usr/local/share/fonts/truetype
for TrueType. ↩