PDFCrowd Blog
Product updates, tips & tricks

Export PDF in Django

Converting HTML to PDF is a common requirement in Django applications - generating invoices, reports, certificates, or any printable document. This tutorial shows how to integrate PDFCrowd's HTML to PDF API into your Django views.

 

If you prefer a client-only solution without server-side code, see WebSave as PDF.

Installation

Install the PDFCrowd Python client:

pip install pdfcrowd

Converting a URL to PDF

The simplest approach is to create a Django view that serves content at a URL, then convert that URL to PDF. This ensures all static assets (CSS, images) are accessible to the conversion API:

import urllib.parse
from django.http import HttpResponse
from django.views.decorators.http import require_POST
import pdfcrowd

@require_POST
def generate_invoice_pdf(request, invoice_id):
    try:
        client = pdfcrowd.HtmlToPdfClient('demo', 'demo')
        client.setPageSize('A4')
        client.setPageMargins('10mm', '10mm', '10mm', '10mm')

        # Build the URL to convert
        invoice_url = request.build_absolute_uri(f'/invoices/{invoice_id}/html/')

        # Prepare the HTTP response
        response = HttpResponse(content_type='application/pdf')
        response['Cache-Control'] = 'max-age=0'
        response['Accept-Ranges'] = 'none'
        response['Content-Disposition'] = (
            "attachment; filename*=UTF-8''" +
            urllib.parse.quote(f'invoice_{invoice_id}.pdf', safe='')
        )

        client.convertUrlToStream(invoice_url, response)
        return response

    except pdfcrowd.Error as why:
        return HttpResponse(
            str(why),
            status=why.getStatusCode(),
            content_type='text/html'
        )

Create a corresponding HTML view that renders the invoice:

from django.shortcuts import render, get_object_or_404

def invoice_html(request, invoice_id):
    invoice = get_object_or_404(Invoice.objects.select_related('customer'), pk=invoice_id)
    return render(request, 'invoices/invoice.html', {
        'invoice': invoice,
        'line_items': invoice.items.all(),
    })
# urls.py
urlpatterns = [
    path('invoices/<int:invoice_id>/html/', invoice_html, name='invoice_html'),
    path('invoices/<int:invoice_id>/pdf/', generate_invoice_pdf, name='invoice_pdf'),
]

Other Conversion Methods

Besides URL conversion, the API supports converting HTML strings and local files.

Use convertStringToStream for rendering Django templates - ideal for documents like invoices or contracts that don't need a public URL:

from django.template.loader import render_to_string

html_content = render_to_string('invoices/invoice_pdf.html', {
    'invoice': invoice,
    'line_items': invoice.items.all(),
})
client.convertStringToStream(html_content, response)

Use convertFileToStream for processing uploaded HTML files:

if hasattr(uploaded_file, 'temporary_file_path'):
    client.convertFileToStream(uploaded_file.temporary_file_path(), response)
else:
    # Small files are kept in memory, use convertString instead
    client.convertStringToStream(uploaded_file.read().decode('utf-8'), response)

Note that when using convertString, any relative URLs for images or CSS won't resolve. Use convertUrl for pages with static assets, or add a <base> tag to your template (requires request in the template context):

<head>
    <base href="{{ request.scheme }}://{{ request.get_host }}/">
    ...
</head>

Adding a "Save as PDF" Button

Create a reusable include template for the PDF button:

<!-- templates/includes/pdf_button.html -->
<form method="post" action="{% url 'save_as_pdf' %}">
    {% csrf_token %}
    <input type="hidden" name="url" value="{{ request.build_absolute_uri }}">
    <input type="hidden" name="filename" value="{{ pdf_filename|default:'page.pdf' }}">
    <button type="submit">Save as PDF</button>
</form>

Then include it on any page that needs PDF export:

<!-- templates/invoices/invoice_detail.html -->
{% extends "base.html" %}

{% block content %}
    <h1>Invoice {{ invoice.number }}</h1>
    ...
    {% include "includes/pdf_button.html" with pdf_filename=invoice.number|add:".pdf" %}
{% endblock %}

Create a generic view that converts any URL:

from django.conf import settings

@require_POST
def save_as_pdf(request):
    try:
        client = pdfcrowd.HtmlToPdfClient('demo', 'demo')
        client.setPageSize('A4')

        # Pass session cookie for authenticated pages
        session_cookie = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
        if session_cookie:
            client.setCookies(f'{settings.SESSION_COOKIE_NAME}={session_cookie}')

        url = request.POST.get('url')
        filename = request.POST.get('filename', 'page.pdf')

        # Validate URL is from your domain
        if not url.startswith(request.build_absolute_uri('/')[:-1]):
            return HttpResponse('Invalid URL', status=400)

        response = HttpResponse(content_type='application/pdf')
        response['Content-Disposition'] = (
            "attachment; filename*=UTF-8''" +
            urllib.parse.quote(filename, safe='')
        )

        client.convertUrlToStream(url, response)
        return response

    except pdfcrowd.Error as why:
        return HttpResponse(
            str(why),
            status=why.getStatusCode(),
            content_type='text/html'
        )
# urls.py
urlpatterns = [
    path('save-as-pdf/', save_as_pdf, name='save_as_pdf'),
]

Now any page can include the PDF button where needed, with a custom filename passed via with.

The example above includes session cookie forwarding via setCookies(), which allows converting password-protected pages. You can pass multiple cookies if needed:

client.setCookies('sessionid=abc123; csrftoken=xyz789')

Configuration Best Practices

Store your API credentials in Django settings:

# settings.py
import os

PDFCROWD_USERNAME = os.environ.get('PDFCROWD_USERNAME')
PDFCROWD_API_KEY = os.environ.get('PDFCROWD_API_KEY')

Then use them in your views:

client = pdfcrowd.HtmlToPdfClient(
    settings.PDFCROWD_USERNAME,
    settings.PDFCROWD_API_KEY
)

Learn More