Formulaire public : encart de paiement de la cotisation via Yapla
FormTemplate.yapla_url (lien vers la page de cotisation Yapla, doit commencer par http(s)://, PATCH .../form comme description/is_open) fait apparaître un encart « Payer ma cotisation » sous la case WILLING_TO_PAY_FEE, sur le formulaire public. Configurable depuis le panneau « Personnalisation » de l'écran /inscriptions. La case reste une simple déclaration : rien ne vérifie que le paiement a eu lieu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8f8350aa1b
commit
5f80f167c8
@@ -102,6 +102,7 @@ def _template_read(template: FormTemplate, fields: list[FormFieldType]) -> FormT
|
||||
fields=fields,
|
||||
has_logo=template.logo is not None,
|
||||
description=template.description,
|
||||
yapla_url=template.yapla_url,
|
||||
)
|
||||
|
||||
|
||||
@@ -138,7 +139,13 @@ def update_form(
|
||||
session: Session = Depends(get_session),
|
||||
) -> FormTemplateRead:
|
||||
template = _get_or_create_template(session, edition.id)
|
||||
for field, value in payload.model_dump(exclude_unset=True).items():
|
||||
changes = payload.model_dump(exclude_unset=True)
|
||||
if changes.get("yapla_url"):
|
||||
if not changes["yapla_url"].startswith(("http://", "https://")):
|
||||
raise HTTPException(
|
||||
status_code=422, detail={"code": "form.invalid_yapla_url"}
|
||||
)
|
||||
for field, value in changes.items():
|
||||
setattr(template, field, value)
|
||||
session.add(template)
|
||||
session.commit()
|
||||
@@ -233,6 +240,7 @@ def get_public_form(
|
||||
artists=artists,
|
||||
has_logo=template.logo is not None,
|
||||
description=template.description,
|
||||
yapla_url=template.yapla_url,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -493,14 +493,16 @@ class FormTemplateRead(ApiModel):
|
||||
fields: list[FormFieldType]
|
||||
has_logo: bool
|
||||
description: str | None
|
||||
yapla_url: str | None
|
||||
|
||||
|
||||
class FormTemplateUpdate(ApiModel):
|
||||
"""`None`/absent = inchangé pour chaque champ (`exclude_unset`) — permet
|
||||
de changer `is_open` sans toucher `description`, et inversement."""
|
||||
de changer `is_open` sans toucher `description`/`yapla_url`, etc."""
|
||||
|
||||
is_open: bool | None = None
|
||||
description: str | None = None
|
||||
yapla_url: str | None = None
|
||||
|
||||
|
||||
class PublicPositionOption(ApiModel):
|
||||
@@ -525,6 +527,7 @@ class PublicFormConfig(ApiModel):
|
||||
artists: list[PublicArtistOption]
|
||||
has_logo: bool
|
||||
description: str | None
|
||||
yapla_url: str | None
|
||||
|
||||
|
||||
class FormSubmissionResult(ApiModel):
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"form.logo_type_not_supported": "Le logo doit être au format PNG ou JPEG.",
|
||||
"form.logo_empty": "Le logo est vide.",
|
||||
"form.logo_too_large": "Le logo est trop volumineux.",
|
||||
"form.invalid_yapla_url": "Le lien Yapla doit commencer par http:// ou https://.",
|
||||
"form.too_many_position_wishes": "Deux vœux de poste au maximum.",
|
||||
"form.too_many_artists_same_day": "Un seul artiste souhaité par soir de festival.",
|
||||
"position_sheets.no_positions": "Aucun poste ne correspond à cette sélection.",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Lien de paiement Yapla
|
||||
|
||||
Revision ID: 0015_form_yapla_url
|
||||
Revises: 0014_form_logo
|
||||
Create Date: 2026-09-23
|
||||
|
||||
Encart affiché sur la page publique (`/inscription`), près de la case
|
||||
« je participe à la cotisation » — lien vers la page de cotisation sur
|
||||
Yapla (yapla.com, plateforme externe). Voir `app/api/forms.py`.
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "0015_form_yapla_url"
|
||||
down_revision: str | None = "0014_form_logo"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("form_template", sa.Column("yapla_url", sa.String(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("form_template", "yapla_url")
|
||||
@@ -467,9 +467,9 @@ class FormTemplate(TimestampMixin, table=True):
|
||||
"""Configuration du formulaire public d'une édition. Une ligne par
|
||||
édition (créée à la demande). `is_open` coupe les nouvelles soumissions
|
||||
sans effacer la configuration (les champs activés restent enregistrés).
|
||||
`logo`/`logo_content_type` et `description` habillent la page publique
|
||||
(`/inscription`) — aucun rapport avec les champs collectés par
|
||||
`FormField` ; stockage en base comme les autres images de l'appli
|
||||
`logo`/`logo_content_type`, `description` et `yapla_url` habillent la
|
||||
page publique (`/inscription`) — aucun rapport avec les champs collectés
|
||||
par `FormField` ; stockage en base comme les autres images de l'appli
|
||||
(badges, photo de bénévole), pas de volume de fichiers à gérer."""
|
||||
|
||||
__tablename__ = "form_template"
|
||||
@@ -485,6 +485,12 @@ class FormTemplate(TimestampMixin, table=True):
|
||||
logo: bytes | None = Field(default=None, sa_column=Column(LargeBinary))
|
||||
logo_content_type: str | None = None
|
||||
description: str | None = None
|
||||
# Lien vers la page de cotisation Yapla (yapla.com, plateforme externe de
|
||||
# paiement pour associations) — encart affiché sur `/inscription` près de
|
||||
# la case « je participe à la cotisation » (`FormFieldType.
|
||||
# WILLING_TO_PAY_FEE`). Rien n'est vérifié côté appli : la case reste
|
||||
# une déclaration, le paiement se passe entièrement chez Yapla.
|
||||
yapla_url: str | None = None
|
||||
|
||||
|
||||
class FormField(SQLModel, table=True):
|
||||
|
||||
@@ -74,6 +74,28 @@ def test_clearing_description_with_an_explicit_null(client, edition):
|
||||
assert resp.json()["description"] is None
|
||||
|
||||
|
||||
def test_no_yapla_link_by_default(client, raw_client, edition):
|
||||
assert client.get(_form_url(edition)).json()["yapla_url"] is None
|
||||
assert raw_client.get(_form_url(edition, "/public")).json()["yapla_url"] is None
|
||||
|
||||
|
||||
def test_setting_the_yapla_link(client, raw_client, edition):
|
||||
resp = client.patch(
|
||||
_form_url(edition), json={"yapla_url": "https://yapla.com/exemple/cotisation"}
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["yapla_url"] == "https://yapla.com/exemple/cotisation"
|
||||
|
||||
body = raw_client.get(_form_url(edition, "/public")).json()
|
||||
assert body["yapla_url"] == "https://yapla.com/exemple/cotisation"
|
||||
|
||||
|
||||
def test_rejects_a_yapla_link_without_a_scheme(client, edition):
|
||||
resp = client.patch(_form_url(edition), json={"yapla_url": "yapla.com/exemple"})
|
||||
assert resp.status_code == 422
|
||||
assert resp.json()["detail"]["code"] == "form.invalid_yapla_url"
|
||||
|
||||
|
||||
def test_logo_is_coordinator_only(edition, make_account, as_account):
|
||||
lead = make_account(Role.LEAD, email="lead@e.fr")
|
||||
lead_client = as_account(lead)
|
||||
|
||||
Reference in New Issue
Block a user