This repository demonstrates a pattern that should feel natural to Django teams:
- define accessible document structure once in Django templates
- reuse that semantic structure for the normal browser page
- send the same structure to Fullbleed for a real tagged PDF export
The example is an account summary because it mirrors a common SaaS workflow: a customer reviews information in the browser, then downloads the same information as a PDF for accounting, procurement, or compliance purposes.
The app has three key routes:
/shows the project framing and a fixture-backed account summary/accounts/northwind-analytics/renders the user-facing browser page/accounts/northwind-analytics/export/pdf/renders a real tagged PDF using Fullbleed
This is not a mocked export route. The PDF endpoint generates a real PDF response with:
pdf_profile="tagged"document_titleanddocument_lang- a footer generated by Fullbleed
- a seed verification pass surfaced in the response headers
The important implementation detail is the shared template boundary:
billing/templates/billing/_account_summary.html
The semantic partial. It owns the headings, table structure, contact details, and note sections.billing/templates/billing/account_summary_page.html
The browser page layout that includes the shared partial directly.billing/templates/billing/account_summary_pdf.html
The PDF document wrapper that also includes the same shared partial.billing/templates/billing/account_summary_pdf.css
The CSS payload sent to Fullbleed alongside the PDF HTML document.billing/services/fullbleed.py
The single Fullbleed integration boundary.
The export route stays small on purpose:
document_html = render_to_string("billing/account_summary_pdf.html", context, request=request)
document_css = render_to_string("billing/account_summary_pdf.css", context, request=request)
renderer = FullbleedRenderer()
fullbleed_request = renderer.build_request(
summary=summary,
html=document_html,
css=document_css,
)
pdf_bytes = renderer.render_tagged_pdf(fullbleed_request)That is the pattern in one glance: Django owns semantics, Fullbleed owns PDF generation.
Django teams already know how to build accessible HTML with semantic headings, tables, landmarks, readable order, and clear labels. The accessibility gap usually appears when the same content has to leave the browser as a PDF.
A lot of teams solve that by creating a second export-only rendering layer. That usually means:
- duplicated structure
- duplicated styling decisions
- drift between web and PDF output
- accessibility bugs that only exist in the export path
This demo argues for a cleaner split:
- keep document semantics in Django templates, where the team already works
- reuse those semantics for both the web UI and the export path
- let Fullbleed turn that HTML into a tagged PDF instead of rebuilding the document in a separate PDF-only layer
That gives the team one place to review structure and accessibility.
The shared account-summary partial is intentionally semantic:
- it uses a real
articlewith nestedheaderandsectionstructure - heading order is explicit (
h1followed byh2sections) - the charge breakdown is a proper data table with a caption, column headers, row headers, and a footer total
- contact information uses
address - notes stay in the same reading order for both screen and export paths
When that structure is passed to Fullbleed, the PDF route preserves the same document logic instead of inventing a second one.
The service layer uses Fullbleed directly:
from fullbleed import PdfEngine
engine = PdfEngine(
document_title=request.document_title,
document_lang="en",
pdf_profile="tagged",
margin="0.5in",
footer_each="Generated with Fullbleed — Page {page} of {pages}",
footer_font_size=8,
footer_color="#5d6873",
footer_y_from_bottom="0.25in",
)
pdf_bytes = engine.render_pdf(request.html, request.css)The demo also runs verify_pdf_ua_seed() on the generated PDF and returns the result through the X-Fullbleed-Seed-Verify response header.
Fullbleed expects HTML and CSS as separate inputs. It does not consume embedded <style> blocks from the HTML payload. That is why this demo renders:
account_summary_pdf.htmlaccount_summary_pdf.css
separately before calling the engine.
That detail is easy to miss and worth preserving in any real integration.
The demo uses one small model pair:
AccountSummaryChargeLineItem
The fixture lives at billing/fixtures/demo.json and seeds a realistic account summary for Northwind Analytics.
python3 -m pip install -r requirements.txt
python3 manage.py migrate
python3 manage.py loaddata billing/fixtures/demo.json
python3 manage.py runserverThen open:
http://127.0.0.1:8000/http://127.0.0.1:8000/accounts/northwind-analytics/http://127.0.0.1:8000/accounts/northwind-analytics/export/pdf/
The PDF route should download a real tagged PDF.
python3 manage.py testThe test suite checks that:
- the overview page renders
- the browser page renders shared content
- the PDF route returns
application/pdf - the response advertises
X-Fullbleed-Pdf-Profile: tagged - the seed verification passes
It gives Django teams a clean division of labor:
- Django templates remain the source of truth for document structure
- Fullbleed handles the hard PDF-specific problems
- accessibility is reviewed once at the semantic HTML layer instead of in parallel rendering stacks
That makes the Fullbleed pitch to Django developers much clearer: use the same accessible component/template thinking for the page your users see and the PDF they download.