# 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 %}