In this tutorial you will learn how to create a pdf from a HTML template using Django.

This may be very useful if you need to create an app that automize pdf generation. For this you can create a Django form, pass as context all the variables you want to include in your pdf to an HTML template and allow the final user to download this template as a pdf .

Installing xhtml2pdf

For this tutorial we will use xhtml2pdf that will allow us to convert HTML templates to PDF.

To install xhtml2pdf we use:

# Using Python 3

pip install --pre xhtml2pdf 

# Using Python 2

pip install xhtml2pdf

HTML template to be converted to PDF

The first thing we need to do is to create the HTML template that will be converted to pdf, for this we will include within the template all the variables that will be received from our Django view as a context, it is highly recommended that we keep are CSS stylesheet inline (in the head) of the html template.

For this example our HTML template will be an acceptance letter, in this case we added the following variables: {{ name }}, {{ starting_date }}, {{ possition }},
{{ amount }}, {{ currency }}, {{ company }} :

{% load static %}
<!DOCTYPE html>
<html>
    <head>
        <title>Acceptance Letter</title>
        <style type="text/css">
            body {
                font-weight: 200;
                font-size: 14px;
            }
            .header {
                padding: 20px 20px 50px 20px;
                text-align: center;
                font-family: Arial, Helvetica, sans-serif;

            }
            .details {
                padding: 0px 40px 0px 40px;
                text-align: justify !important;
                font-family: Arial, Helvetica, sans-serif;
            }

        </style>
    </head>
    <body>
        <div style="text-align: center;" class='header'>
            <h1>  
                Job Acceptance Letter from {{ company }}
            </h1>
        </div>
        <div>
        <div class='details'>
            <p>
                Dear {{ name }}<br><br>

                This is in reference to the job offer letter sent by you regarding the role as {{ possition }}. {{ company }} 
                is pleased to extend to you an offer of employment as {{possition}} at a rate of {{currency}} {{amount}} 
                beginning on {{ starting_date }}.
            </p>

            <p style="padding: 100px 40px 100px 40px;">
                <img width="300" src="{{ firma }}"> <br>
                Jane Doe | Talent Acquisition Manager | {{ company }} <br>
                Cel.: (+34) 333-1234567 <br>
                <a href="http://www.awesomestartup.com">http://www.awesomestartup.com</a> <br>
            </p>

        </div>

    </body>
</html>

Django views

Index view

The first Django view we will create is a view where the final user can download the pdf:

def index(request):
    if request.method == 'GET':
        return render(request, 'pdf_manager/user.html')

For this example, this view will render a simple download form with a download button from the HTML template user.html, it might be something as follows:

<!DOCTYPE html>
<html>
    <head>
        <title>Certificates</title>
    </head>
    <body>
        <form method="POST" action="{% url 'download' %}">
            {% csrf_token %}
            <button type="submit">Download</button> <br><br>
        </form>
    </body>
</html>

Download view

Now we create a Django view that downloads the PDF, notice that this view is called from the view "index" when the user click on the download button and that is going to pass as context the variables that we included in our HTML template, we will save these variables in template_path:

from django.shortcuts import render
from django.http import HttpResponse
from django.template.loader import get_template
from xhtml2pdf import pisa

def download(request):
    if request.method == 'POST':
        name = 'Jon Doe'
        starting_date = 'May 05 2022'
        possition = 'Senior Backend Developer'
        amount = '7000'
        currency = 'EUR'
        company = 'Awesome Startup'
        logo = 'pdf_manager/logo.JPG'
        firma = 'pdf_manager/firma.JPG'

        template_path = 'pdf_manager/acceptance_letter.html'
        context = {
            'name': name,
            'starting_date': starting_date,
            'possition': possition,
            'amount': amount,
            'currency': currency,
            'company': company,
            }

        # Create a Django response object, and specify content_type as pdf
        response = HttpResponse(content_type='application/pdf')
        response['Content-Disposition'] = 'attachment; filename="acceptance_letter.pdf"'
        # find the template and render it.
        template = get_template(template_path)
        html = template.render(context)

        # create a pdf
        pisa_status = pisa.CreatePDF(
            html, dest=response)
        return response

Remember to include your views to urls.py, it should looks something like this:

from django.urls import path
from .import views


urlpatterns = [
    path('', views.index, name='index'),
    path('download', views.download, name='download'),
]

If you have followed all the previous steps now you should have a simple web app with a button to download your HTML template as a PDF, for this example the pdf generated will look like this:

Screenshot 2022-05-01 185548.png

Adding images to our template

Up to this point we have learned how generate pdf documents from an HTML template using only text, this is good but there might be the situation where you want to include an image within your pdf, now we will look how to do this.

Relative path to absolute path

Unfortunately xhtml2pdf won't allow us to simply add our images within our HTML template using a relative path. For doing so we have to use an absolute path to our image, there are several approaches we could take at this point but the solution recommended in the xhtml2pdf documentation is to implement the following function that converts a relative path to absolute system paths:

import os
from django.conf import settings
from django.http import HttpResponse
from django.template.loader import get_template
from xhtml2pdf import pisa
from django.contrib.staticfiles import finders


    def link_callback(uri, rel):
            """
            Convert HTML URIs to absolute system paths so xhtml2pdf can access those
            resources
            """
            result = finders.find(uri)
            if result:
                    if not isinstance(result, (list, tuple)):
                            result = [result]
                    result = list(os.path.realpath(path) for path in result)
                    path=result[0]
            else:
                    sUrl = settings.STATIC_URL        # Typically /static/
                    sRoot = settings.STATIC_ROOT      # Typically /home/userX/project_static/
                    mUrl = settings.MEDIA_URL         # Typically /media/
                    mRoot = settings.MEDIA_ROOT       # Typically /home/userX/project_static/media/

                    if uri.startswith(mUrl):
                            path = os.path.join(mRoot, uri.replace(mUrl, ""))
                    elif uri.startswith(sUrl):
                            path = os.path.join(sRoot, uri.replace(sUrl, ""))
                    else:
                            return uri

            # make sure that file exists
            if not os.path.isfile(path):
                    raise Exception(
                            'media URI must start with %s or %s' % (sUrl, mUrl)
                    )
            return path

Adding images to our HTML template

Now we add the new image to the template_path dictionary in the download view, we should be sure that the context that the view is going to pass to the HTML template is the relative path to the image.

We also have to import the link_callback(uri, rel) function that we just created and add it in our download view:

from django.shortcuts import render
from django.http import HttpResponse
from django.template.loader import get_template
from xhtml2pdf import pisa
from .utils import link_callback 

def download(request):
    if request.method == 'POST':
        name = 'Jon Doe'
        starting_date = 'May 05 2022'
        possition = 'Senior Backend Developer'
        amount = '7000'
        currency = 'EUR'
        company = 'Awesome Startup'
        logo = 'pdf_manager/logo.JPG' #New Image

        template_path = ''pdf_manager/acceptance_letter.html''
        context = {
            'name': name,
            'starting_date': starting_date,
            'possition': possition,
            'amount': amount,
            'currency': currency,
            'company': company,
            'logo': logo,
            }

        # Create a Django response object, and specify content_type as pdf
        response = HttpResponse(content_type='application/pdf')
        response['Content-Disposition'] = 'attachment; filename="acceptance_letter.pdf"'
        # find the template and render it.
        template = get_template(template_path)
        html = template.render(context)

        # create a pdf
        pisa_status = pisa.CreatePDF(
            html, dest=response, link_callback=link_callback)
        return response

Finally we have to modify the HTML to PDF template adding the image that we want to render when the final user downloads the PDF <img src="{{ logo }}">.

{% load static %}
<!DOCTYPE html>
<html>
    <head>
        <title>Acceptance Letter</title>
        <style type="text/css">
            body {
                font-weight: 200;
                font-size: 14px;
            }
            .header {
                padding: 20px 20px 50px 20px;
                text-align: center;
                font-family: Arial, Helvetica, sans-serif;

            }
            .details {
                padding: 0px 40px 0px 40px;
                text-align: justify !important;
                font-family: Arial, Helvetica, sans-serif;
            }

        </style>
    </head>
    <body>
        <img width="250" src="{{ logo }}"> <br><br>
        <div style="text-align: center;" class='header'>
            <h1>  
                Job Acceptance Letter from {{ company }}
            </h1>
        </div>
        <div>
        <div class='details'>
            <p>
                Dear {{ name }}<br><br>

                This is in reference to the job offer letter sent by you regarding the role as {{ possition }}. {{ company }} 
                is pleased to extend to you an offer of employment as {{possition}} at a rate of {{currency}} {{amount}} 
                beginning on {{ starting_date }}.
            </p>

            <p style="padding: 100px 40px 100px 40px;">
                Jane Doe | Talent Acquisition Manager | {{ company }} <br>
                Cel.: (+34) 333-1234567 <br>
                <a href="http://www.awesomestartup.com">http://www.awesomestartup.com</a> <br>
            </p>

        </div>

    </body>
</html>

The final result with the new image is:

conimagen.jpg

Logo

学AI,认准AI Studio!GPU算力,限时免费领,邀请好友解锁更多惊喜福利 >>>

更多推荐