django-wkhtmltopdf on a linux (Webfaction) server

Generating PDF with python is not an easy thing. Here is what I’ve tried:

  • xhtml2pdf: the worst. It has a very bad CSS interpreter and it is pretty impossible to make a clean PDF out from a complex html page.
  • WeasyPrint: that’s better. It has its own HTML and CSS2 interpreters, and despite some minor issues it is fairly reliable. However it requires some big dependencies, and I eventually had some memory issues on my webserver.
  • wkhtmltopdf with its Django wrapper: for now, the best. It makes use of Webkit to render HTML+CSS+JS(!) pages, I don’t think you can do better. However, it requires QT, and this is big. Well, not the “classical” QT, a patched QT (that’s even worse) that allows to run without an X server. This is what I’m using for now in my projects

We’ll see here how to build wkhtmltopdf and a basic django-wkhtmltopdf usage. These steps has been tested on a Webfaction server (CentOS 6) through SSH.

The easy way

Enter this command and go to the “Post-install” chapter.

1
curl -fsSkL https://raw.github.com/gist/3608048/wkhtmltopdf.sh | sh

It will install wkhtmltopdf in ~/bin with its static libraries.

The hard way – Let’s build this

This is mostly taken from the wkhtmltopdf Wiki, but with some no-shared-libraries webserver specifics. Log on SSH, and let the magic begin.

Create a ~/download folder

1
2
$ mkdir -p ~/downloads
$ cd ~/downloads

Get wkhtmltopdf-qt:

1
2
3
4
$ git clone git://gitorious.org/+wkhtml2pdf/qt/wkhtmltopdf-qt.git wkhtmltopdf-qt
$ cd wkhtmltopdf-qt
$ git checkout staging
$ QTDIR=. ./bin/syncqt

Configure, and build it! It will take some time (about an hour, which is not much as we disabled a lot of features):

1
2
3
$ ./configure -release -static -fast -exceptions -no-accessibility -no-stl -no-sql-ibase -no-sql-mysql -no-sql-odbc -no-sql-psql -no-sql-sqlite -no-sql-sqlite2 -no-qt3support -xmlpatterns -no-phonon -no-phonon-backend -webkit -no-scripttools -no-mmx -no-3dnow -no-sse -no-sse2  -qt-zlib -qt-libtiff -qt-libpng -qt-libmng -qt-libjpeg -graphicssystem raster -opensource -nomake tools -nomake examples -nomake demos -nomake docs -nomake translations -no-opengl -no-dbus -no-multimedia -openssl -no-declarative -largefile -rpath -no-nis -no-cups -no-iconv -no-pch -no-gtkstyle -no-nas-sound -no-sm -no-xshape -no-xinerama -no-xcursor -no-xfixes -no-xrandr -no-mitshm -no-xinput -no-xkb -no-glib -no-openvg  -no-xsync -no-audio-backend -no-sse3 -no-ssse3 -no-sse4.1 -no-sse4.2 -no-avx -no-neon -confirm-license -prefix "../wkqt"
$ gmake -j3
$ gmake install

Then, download wkhtmltopdf, and build it:

1
2
3
4
5
$ cd ~/downloads
$ git clone git://github.com/antialize/wkhtmltopdf.git wkhtmltopdf
$ cd wkhtmltopdf
$ ../wkqt/bin/qmake
$ make

You now have wkhtmltopdf and its static dependencies in ./bin/. Let’s copy them into ~/bin:

1
2
$ mkdir -p ~/bin
$ cp bin/* ~/bin/

Post-installation

We need ~/bin in our $PATH. That’s where django-wkhtmltopdf will look for. Add the following line in your ~/.bashrc.

1
2
export PATH=$HOME/bin:$PATH
export LD_LIBRARY_PATH=$HOME/lib:$LD_LIBRARY_PATH

And source it:

1
$ source ~/.bashrc

We’re done with wkhtmltopdf, let’s install its Django wrapper.

django-wkhtmltopdf

Go to your django app, and install django-wkhtmltopdf with PIP:

1
$ pip install wkhtmltopdf

Then, in your view:

1
2
3
4
from wkhtmltopdf.views import PDFTemplateResponse

def view(request):
    PDFTemplateResponse(request, template_name, context_dictionary).rendered_content

Be careful, this last line of code is not documented in the django-wkhtmltopdf’s wiki. It may be subject to changes in future releases. However, that’s the only way I found to get a binary-string instead of a HttpResponse.

That’s all, folks !

Loïs Di Qual: I'm an iOS developer based in Bordeaux, France. This is my blog.

  • Dave

    When you configure wkhtmltopdf-qt, you’re not setting the -prefix to “../wkqt”. How did you get this to work? Or is that just a typo?

    Every time I make install, it fails because it’s trying to install something in /usr/local/Trolltech/ which I don’t have permission to do on webfaction. How did you get this to work?

    • ldiqual

      You’re right, even the wkhtmltopdf wiki says that QT’s configure needs `-prefix “../wkqt”`. I can’t test this right now, could you tell me if it works with this flag ? Thanks for your notice ;)

  • Dave

    OK. For anyone else, you will have to prefix it so add -prefix “../wkqt” to the ./configure so that it looks like the following:

    $ ./configure -release -static -fast -exceptions -no-accessibility -no-stl -no-sql-ibase -no-sql-mysql -no-sql-odbc -no-sql-psql -no-sql-sqlite -no-sql-sqlite2 -no-qt3support -xmlpatterns -no-phonon -no-phonon-backend -webkit -no-scripttools -no-mmx -no-3dnow -no-sse -no-sse2 -qt-zlib -qt-libtiff -qt-libpng -qt-libmng -qt-libjpeg -graphicssystem raster -opensource -nomake tools -nomake examples -nomake demos -nomake docs -nomake translations -no-opengl -no-dbus -no-multimedia -openssl -no-declarative -largefile -rpath -no-nis -no-cups -no-iconv -no-pch -no-gtkstyle -no-nas-sound -no-sm -no-xshape -no-xinerama -no-xcursor -no-xfixes -no-xrandr -no-mitshm -no-xinput -no-xkb -no-glib -no-openvg -no-xsync -no-audio-backend -no-sse3 -no-ssse3 -no-sse4.1 -no-sse4.2 -no-avx -no-neon -confirm-license -prefix “../wkqt”

    You’ll also have to change your LD_LIBRARY_PATH to not point to /usr/local/ as you will not have permission on this directory in the webfaction environment. So add

    LD_LIBRARY_PATH=$HOME/lib:$LD_LIBRARY_PATH
    export LD_LIBRARY_PATH

    to your ~.bash_profile.

    Restart. And you’re all set!

    • ldiqual

      Thanks Dave! I updated my post with the elements you just gave.

      • Dave

        No problem. This was a great help for me as a starting place. I want to make sure it is complete as possible for others.

        Also, since, you did

        $ mkdir -p ~/bin
        $ cp bin/* ~/bin/

        Which moves all the compiled files to your ~/bin/ directory, you’ll have to add ~/bin to the LD_LIBRARY_PATH as well. So the LD_LIBRARY_PATH assignment in the ~.bash_profile will have to be:

        LD_LIBRARY_PATH=$HOME/lib:$HOME/bin:$LD_LIBRARY_PATH

        export LD_LIBRARY_PATH

        You could optionally copy all the .so* files from ~/downloads/wkhtmltopdf/bin into your ~/lib/ directory and only copy the two executable files into your ~/bin/ directory. But there’s no real reason. I prefer to keep them all together and just add the ~/bin/ directory to the library path.

        Thanks for the article!