Add support for PDF view

This commit is contained in:
2025-10-15 11:12:43 +02:00
parent 70fa1168ea
commit 8fe744afe1
5 changed files with 198 additions and 1 deletions

View File

@@ -6,7 +6,7 @@ from functools import partial
from typing import Annotated
import starlette.status as status
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi import Depends, FastAPI, HTTPException, Request, Response
from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
@@ -21,6 +21,7 @@ from meal_manager.models import (
Subscription,
TeamRegistration,
)
from meal_manager.pdf import build_dinner_overview_pdf
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
@@ -332,3 +333,21 @@ async def delete_team_registration(
session.delete(session.scalars(statement).one())
session.commit()
return RedirectResponse(url=f"/event/{event_id}", status_code=status.HTTP_302_FOUND)
@app.get("/event/{event_id}/pdf")
def get_event_attendance_pdf(event_id: int, session: SessionDep):
statement = select(Event).where(Event.id == event_id)
event = session.scalars(statement).one()
pdf_buffer = build_dinner_overview_pdf(event)
headers = {
"Content-Disposition": f"inline; filename=attendance_event_{event_id}.pdf"
}
return Response(
content=pdf_buffer.getvalue(),
media_type="application/pdf",
headers=headers
)

101
src/meal_manager/pdf.py Normal file
View File

@@ -0,0 +1,101 @@
from io import BytesIO
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, portrait
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle
from meal_manager.models import Event
def build_dinner_overview_pdf(event: Event) -> BytesIO:
"""Build a PDF with an overview of the event's attendance."""
# Create an in-memory PDF
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=portrait(A4), topMargin=30, bottomMargin=30, leftMargin=40, rightMargin=40)
styles = getSampleStyleSheet()
elements = []
# Title
title_style = styles["Title"]
title_style.fontSize = 16
title_style.spaceAfter = 20
elements.append(Paragraph(f"Anwesenheitsliste {event.title} ({event.event_time.date().strftime('%d.%m.%y')})",
title_style))
elements.append(Spacer(1, 20))
# Team overview section
elements.append(Paragraph("Dienste", styles["Heading2"]))
elements.append(Spacer(1, 12))
team_data = [["Team", "Personen"]]
team_types = {
"Kochen": [r for r in event.team if r.work_type == "cooking"],
"Abwaschen": [r for r in event.team if r.work_type == "dishes"],
"Tische decken": [r for r in event.team if r.work_type == "tables"]
}
for team_name, registrations in team_types.items():
members = ", ".join(r.person_name for r in registrations)
team_data.append([team_name, members])
team_table = Table(team_data, repeatRows=1, colWidths=[100, '*'])
team_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor('#E8E8E8')),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor('#A0A0A0')),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 10),
("BOTTOMPADDING", (0, 0), (-1, -1), 8),
("TOPPADDING", (0, 0), (-1, -1), 8),
]))
elements.append(team_table)
elements.append(Spacer(1, 25))
sum_adults = 0
sum_children = 0
sum_small_children = 0
for r in event.registrations:
sum_adults += r.num_adult_meals
sum_children += r.num_children_meals
sum_small_children += r.num_small_children_meals
# Attendance section
elements.append(Paragraph("Teilnehmende", styles["Heading2"]))
elements.append(Paragraph(f"Gesamt: {sum_adults} Erwachsene, {sum_children} Kinder, {sum_small_children} Kleinkinder"))
elements.append(Spacer(1, 12))
# Table header
data = [["Haushalt", "Erwachsene", "Kinder >7", "Kinder <7", "Kommentar", "Anwesend?"]]
# Table rows
for r in event.registrations:
data.append([
r.household.name,
r.num_adult_meals,
r.num_children_meals,
r.num_small_children_meals,
Paragraph(r.comment),
""
])
for _ in range(5):
data.append([""] * 6)
# Create table
table = Table(data, repeatRows=1, colWidths=[120, 70, 60, 60, '*', 65])
table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor('#E8E8E8')),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor('#A0A0A0')),
("ALIGN", (1, 1), (-2, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 10),
("BOTTOMPADDING", (0, 0), (-1, -1), 8),
("TOPPADDING", (0, 0), (-1, -1), 8),
]))
elements.append(table)
doc.build(elements)
buffer.seek(0)
return buffer

View File

@@ -34,6 +34,9 @@
<i class="bi bi-book"></i> Original Rezept ansehen
</a>
{% endif %}
<a href="/event/{{event.id}}/pdf" class="btn btn-secondary mb-2 w-100" target="_blank">
<i class="bi bi-printer m-2"></i> Druckansicht
</a>
{% if user and user.admin %}
<button type="button" class="btn btn-danger mb-2 w-100" data-bs-toggle="modal" data-bs-target="#deleteEvent">
Event Löschen