Overlay Django model data on government PDF forms
The Challenge
Fill official OCDD government forms (OCDDProgressNoteSingleShiftNotFillable021225.pdf) with Django ProgressNoteEntry data:
- Overlay on real PDF template (not HTML recreation)
- Works on NixOS dev + Ubuntu Apache2/uWSGI prod
- No WeasyPrint (GTK/Pango nightmare on NixOS)
- Visual coordinate tweaking (no parsing tools)
- Graceful empty field handling
Production File Structure
static/
└── progress_notes/
└── forms/
└── OCDDProgressNoteSingleShiftNotFillable021225.pdf
python manage.py collectstatic
Core Implementation
1. build_overlay_pdf() - Dynamic Content Only
def build_overlay_pdf(entry) -> BytesIO:
pdf = FPDF(format="Letter")
pdf.add_page()
pdf.set_margins(left=15, top=15, right=15)
pdf.set_font("Arial", "", 10)
if entry.beneficiary_name:
pdf.set_xy(45, 33)
pdf.cell(60, 6, entry.beneficiary_name)
pdf.set_xy(130, 33)
pdf.cell(40, 6, entry.date_of_service.strftime("%m/%d/%Y"))
pdf.set_xy(13, 52)
pdf.cell(80, 6, entry.provider_name or "")
if entry.start_time:
pdf.set_xy(158, 52)
pdf.cell(30, 6, entry.start_time.strftime("%I:%M %p"))
pdf_bytes = pdf.output(dest="S")
if isinstance(pdf_bytes, str):
pdf_bytes = pdf_bytes.encode('latin1', 'replace')
buf = BytesIO(pdf_bytes)
buf.seek(0)
return buf
2. generate_pdf() - Merge + Serve
@login_required
def generate_pdf(request, entry_id):
entry = ProgressNoteEntry.objects.get(id=entry_id)
overlay_buf = build_overlay_pdf(entry)
template_path = staticfiles_storage.path(
"progress_notes/forms/OCDDProgressNoteSingleShiftNotFillable021225.pdf"
)
base_reader = PdfReader(open(template_path, 'rb'))
overlay_reader = PdfReader(overlay_buf)
base_page = base_reader.pages
overlay_page = overlay_reader.pages
base_page.merge_page(overlay_page)
writer = PdfWriter()
writer.add_page(base_page)
out_buf = BytesIO()
writer.write(out_buf)
out_buf.seek(0)
response = HttpResponse(out_buf, content_type="application/pdf")
response["Content-Disposition"] = f'attachment; filename="progress_note_{entry_id}.pdf"'
return response
Positioning Workflow
pdf.set_xy(X, Y)
🔼 Up: Y-5 🔽 Down: Y+5
⬅️ Left: X-5 ➡️ Right: X+5
- Generate PDF
- Zoom 200% in viewer
- Tweak by 5pt → repeat
Install
pip install fpdf2 pypdf
Pure Python. Pixel-perfect forms. Production ready.