Recto personnalisable (fond image + champs positionnés : nom, poste(s)
affectés, catégorie, rôle) via un éditeur visuel glisser-déposer ; verso
fixe, la même image pour tous les bénévoles. Nouvel écran « Badges »
(COORDINATION uniquement, /badges).
Backend : modèle BadgeTemplate/BadgeField (migration 0011), génération
PDF isolée (app/badges/pdf.py, ReportLab — planches recto suivies des
planches verso correspondantes pour une impression recto-verso), route
POST /editions/{id}/badges/generate, tests (module pur + API).
Frontend : client HTTP (upload multipart, réponses binaires images/PDF),
client mock (mode démonstration), éditeur de positionnement en
millimètres (coordonnées partagées avec le backend), écran de génération
avec sélection des bénévoles et téléchargement.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
63 lines
1.9 KiB
Python
63 lines
1.9 KiB
Python
"""Génération des planches de badges (module pur, sans base)."""
|
||
|
||
from app.badges.pdf import (
|
||
BadgeFieldSpec,
|
||
BadgeSpec,
|
||
_cell_origin,
|
||
_grid_size,
|
||
generate_badge_sheets_pdf,
|
||
)
|
||
|
||
|
||
def _badge(text="Alex Martin") -> BadgeSpec:
|
||
return BadgeSpec(
|
||
fields=(
|
||
BadgeFieldSpec(
|
||
text=text,
|
||
x_mm=5,
|
||
y_mm=5,
|
||
width_mm=60,
|
||
height_mm=10,
|
||
font_size=12,
|
||
align="LEFT",
|
||
bold=True,
|
||
color="#000000",
|
||
),
|
||
)
|
||
)
|
||
|
||
|
||
def test_grid_size_fits_a4_with_cr80_badges():
|
||
# A4 (210 × 297 mm), marge 10 mm, gouttière 4 mm, badge CR80 85,6 × 54 mm
|
||
# ⇒ 2 colonnes (190 mm utiles / 89,6 mm par badge+gouttière) x 4 rangées
|
||
# (277 mm utiles / 58 mm par badge+gouttière).
|
||
assert _grid_size() == (2, 4)
|
||
|
||
|
||
def test_cell_origin_places_badges_left_to_right_top_to_bottom():
|
||
cols, _ = _grid_size()
|
||
x0, y0 = _cell_origin(0, cols)
|
||
x1, _ = _cell_origin(1, cols)
|
||
_, y_row2 = _cell_origin(cols, cols)
|
||
assert x1 > x0 # la 2e colonne est à droite de la 1re
|
||
assert y_row2 < y0 # la 2e rangée est plus bas (repère PDF : Y croît vers le haut)
|
||
|
||
|
||
def test_generates_a_valid_pdf_for_a_single_badge():
|
||
pdf = generate_badge_sheets_pdf([_badge()], front_image=None, back_image=None)
|
||
assert pdf.startswith(b"%PDF")
|
||
assert pdf.rstrip().endswith(b"%%EOF")
|
||
|
||
|
||
def test_more_badges_than_one_sheet_produces_a_larger_pdf():
|
||
cols, rows = _grid_size()
|
||
per_page = cols * rows
|
||
one_page = generate_badge_sheets_pdf(
|
||
[_badge() for _ in range(per_page)], front_image=None, back_image=None
|
||
)
|
||
# Un badge de plus déborde sur une 2e planche (recto + verso en plus).
|
||
two_pages = generate_badge_sheets_pdf(
|
||
[_badge() for _ in range(per_page + 1)], front_image=None, back_image=None
|
||
)
|
||
assert len(two_pages) > len(one_page)
|