# PDF Generation

# Install Dependencies

Start with xvfb if you do not have it already, don't ask why - just install it.

sudo apt-get update

sudo apt-get install xvfb

Next thing you need is wkhtmltopdf, very important. Ubuntu 20.04

wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb

sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb

Ubuntu 18.04

wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.bionic_amd64.deb

sudo apt install ./wkhtmltox_0.12.6-1.bionic_amd64.deb

Ubuntu 16.04

wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.xenial_amd64.deb
sudo apt install ./wkhtmltox_0.12.6-1.xenial_amd64.deb

Make sure you clean up, delete the downloaded installer when done by just running:

rm xxxxxxxxxxxxxxx

# Creating the PDF

Start by installing pdfkit

pip install pdfkit

Find out where wkhtmltopdf is installed by running

which wkhtmltopdf

Copy that location - you will need it.

Django View Route that Displays a PDF from an HTML File, without saving it anywhere in your server.

from django.http import HttpResponse
from django.shortcuts import render

import pdfkit
from django.template.loader import get_template
import os



def createPDF(request):
  #The name of your PDF file
  filename = 'filename.pdf'

  #HTML FIle to be converted to PDF - inside your Django directory
  template = get_template('app/html-to-be-converted-to-pdf.html')

  #Add any context variables you need to be dynamically rendered in the HTML
  context = {}
  context['name'] = 'Skolo'
  context['surname'] = 'Online'

  #Render the HTML
  html = template.render(context)

  #Options - Very Important [Don't forget this]
  options = {
        'encoding': 'UTF-8',
        'javascript-delay':'1000', #Optional
        'enable-local-file-access': None, #To be able to access CSS
        'page-size': 'A4',
        'custom-header' : [
            ('Accept-Encoding', 'gzip')
        ],
    }
    #Javascript delay is optional

    #Remember that location to wkhtmltopdf
    config = pdfkit.configuration(wkhtmltopdf='/usr/local/bin/wkhtmltopdf')

    #IF you have CSS to add to template
    css1 = os.path.join(settings.STATIC_ROOT, 'css', 'app.css')
    css2 = os.path.join(settings.STATIC_ROOT, 'css', 'bootstrap.css')

    #Create the file
    file_content = pdfkit.from_string(html, False, configuration=config, options=options)

    #Create the HTTP Response
    response = HttpResponse(file_content, content_type='application/pdf')
    response['Content-Disposition'] = 'inline; filename = {}'.format(filename)

    #Return
    return response

If you need to save the file to your server, before you render or as you render, use the following code

from django.http import HttpResponse
from django.shortcuts import render

import pdfkit
from django.template.loader import get_template
import os



def createPDF(request):
  #The name of your PDF file
  filename = 'filename.pdf'

  #HTML FIle to be converted to PDF - inside your Django directory
  template = get_template('app/html-to-be-converted-to-pdf.html')

  #Add any context variables you need to be dynamically rendered in the HTML
  context = {}
  context['name'] = 'Skolo'
  context['surname'] = 'Online'

  #Render the HTML
  html = template.render(context)

  #Options - Very Important [Don't forget this]
  options = {
        'encoding': 'UTF-8',
        'javascript-delay':'1000', #Optional
        'enable-local-file-access': None, #To be able to access CSS
        'page-size': 'A4',
        'custom-header' : [
            ('Accept-Encoding', 'gzip')
        ],
    }
    #Javascript delay is optional

    #Remember that location to wkhtmltopdf
    config = pdfkit.configuration(wkhtmltopdf='/usr/local/bin/wkhtmltopdf')

    #IF you have CSS to add to template
    css1 = os.path.join(settings.STATIC_ROOT, 'css', 'app.css')
    css2 = os.path.join(settings.STATIC_ROOT, 'css', 'bootstrap.css')

    #Saving the File
    filepath = '/absolute/path/to/directory/where/you/want/to/save/file/'
    os.makedirs(file_path, exist_ok=True)
    pdf_save_path = filepath+filename
    #Save the PDF
    pdfkit.from_string(html, pdf_save_path, configuration=config, options=options)

    #Create the file
    file_content = pdfkit.from_string(html, False, configuration=config, options=options)

    #Create the HTTP Response
    response = HttpResponse(file_content, content_type='application/pdf')
    response['Content-Disposition'] = 'inline; filename = {}'.format(filename)

    #Return
    return response

# Display PDF file Iframe

In some cases you might want to display the PDF in an Iframe, as opposed to a full page PDF. You can do this with a PDF you created, or any other PDF saved in your server. Iframe displays is good for inline displays, inside other HTML content. What you need is again - the absolute path to the pdf location.

Create a VIEW path - inside view.py that will return File Response

from django.http import FileResponse, Http404, HttpResponse

def displayPDF(request):
    file_path = '/absolute/path/to/location/of/pdf/document/pdfname.pdf'
    try:
        return FileResponse(open(file_path, 'rb'), content_type='application/pdf')
    except FileNotFoundError:
        raise Http404('not found')

You can also use this method to display a file uploaded to Media Directory of Django Model, just edit like so:

from django.http import FileResponse, Http404, HttpResponse

def displayPdf(request, filename):
    file_path = settings.MEDIA_ROOT+ '/upload_directory/' + filename
    try:
        return FileResponse(open(file_path, 'rb'), content_type='application/pdf')
    except FileNotFoundError:
        raise Http404('not found')

The upload_directory in this case, would be the subfolder inside your media directory where your model is uploading to. This should match, the same upload_to in side your model file, like so.

file = models.FileField(upload_to='upload_directory', null=True, blank=True,)

Inside of the urls.py file Method 1 - you know the filename, no need to pass it to the route.

 ...
 path('display-pdf/', views.displayPdf, name='display-pdf'),
 ...

Method 2 - you are dynamically displaying it - from a model, the filename will come from the model.

 ...
 path('display-pdf/<str:filename>', views.displayPdf, name='display-pdf'),
 ...

Then inside of your html template Method 1

<iframe src="{% url 'display-pdf' %}" width="100%" height="800px"></iframe>

Method 2

<iframe src="{% url 'display-pdf' model.file|filename %}" width="100%" height="800px"></iframe>

Note we are using a template filter, called filename. You would need to create this filter and load it at the top of the HTML. You can create the filter like so:

import os
from django import template

register = template.Library()


@register.filter
def filename(value):
    return os.path.basename(value.file.name)

TIP

Remember to register the tag in the settings.py file, check the settings documentation. Then include it at the top of the html file.

{% load filename %}