Render HTML to PDF in Django site

Viewed 125140

For my django powered site, I am looking for an easy solution to convert dynamic html pages to pdf.

Pages include HTML and charts from Google visualization API (which is javascript based, yet including those graphs is a must).

10 Answers

I get the code to generate the PDF from html template :

    import os

    from weasyprint import HTML

    from django.template import Template, Context
    from django.http import HttpResponse 


    def generate_pdf(self, report_id):

            # Render HTML into memory and get the template firstly
            template_file_loc = os.path.join(os.path.dirname(__file__), os.pardir, 'templates', 'the_template_pdf_generator.html')
            template_contents = read_all_as_str(template_file_loc)
            render_template = Template(template_contents)

            #rendering_map is the dict for params in the template 
            render_definition = Context(rendering_map)
            render_output = render_template.render(render_definition)

            # Using Rendered HTML to generate PDF
            response = HttpResponse(content_type='application/pdf')
            response['Content-Disposition'] = 'attachment; filename=%s-%s-%s.pdf' % \
                                              ('topic-test','topic-test', '2018-05-04')
            # Generate PDF
            pdf_doc = HTML(string=render_output).render()
            pdf_doc.pages[0].height = pdf_doc.pages[0]._page_box.children[0].children[
                0].height  # Make PDF file as single page file 
            pdf_doc.write_pdf(response)
            return response

    def read_all_as_str(self, file_loc, read_method='r'):
        if file_exists(file_loc):
            handler = open(file_loc, read_method)
            contents = handler.read()
            handler.close()
            return contents
        else:
            return 'file not exist'  

If you have context data along with css and js in your html template. Than you have good option to use pdfjs.

In your code you can use like this.

from django.template.loader import get_template
import pdfkit
from django.conf import settings

context={....}
template = get_template('reports/products.html')
html_string = template.render(context)
pdfkit.from_string(html_string, os.path.join(settings.BASE_DIR, "media", 'products_report-%s.pdf'%(id)))

In your HTML you can link extranal or internal css and js, it will generate best quality of pdf.

  • This is for Django >=3
  • This code converts HTML template to pdf file for any page. For example: post/1/new1, post/2/new2
  • pdf file name is last part in url. For example for post/2/new2, file name is new2

First install xhtml2pdf

pip install xhtml2pdf

urls.py

from .views import generatePdf as GeneratePdf
from django.urls import re_path
urlpatterns = [
#...
re_path(r'^pdf/(?P<cid>[0-9]+)/(?P<value>[a-zA-Z0-9 :._-]+)/$', GeneratePdf, name='pdf'),
#...
]

views.py

from django.template.loader import get_template
from .utils import render_to_pdf
# pdf
def generatePdf(request,cid,value):
    print(cid,value)
    pdf = render_to_pdf('myappname/pdf/your.html',cid)
    return HttpResponse(pdf, content_type='application/pdf')

utils.py

from io import BytesIO #A stream implementation using an in-memory bytes buffer
                       # It inherits BufferIOBase

from django.http import HttpResponse
from django.template.loader import get_template

#pisa is a html2pdf converter using the ReportLab Toolkit,
#the HTML5lib and pyPdf.

from xhtml2pdf import pisa  
#difine render_to_pdf() function
from .models import myappname
from django.shortcuts import get_object_or_404


def render_to_pdf(template_src,cid, context_dict={}):
    template = get_template(template_src)
    node = get_object_or_404(myappname, id =cid)
    context = {'node':node}
    context_dict=context
    html  = template.render(context_dict)
    result = BytesIO()

    #This part will create the pdf.
    pdf = pisa.pisaDocument(BytesIO(html.encode("ISO-8859-1")), result)
    if not pdf.err:
        return HttpResponse(result.getvalue(), content_type='application/pdf')
    return None

Structure:

myappname/
      |___views.py
      |___urls.py
      |___utils.py
      |___templates/myappname/your.html

I tried the best answer in this thread and it didn't work for python3.8, hence I had to do some changes as follows ( for anyone working on python3.8 ) :

import io 
from xhtml2pdf import pisa
from django.http import HttpResponse
from html import escape

from django.template.loader import render_to_string

def render_to_pdf(template_src, context_dict):
    html = render_to_string(template_src, context_dict)
    result = io.BytesIO()



    pdf = pisa.pisaDocument(io.BytesIO (html.encode("utf-8")), result)
    if not pdf.err:
        return HttpResponse(result.getvalue(), content_type='application/pdf')
    return HttpResponse('We had some errors<pre>%s</pre>' % escape(html))

I had to change cgi to html since cgi.escape is depricated, and I replaced StringIO with io.ByteIO() as for the rendering I used render_to_string instead of converting the dict to context which was throwing an error.

Related