Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11To convert Django HTML to PDF, render the template to a string, pass that string to a PDF engine such as xhtml2pdf or WeasyPrint, resolve static and media files explicitly, and return the resulting bytes from a Django view with content_type="application/pdf". The renderer—not Django itself—performs the HTML-to-PDF conversion.
The conversion pipeline
- Load and render a Django template with its context.
- Give the HTML to a PDF renderer.
- Resolve stylesheets, images, and fonts through a known filesystem path or URL callback.
- Write the generated bytes to an
HttpResponse. - Test pagination, fonts, links, images, and long tables in your project.
Keep the template used for PDF output deliberately print-oriented. Browser-only assumptions—responsive breakpoints, JavaScript layout changes, cross-origin assets, or authenticated URLs—may not work in a server-side renderer.
Option 1: xhtml2pdf in a Django view
xhtml2pdf is a pure-Python converter built with ReportLab, html5lib, and pypdf. It supports HTML5, CSS 2.1, and part of CSS 3, making it suitable for invoices, receipts, letters, and other controlled layouts. Its documented entry point is pisa.CreatePDF(src, dest=...).
Install the dependencies
python -m pip install Django xhtml2pdf
Create the PDF view
from io import BytesIO
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.template.loader import get_template
from xhtml2pdf import pisa
from .models import Invoice
def invoice_pdf(request, invoice_id):
invoice = get_object_or_404(Invoice, pk=invoice_id)
html = get_template("billing/invoice.html").render({"invoice": invoice})
output = BytesIO()
status = pisa.CreatePDF(
src=html,
dest=output,
path="/srv/app/templates/",
)
if status.err:
return HttpResponse("PDF generation failed", status=500)
response = HttpResponse(output.getvalue(), content_type="application/pdf")
response["Content-Disposition"] = (
f'attachment; filename="invoice-{invoice_id}.pdf"'
)
return response
The path argument supplies a base directory for relative resources. Adapt it to your deployment; it is not a universal path. For inline browser viewing, use inline instead of attachment in the disposition value.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →#1 Best Overall
Add the URL
from django.urls import path
from .views import invoice_pdf
urlpatterns = [
path("invoices/<int:invoice_id>/pdf/", invoice_pdf, name="invoice-pdf"),
]
Use a PDF-friendly template
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
@page { size: A4; margin: 18mm; }
body { font-family: DejaVu Sans, sans-serif; font-size: 10pt; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #bbb; padding: 5px; }
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
</style>
</head>
<body>
<h1>Invoice {{ invoice.number }}</h1>
<p>Issued: {{ invoice.issued_at|date:"Y-m-d" }}</p>
<table>
<thead><tr><th>Description</th><th>Amount</th></tr></thead>
<tbody>
{% for line in invoice.lines.all %}
<tr><td>{{ line.description }}</td><td>{{ line.amount }}</td></tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
Django auto-escapes template variables in normal HTML contexts. Do not apply safe, mark_safe, or disabled autoescaping to user-authored text unless it has been sanitized for the output format.
Static files, media, fonts, and URLs
A PDF process does not share the browser’s document context. Relative URLs must resolve through a deterministic base path or callback. For xhtml2pdf, link_callback can rewrite a URI; the resulting path is still checked by the renderer’s resource policy.
Map Django assets to approved files
from pathlib import Path
from django.conf import settings
from django.contrib.staticfiles import finders
def link_callback(uri, rel):
if uri.startswith(settings.STATIC_URL):
path = finders.find(uri[len(settings.STATIC_URL):])
elif uri.startswith(settings.MEDIA_URL):
path = Path(settings.MEDIA_ROOT) / uri[len(settings.MEDIA_URL):]
else:
path = None
if not path or not Path(path).exists():
raise FileNotFoundError(f"PDF asset not found: {uri}")
return str(path)
Pass link_callback=link_callback to CreatePDF. In production, ensure collectstatic has populated the directory and that uploaded media paths cannot escape MEDIA_ROOT. Remote assets should use an explicit allowlist rather than arbitrary user-provided URLs.
Absolute URLs when required
If a renderer must fetch an HTTP asset, use a configured, canonical host and credentials that are safe for the PDF worker. A renderer may not have the user’s session cookie, and private URLs can fail or accidentally expose data. Prefer local files for stylesheets, images, and fonts whenever possible.
Rank #2
Choosing a renderer
| Renderer | Best fit | Important trade-offs |
|---|---|---|
| xhtml2pdf | Python-native invoices and documents with a constrained layout | CSS support is HTML5, CSS 2.1, and some CSS 3; responsive media-query conditions are ignored, although all, print, and pdf media types are honored. Asset paths and policies require explicit configuration. |
| WeasyPrint | CSS paged-media rules, hyperlinks, bookmarks, and attachments | Verify the installed release’s feature set and operating-system libraries before deployment. Compare its Python and system dependencies with your container image. |
| wkhtmltopdf via django-wkhtmltopdf | Systems already standardized on wkhtmltopdf | The Django wrapper provides PDFTemplateView, but the engine, JavaScript behavior, maintenance, and packaging should be evaluated for a new project. |
Choose by supported CSS and paged-media rules, JavaScript or browser fidelity, asset and font resolution, SSRF controls, Python and operating-system dependencies, container complexity, concurrent-request behavior, and maintenance. Do not select from a benchmark that was not run against your templates; measure your own workload.
WeasyPrint example
WeasyPrint’s API documentation describes broad W3C CSS support and PDF output with hyperlinks, bookmarks, and attachments. A minimal view is:
from io import BytesIO
from django.http import HttpResponse
from django.template.loader import render_to_string
from weasyprint import HTML
def report_pdf(request):
html = render_to_string("reports/report.html", {"title": "Report"})
pdf_bytes = HTML(string=html, base_url=request.build_absolute_uri("/")).write_pdf()
response = HttpResponse(pdf_bytes, content_type="application/pdf")
response["Content-Disposition"] = 'attachment; filename="report.pdf"'
return response
Use a carefully controlled base_url. It can make relative resources convenient, but allowing arbitrary remote content turns PDF generation into a server-side request surface.
Security controls you should not skip
xhtml2pdf’s security documentation states that a document can determine which files the converter opens and which hosts it contacts. Its default policy refuses destinations resolving to internal addresses and local reads outside the document directory, while public HTTP(S) remains available.
- Keep a restrictive
resource_policy; grant only required asset roots and hosts. - Block loopback, link-local, private-network, metadata-service, and other internal destinations.
- Set network timeouts, maximum HTML and output sizes, and request or job limits.
- Validate uploaded templates and rich-text fields. Treat stored HTML as untrusted.
- Do not use
safeormark_safeto silence escaping errors. - Run conversion in an isolated worker or container when documents contain user-controlled content.
Django’s security guidance covers auto-escaping, uploaded files, and the risks of disabled escaping; apply those controls before handing HTML to any renderer.
Testing and troubleshooting
Blank or missing images and CSS
Cause: a relative URL has no usable base, collectstatic was not run, or the callback returned a nonexistent path. Log every resolved URI, verify the file inside the worker/container, and pass path, base_url, or link_callback explicitly.
Fonts are substituted or text wraps differently
Install the required fonts in the image, reference them through approved local paths, and test on the same operating-system image used in production. Font availability differs between a laptop and a container.
CSS looks correct in Chrome but not in the PDF
PDF engines are not identical browsers. Reduce layout to the engine’s supported CSS, add print-specific rules, and avoid relying on ignored responsive media-query conditions in xhtml2pdf. If paged-media features are central, evaluate WeasyPrint.
Free tools Windows power users keep installed
One-click scans. No signup required.
Remote images time out or leak data
Use local copies or an allowlist, set timeouts, and reject private or internal addresses. Do not make the resource policy permissive just to hide an asset-resolution bug.
Long tables split badly
Use a real thead, print-friendly table styles, and row rules such as page-break-inside: avoid. Add regression fixtures with enough rows to cross several pages.
The response is slow under load
Conversion is CPU- and I/O-intensive. Keep it out of latency-sensitive request paths when documents are large, queue jobs, cap concurrency, cache immutable PDFs, and measure memory, duration, failure rate, and output size using your own templates.
Regression checklist
- Page breaks occur at intentional boundaries.
- Fonts, logos, uploaded images, and CSS load in the production image.
- Links point to the intended destinations.
- Long tables repeat headers and do not overlap footers.
- Dates, currency, time zones, and localization are correct.
- Unauthorized users cannot request another user’s document.
- Malformed HTML and failed resources return a controlled error.
- Generated files stay within configured size and time limits.
Or skip the browser setup
If your requirement is simply a reliable screenshot or PDF of a URL rather than rendering a Django template inside your application, ScreenshotNeo provides a website screenshot API. Its cleanup step accepts cookie-consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. It also offers an MCP server for AI agents, including Claude and Cursor.
For a PDF or image generated from a deployed page, see the ScreenshotNeo API documentation and call:
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
There is a free allowance of 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account to try it.
FAQ
Can Django itself create a PDF?
No. Django renders the HTML and serves the response; a separate renderer creates the PDF bytes.
Which engine should I start with?
Start with xhtml2pdf for a Python-native, controlled document. Evaluate WeasyPrint for paged-media navigation features or an existing wkhtmltopdf standard.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Why does a PDF need a resource policy?
The renderer may read files and contact hosts while resolving assets. A policy limits that access and reduces accidental data exposure and SSRF risk.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

