Django Tutorial on PDF Generation and Rendering with xhtml2pdf Package

Generating PDF is a very common task in web development. They are used for a variety of purposes such are reporting, invoice generation and accounting purposes, etc.

In Django, there is this package xhtml2pdf through which we can generate pdf. In this post you’ll be covering three main topics those are generating PDF from HTML and saving as PDF file in the directory, generating PDF from a template by passing context data and saving as PDF file in a directory, rendering PDF from a template in the browser.

Installation and Configuration of xhtml2pdf Package

Enter below command in terminal.

pip install xhtml2pdf

If you want to install the latest version for Python 3 then go with below command

pip install --pre xhtml2pdf

Setup App

First, let us create a project in Django to do so type below command in terminal.

django-admin startproject pdf_demo

Django will create you in the current directory you are in. Now move to the pdf_demo project root location and to create an app using below command.

django-admin startapp pdf

This will create an app named pdf now we must specify and let Django know the app exists by going to `settings.py` file and add app path below `INSTALLED_APPS`.

INSTALLED_APPS = [
    '...',
    '...',
    'pdf.apps.PdfConfig',
]

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, ""),
    os.path.join(BASE_DIR, "static")
]

MEDIA_URL = 'media/'
MEDIA_ROOT = os.path.join(BASE_DIR, MEDIA_URL)

In app pdf create urls.py file and go project directory’s urls.py file and include pdf.urls path.

#project/app/urls.py
from django.urls import path
from pdf import views
urlpatterns = [
    path('generate-pdf', views.generate_pdf, name="generate_pdf"),
    path('generate-pdf-through-template', views.generate_pdf_through_template, name="generate_pdf_through_template"),
    path('render-pdf', views.render_pdf, name="render_pdf"),
]

In project/project/urls.py.

from django.urls import path, include

urlpatterns = [
    path('pdf/', include('pdf.urls'), name='pdf'),
] 

We have specified URLs its time to create views and templates for respective URLs.

Creating Views

First, we’ll create a function generate_pdf() that basically saves a string file to a location. The string is nothing but HTML.

from django.shortcuts import render
from django.http import HttpResponse
from django.template.loader import render_to_string
from io import BytesIO
from xhtml2pdf import pisa
from pdf.models import State

def generate_pdf(request):
    html = '<html><body><p>To PDF or not to PDF</p></body></html>'
    write_to_file = open('media/test.pdf', "w+b")
    result = pisa.CreatePDF(html,dest=write_to_file)
    write_to_file.close()
    return HttpResponse(result.err)

Inside function generate_pdf(request) the variable html has HTML contents and we are writing files to directory media where it will be saved.

But most of the cases it is not always possible to create a variable like HTML in views to solve this issue in the next function we’ll make use of render_to_string a template loader. We specify HTML in the templates section and call the template whenever there is a need of generating pdf through them.

def generate_pdf_through_template(request):
    context={}

    html = render_to_string('pdf/pdf_template.html',context)
    
    write_to_file = open('media/test_1.pdf', "w+b")
    
    result = pisa.CreatePDF(html,dest=write_to_file)
    
    write_to_file.close()
    
    return HttpResponse(result.err)

The above function saves the pdf as a file in the directory. There will be cases were insisted of saving PDF you need to render it in the browser. This is covered in the below code.

def render_pdf(request):
    path = "pdf/pdf_template.html"
    context = {"states" : State.objects.all()[:100]}

    html = render_to_string('pdf/pdf_template.html',context)
    io_bytes = BytesIO()
    
    pdf = pisa.pisaDocument(BytesIO(html.encode("UTF-8")), io_bytes)
    
    if not pdf.err:
        return HttpResponse(io_bytes.getvalue(), content_type='application/pdf')
    else:
        return HttpResponse("Error while rendering PDF", status=400)

We are passing information to the template through context dictionary and it has states that have a key which has objects of states.
The BytesIO() stores HTML data in memory and is efficient other than using open() method.
While returning response we’ll specify it has application/pdf.

Creating Models

In pdf/models.py.

from django.db import models
class State(models.Model):

    state_id = models.AutoField(primary_key=True)
    state_name = models.CharField(max_length=30, blank=True, null=True)
    country = models.ForeignKey(Country,related_name="country",on_delete=models.CASCADE,null=True)
    is_deleted = models.IntegerField(default=0)

    class Meta:
        managed = True
        db_table = 'states'

Creating Templates

In the app, pdf create HTML file pdf/templates/pdf/pdf_template.html.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Django PDF</title>
    <style>
        th, td{
            padding: 5px 15px;
        }
        th{
            font-size: 20px;
            font-weight: bold;
        }
        td{
            font-size: 15px;
        }
    </style>
</head>
<body>
    <h1 style="text-align: center;" >List of States by respective Countries</h1>

    <table border="1" style="border-collapse: collapse;" >
        <thead>
            <tr>
                <td>Country Name</td>
                <td>Country Short Code</td>
                <td>State Name</td>
            </tr>
        </thead>

        <tbody>
            {% for value in states %}
                <tr>
                    <td>{{value.country.country_name}}</td>
                    <td>{{value.country.country_short}}</td>
                    <td>{{value.state_name}}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>            
</body>
</html>
Django xhtml2pdf Package convert HTML to PDF Output
Result of Django xhtml2pdf Package convert HTML to PDF Output

To learn more on converting HTML to PDF in Django visit xhtml2pdf package documentation.

Conclusion

You have reached the end of our post on Django Tutorial on PDF Generation and Rendering with xhtml2pdf Package. If you find anything difficult with our way of explanation please leave us a comment and if you have enjoyed reading our post then help us grow by share this post link.

Related Posts

Summary
Review Date
Reviewed Item
Django Tutorial on PDF Generation and Rendering with xhtml2pdf Package
Author Rating
51star1star1star1star1star
Software Name
Django Framework
Software Name
Windows Os, Mac Os, Ubuntu Os
Software Category
Web Development