11 KiB
PDF Implementation Guide
This guide explains how to implement PDF generation (view, download, email) for all clinical documents using the reusable BasePDFGenerator service.
Overview
We've created a centralized PDF service (core/pdf_service.py) that provides:
- ✅ Tenant branding (logo + name)
- ✅ Arabic font support with proper text rendering
- ✅ Bilingual labels (English/Arabic)
- ✅ Consistent professional styling
- ✅ Email functionality with PDF attachment
- ✅ View inline or download options
Implementation Steps for Each Module
Step 1: Create PDF Generator Class
In each module's views.py, create a PDF generator class that extends BasePDFGenerator:
from core.pdf_service import BasePDFGenerator
class MedicalConsultationPDFGenerator(BasePDFGenerator):
"""PDF generator for Medical Consultation (MD-F-1)."""
def get_document_title(self):
"""Return document title in English and Arabic."""
consultation = self.document
return (
f"Medical Consultation - {consultation.patient.mrn}",
"استشارة طبية"
)
def get_pdf_filename(self):
"""Return PDF filename."""
consultation = self.document
return f"medical_consultation_{consultation.patient.mrn}_{consultation.consultation_date}.pdf"
def get_document_sections(self):
"""Return document sections to render."""
consultation = self.document
patient = consultation.patient
sections = []
# Patient Information Section
sections.append({
'heading_en': 'Patient Information',
'heading_ar': 'معلومات المريض',
'type': 'table',
'content': [
('Name', 'الاسم', f"{patient.first_name_en} {patient.last_name_en}",
f"{patient.first_name_ar} {patient.last_name_ar}" if patient.first_name_ar else ""),
('MRN', 'رقم السجل الطبي', patient.mrn, ""),
('Date of Birth', 'تاريخ الميلاد', patient.date_of_birth.strftime('%Y-%m-%d'), ""),
('Gender', 'الجنس', patient.get_sex_display(), ""),
]
})
# Consultation Details Section
sections.append({
'heading_en': 'Consultation Details',
'heading_ar': 'تفاصيل الاستشارة',
'type': 'table',
'content': [
('Date', 'التاريخ', consultation.consultation_date.strftime('%Y-%m-%d'), ""),
('Provider', 'مقدم الخدمة', consultation.provider.get_full_name(), ""),
('Chief Complaint', 'الشكوى الرئيسية', consultation.chief_complaint or 'N/A', ""),
]
})
# Add more sections as needed...
return sections
Step 2: Create PDF View
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404
from django.views import View
from core.mixins import TenantFilterMixin
class MedicalConsultationPDFView(LoginRequiredMixin, TenantFilterMixin, View):
"""Generate PDF for medical consultation."""
def get(self, request, pk):
"""Generate and return PDF."""
# Get consultation
consultation = get_object_or_404(
MedicalConsultation.objects.select_related(
'patient', 'provider', 'tenant'
),
pk=pk,
tenant=request.user.tenant
)
# Create PDF generator
pdf_generator = MedicalConsultationPDFGenerator(consultation, request)
# Get view mode from query parameter
view_mode = request.GET.get('view', 'download')
# Generate and return PDF
return pdf_generator.generate_pdf(view_mode=view_mode)
Step 3: Create Email PDF View
from django.contrib import messages
from django.shortcuts import redirect
from django.utils.translation import gettext as _
class MedicalConsultationEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View):
"""Email medical consultation PDF to patient."""
def post(self, request, pk):
"""Send PDF via email."""
# Get consultation
consultation = get_object_or_404(
MedicalConsultation.objects.select_related(
'patient', 'provider', 'tenant'
),
pk=pk,
tenant=request.user.tenant
)
# Get form data
email_address = request.POST.get('email_address', '').strip()
custom_message = request.POST.get('email_message', '').strip()
if not email_address:
messages.error(request, _('Email address is required.'))
return redirect('medical:consultation_detail', pk=pk)
# Create PDF generator
pdf_generator = MedicalConsultationPDFGenerator(consultation, request)
# Prepare email content
subject = f"Medical Consultation - {consultation.patient.mrn}"
body = f"""
Dear {consultation.patient.first_name_en} {consultation.patient.last_name_en},
Please find attached your medical consultation details.
Consultation Date: {consultation.consultation_date.strftime('%Y-%m-%d')}
Provider: {consultation.provider.get_full_name()}
Best regards,
{consultation.tenant.name}
"""
# Send email
success, message = pdf_generator.send_email(
email_address=email_address,
subject=subject,
body=body,
custom_message=custom_message
)
if success:
messages.success(request, _('PDF sent to %(email)s successfully!') % {'email': email_address})
else:
messages.error(request, _('Failed to send email: %(error)s') % {'error': message})
return redirect('medical:consultation_detail', pk=pk)
Step 4: Add URL Routes
In the module's urls.py:
urlpatterns = [
# ... existing routes ...
path('<uuid:pk>/pdf/', views.MedicalConsultationPDFView.as_view(), name='consultation_pdf'),
path('<uuid:pk>/email-pdf/', views.MedicalConsultationEmailPDFView.as_view(), name='consultation_email_pdf'),
]
Step 5: Update Detail Template
Add PDF options dropdown to the detail template:
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-file-pdf me-1"></i>{% trans "PDF Options" %}
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="{% url 'medical:consultation_pdf' consultation.pk %}?view=inline" target="_blank">
<i class="fas fa-eye me-2"></i>{% trans "View PDF" %}
</a>
</li>
<li>
<a class="dropdown-item" href="{% url 'medical:consultation_pdf' consultation.pk %}" target="_blank">
<i class="fas fa-download me-2"></i>{% trans "Download PDF" %}
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#emailPdfModal">
<i class="fas fa-envelope me-2"></i>{% trans "Email PDF to Patient" %}
</a>
</li>
</ul>
</div>
<!-- Email PDF Modal -->
<div class="modal fade" id="emailPdfModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="{% url 'medical:consultation_email_pdf' consultation.pk %}">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-envelope me-2"></i>{% trans "Email PDF to Patient" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="email_address" class="form-label">{% trans "Email Address" %}</label>
<input type="email" name="email_address" id="email_address" class="form-control"
value="{{ consultation.patient.email }}" required>
</div>
<div class="mb-3">
<label for="email_message" class="form-label">{% trans "Additional Message (Optional)" %}</label>
<textarea name="email_message" id="email_message" class="form-control" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-paper-plane me-1"></i>{% trans "Send Email" %}
</button>
</div>
</form>
</div>
</div>
</div>
Modules to Implement
Priority 1 (Week 1)
- ✅ Appointments - Already implemented
- ✅ Finance/Invoices - Already implemented
- ⏳ Medical Consultation (MD-F-1)
- ⏳ Medical Follow-up (MD-F-2)
- ⏳ Nursing Encounter
Priority 2 (Week 2)
- ⏳ OT Consultation (OT-F-1)
- ⏳ OT Session (OT-F-3)
- ⏳ SLP Consultation (SLP-F-1)
Priority 3 (Week 3)
- ⏳ SLP Assessment (SLP-F-2)
- ⏳ SLP Intervention (SLP-F-3)
- ⏳ ABA Consult (ABA-F-1)
Priority 4 (Week 4)
- ⏳ Consent Forms
- ⏳ Patient Summary
Tips for Implementation
-
Keep sections modular: Each clinical form has different sections, so structure them clearly in
get_document_sections() -
Handle optional fields: Use
or 'N/A'for fields that might be empty -
Format dates consistently: Use
strftime('%Y-%m-%d')for dates -
Test Arabic text: Make sure Arabic names and content display correctly
-
Check permissions: Ensure only authorized users can generate/email PDFs
-
Validate email addresses: Always validate before sending emails
-
Handle errors gracefully: Wrap email sending in try-except blocks
Testing Checklist
For each implementation, test:
- PDF generates without errors
- Tenant logo displays correctly
- Arabic text renders properly
- All sections display complete data
- View inline opens in browser
- Download saves file correctly
- Email sends successfully
- Email contains correct PDF attachment
- Custom message appears in email
- Permissions are enforced
Next Steps
- Start with Medical Consultation (most used)
- Test thoroughly
- Use as template for other modules
- Iterate and improve based on feedback