Digitale Lösungen

Generating PDFs with Django

Raphael Kimmig, 29 Apr 2013

Last week I needed to render some PD­Fs from a Django tem­plate. In the past I've been us­ing xhtm­l2p­df which is quite quirky. Get­ting a doc­u­ment to look right has al­ways been quite chal­len­ging be­cause you are con­stantly forced to work around xhtm­l2p­df's short­com­ings. When I no­ticed that I wasn't able to make things work I star­ted search­ing for an al­tern­at­ive.

Enter WeasyP­rint

Cre­at­ing PD­Fs with WeasyP­rint has been noth­ing but a pleas­ure. It has great CSS sup­port (in­clud­ing CSS3 Paged me­dia), sup­port for floats and it is in­cred­ibly straight for­ward to cre­ate great look­ing doc­u­ments with it.

After in­stalling the de­pend­en­cies1 in­stalling it with pip is as easy as typ­ing pip install weasyprint.

Ren­der­ing a PDF with WeasyP­rint is straight­for­ward - 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 ar­gu­ment url_fetcher=url_fetcher to weasyprint.HTML. The url_fetcher is a call­back that al­lows WeasyP­rint to fetch stat­ic me­dia that you use in your tem­plate. Per de­fault it un­der­stands com­mon schemes like HT­TP/HT­TPS and file. Be­cause I like to gath­er all as­sets that will not be pub­licly avail­able in a single dir­ect­ory (settings.ASSETS_ROOT) I use a cus­tom url_fetcher to fetch those. You can see the url fetch­er and a us­age ex­ample be­low.

<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 WeasyP­rint does not (yet?) im­ple­ment @font-face there is an easy way to use cus­tom fonts. Just place them where cairo (the lib­rary that WeasyP­rint uses for ren­der­ing) can find them.2

For foot­ers and head­ers I re­com­mend that you in­stall a re­cent ver­sion of WeasyP­rint. This will al­low you to use position: fixed for po­s­i­tion­ing with fixed ele­ments be­ing re­peated on every page.

If you need to gen­er­ate PD­Fs with Py­thon I re­com­mend giv­ing WeasyP­rint a try, it is truly awe­some.

2014.08.07. - Up­dated url fetch­er, thanks Le­on for the Idea of us­ing file://

  1. On re­cent Debi­an and Ubuntu ver­sions apt-get install python-dev python-pip python-lxml libcairo2 libpango1.0-0 libgdk-pixbuf2.0-0 libffi-dev 

  2. I put them in the sys­tem wide font dir­ect­or­ies which on Debi­an/Ubuntu means /usr/local/share/fonts/type1 for Open­Type and /usr/local/share/fonts/truetype for TrueType.