pdf-quiz-generator/backend/app/services/certificate_service.py
Daniel db98c6ae69 Add certificate generation, SCORM support, QTI export/import
- Certificate: PDF generated on course completion, download from course page
- SCORM: upload ZIP packages as lesson type, served in iframe, manifest parsed
- QTI 2.1: export selected/all questions as XML, import QTI files into bank
- Uses fpdf2 for certificate PDF generation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:30:09 +02:00

87 lines
2.7 KiB
Python

"""Generate PDF completion certificates for courses."""
import os
import uuid
from datetime import datetime
from fpdf import FPDF
def generate_certificate(
student_name: str,
course_title: str,
completed_at: datetime,
upload_dir: str,
) -> str:
"""Generate a PDF certificate and return the file path relative to uploads."""
cert_dir = os.path.join(upload_dir, "certificates")
os.makedirs(cert_dir, exist_ok=True)
filename = f"cert_{uuid.uuid4().hex[:12]}.pdf"
filepath = os.path.join(cert_dir, filename)
pdf = FPDF(orientation="L", unit="mm", format="A4")
pdf.set_auto_page_break(auto=False)
pdf.add_page()
w, h = 297, 210 # A4 landscape
# Border
pdf.set_draw_color(26, 86, 219)
pdf.set_line_width(2)
pdf.rect(10, 10, w - 20, h - 20)
pdf.set_line_width(0.5)
pdf.rect(14, 14, w - 28, h - 28)
# Header
pdf.set_font("Helvetica", "B", 32)
pdf.set_text_color(26, 86, 219)
pdf.set_y(35)
pdf.cell(w, 12, "Certificate of Completion", align="C", new_x="LMARGIN", new_y="NEXT")
# Decorative line
pdf.set_draw_color(26, 86, 219)
pdf.set_line_width(0.8)
pdf.line(w / 2 - 60, 52, w / 2 + 60, 52)
# "This certifies that"
pdf.set_font("Helvetica", "", 14)
pdf.set_text_color(100, 100, 100)
pdf.set_y(62)
pdf.cell(w, 8, "This certifies that", align="C", new_x="LMARGIN", new_y="NEXT")
# Student name
pdf.set_font("Helvetica", "B", 28)
pdf.set_text_color(30, 30, 30)
pdf.set_y(75)
pdf.cell(w, 14, student_name, align="C", new_x="LMARGIN", new_y="NEXT")
# "has successfully completed"
pdf.set_font("Helvetica", "", 14)
pdf.set_text_color(100, 100, 100)
pdf.set_y(96)
pdf.cell(w, 8, "has successfully completed the course", align="C", new_x="LMARGIN", new_y="NEXT")
# Course title
pdf.set_font("Helvetica", "BI", 22)
pdf.set_text_color(26, 86, 219)
pdf.set_y(110)
pdf.cell(w, 12, course_title, align="C", new_x="LMARGIN", new_y="NEXT")
# Date
date_str = completed_at.strftime("%B %d, %Y")
pdf.set_font("Helvetica", "", 12)
pdf.set_text_color(100, 100, 100)
pdf.set_y(135)
pdf.cell(w, 8, f"Completed on {date_str}", align="C", new_x="LMARGIN", new_y="NEXT")
# Footer — platform name
pdf.set_font("Helvetica", "B", 11)
pdf.set_text_color(26, 86, 219)
pdf.set_y(160)
pdf.cell(w, 8, "PedsHub", align="C", new_x="LMARGIN", new_y="NEXT")
pdf.set_font("Helvetica", "", 9)
pdf.set_text_color(150, 150, 150)
pdf.cell(w, 5, "Pediatric Medical Learning Platform", align="C", new_x="LMARGIN", new_y="NEXT")
pdf.output(filepath)
return f"/uploads/certificates/{filename}"