Refonte du créateur de badges et nombre de badges par bénévole
Créateur de badges refait d'après la maquette : champs sur le recto et le verso (BadgeField.face, verso placé en miroir dans le PDF pour le recto-verso), fond de zone, réduction automatique du texte, photo ovale, initiales sans photo, nom sur deux lignes (migration 0026) ; éditeur en trois colonnes (champs, aperçu recto/verso avec de vrais bénévoles, inspecteur), formats prédéfinis, annuler, flèches, zoom, repères de découpe, dépôt d'image sur l'aperçu, contrôle de tous les badges avant génération. Nombre de badges par bénévole (Volunteer.badge_count, 1 par défaut, migration 0027) : champ sur la fiche, autant d'exemplaires sur les planches, total affiché à la génération, colonne dans l'export Excel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1a23881e81
commit
ced0855d47
@@ -247,3 +247,154 @@ def test_generating_with_photo_and_free_text_fields(client, edition, session):
|
||||
resp = client.post(f"{_badges_url(edition)}/generate", json={})
|
||||
assert resp.status_code == 200
|
||||
assert resp.content.startswith(b"%PDF")
|
||||
|
||||
|
||||
def _field(**kw):
|
||||
return {"x_mm": 0, "y_mm": 0, "width_mm": 10, "height_mm": 10, **kw}
|
||||
|
||||
|
||||
def test_new_field_attributes_default_and_round_trip(client, edition):
|
||||
resp = client.put(
|
||||
f"{_badges_url(edition)}/template/fields",
|
||||
json={
|
||||
"fields": [
|
||||
_field(field_type="FULL_NAME"),
|
||||
_field(
|
||||
field_type="PHOTO", face="BACK", photo_shape="OVAL",
|
||||
bg_color="#FFD23F", auto_fit=True,
|
||||
),
|
||||
]
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
by_face = {f["face"]: f for f in resp.json()}
|
||||
front, back = by_face["FRONT"], by_face["BACK"]
|
||||
assert front["field_type"] == "FULL_NAME"
|
||||
assert front["bg_color"] is None
|
||||
assert front["auto_fit"] is False
|
||||
assert front["photo_shape"] == "RECT"
|
||||
assert back["field_type"] == "PHOTO"
|
||||
assert back["photo_shape"] == "OVAL"
|
||||
assert back["bg_color"] == "#FFD23F"
|
||||
assert back["auto_fit"] is True
|
||||
|
||||
again = client.get(f"{_badges_url(edition)}/template").json()["fields"]
|
||||
assert {(f["face"], f["field_type"]) for f in again} == {
|
||||
("FRONT", "FULL_NAME"), ("BACK", "PHOTO"),
|
||||
}
|
||||
|
||||
|
||||
def test_same_field_type_on_front_and_back_is_allowed(client, edition):
|
||||
resp = client.put(
|
||||
f"{_badges_url(edition)}/template/fields",
|
||||
json={
|
||||
"fields": [
|
||||
_field(field_type="FREE_TEXT", face="FRONT", text="Recto"),
|
||||
_field(field_type="FREE_TEXT", face="BACK", text="Verso"),
|
||||
]
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert {(f["face"], f["text"]) for f in resp.json()} == {
|
||||
("FRONT", "Recto"), ("BACK", "Verso"),
|
||||
}
|
||||
|
||||
|
||||
def test_same_field_type_twice_on_the_back_is_rejected(client, edition):
|
||||
resp = client.put(
|
||||
f"{_badges_url(edition)}/template/fields",
|
||||
json={
|
||||
"fields": [
|
||||
_field(field_type="ROLE", face="BACK"),
|
||||
_field(field_type="ROLE", face="BACK"),
|
||||
]
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 422
|
||||
assert resp.json()["detail"]["code"] == "badge.duplicate_field_type"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("color", ["red", "#FFF", "#GGGGGG", "FFD23F", "#FFD23F0"])
|
||||
def test_invalid_background_color_is_rejected(client, edition, color):
|
||||
resp = client.put(
|
||||
f"{_badges_url(edition)}/template/fields",
|
||||
json={"fields": [_field(field_type="ROLE", bg_color=color)]},
|
||||
)
|
||||
assert resp.status_code == 422
|
||||
assert resp.json()["detail"]["code"] == "badge.invalid_color"
|
||||
|
||||
|
||||
def test_generating_with_back_fields(client, edition, session, monkeypatch):
|
||||
import app.api.badges as badges_api
|
||||
|
||||
captured = {}
|
||||
original = badges_api.generate_badge_sheets_pdf
|
||||
|
||||
def spy(badges, *args, **kwargs):
|
||||
captured["badges"] = badges
|
||||
return original(badges, *args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(badges_api, "generate_badge_sheets_pdf", spy)
|
||||
resp = client.put(
|
||||
f"{_badges_url(edition)}/template/fields",
|
||||
json={
|
||||
"fields": [
|
||||
_field(field_type="FULL_NAME", width_mm=60, font_size=12),
|
||||
_field(field_type="ROLE", face="BACK", bg_color="#FFD23F", auto_fit=True),
|
||||
_field(field_type="PHOTO", face="BACK", photo_shape="OVAL"),
|
||||
]
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
make_volunteer(session, edition, first_name="Alex", last_name="Martin")
|
||||
|
||||
resp = client.post(f"{_badges_url(edition)}/generate", json={})
|
||||
assert resp.status_code == 200
|
||||
assert resp.content.startswith(b"%PDF")
|
||||
|
||||
(badge,) = captured["badges"]
|
||||
assert [f.text for f in badge.fields] == ["Alex\nMartin"]
|
||||
back = {f.is_photo: f for f in badge.back_fields}
|
||||
assert back[False].text == "Bénévole"
|
||||
assert back[False].valign == "MIDDLE"
|
||||
assert back[False].bg_color == "#FFD23F"
|
||||
assert back[False].auto_fit is True
|
||||
assert back[True].photo_shape == "OVAL"
|
||||
assert back[True].text == "AM" # initiales, faute de photo
|
||||
|
||||
|
||||
def test_generating_prints_as_many_badges_as_requested(client, edition, session, monkeypatch):
|
||||
import app.api.badges as badges_api
|
||||
|
||||
captured = {}
|
||||
original = badges_api.generate_badge_sheets_pdf
|
||||
|
||||
def spy(badges, *args, **kwargs):
|
||||
captured["badges"] = badges
|
||||
return original(badges, *args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(badges_api, "generate_badge_sheets_pdf", spy)
|
||||
client.put(
|
||||
f"{_badges_url(edition)}/template/fields",
|
||||
json={"fields": [_field(field_type="FULL_NAME", width_mm=60, font_size=12)]},
|
||||
)
|
||||
many = make_volunteer(session, edition, first_name="Alex", last_name="Martin")
|
||||
make_volunteer(session, edition, first_name="Sam", last_name="Nuit")
|
||||
resp = client.patch(f"/editions/{edition.id}/volunteers/{many.id}", json={"badge_count": 3})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["badge_count"] == 3
|
||||
|
||||
resp = client.post(f"{_badges_url(edition)}/generate", json={})
|
||||
assert resp.status_code == 200
|
||||
|
||||
names = [badge.fields[0].text for badge in captured["badges"]]
|
||||
assert names == ["Alex\nMartin"] * 3 + ["Sam\nNuit"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("count", [0, -1, 101])
|
||||
def test_badge_count_out_of_range_is_rejected(client, edition, session, count):
|
||||
volunteer = make_volunteer(session, edition)
|
||||
resp = client.patch(
|
||||
f"/editions/{edition.id}/volunteers/{volunteer.id}", json={"badge_count": count}
|
||||
)
|
||||
assert resp.status_code == 422
|
||||
|
||||
Reference in New Issue
Block a user