ARCH-005, premiere moitie. matches.py et team_matches.py lisaient une
quarantaine de champs sur request.form a la main et les croyaient tous.
Ce que ca produisait n etait pas bruyant :
- edit_match attrapait une heure invalide et faisait start_time = None,
puis annoncait que le match etait mis a jour. Le match perdait son
heure et le calendrier l affichait a minuit ;
- match_type etait accepte tel quel. Une valeur inconnue creait un match
auquel aucun joueur n etait rattache, sans un mot ;
- une fin avant le debut etait enregistree telle quelle ;
- title est NOT NULL dans le modele et n etait pas verifie dans la route,
donc un titre vide etait un 500 ;
- 'a,b' dans la selection de joueurs arrivait sur int() sans garde.
app/forms.py rassemble les deux fonctions de frontiere, qui vivaient dans
users/_shared.py parce que c est la qu elles avaient d abord servi. Elles
y restent re-exportees, donc aucun des trente appels n a bouge.
Le mixin des schemas lit desormais un champ vide comme un champ absent.
C est ce qui rendait ces formulaires invalidables : un formulaire HTML
envoie tout ce qu il affiche, donc une date optionnelle non remplie
arrive comme '' et non comme rien. Seuls les champs declares optionnels
sont concernes ; un champ requis laisse vide doit toujours echouer.
Deux duplications absorbees au passage, toutes deux nommees par l audit :
la boucle de creation des participants, ecrite deux fois et deja divergee
— la copie de edit_match gardait ses identifiants en chaines et appelait
int() une ligne plus loin — et le contexte de re-affichage du formulaire,
dont les versions courtes faisaient mourir un refus dans tojson sur un
Undefined : un message de validation devenait un 500.
Limite connue et consignee : le formulaire revient rempli avec les
valeurs enregistrees, pas avec la saisie refusee. Reafficher la
soumission demande de toucher aux gabarits, c est un autre changement.
19 tests neufs sur ces routes, qui n en avaient aucun. 447 au total.
Active la regle isort (I) de ruff. 45 fichiers reordonnes, aucun
changement de comportement : la suite passe avant comme apres.
app/models/__init__.py en est exclu. Ses imports sont ranges en onze
couches commentees qui decrivent le graphe de dependances ; trier par
ordre alphabetique laisse chaque titre au-dessus d un import qu il ne
decrit pas, et ce fichier n a qu un role, etre lu.
Commit isole, comme le formatage : un diff de brassage ne doit pas
servir de couverture a un changement de comportement.
QUA-002, premiere moitie. **Ce commit ne fait que reformater** : aucun
changement de comportement, aucune ligne de logique touchee. 72 fichiers,
4 restaient deja conformes. Il est isole exprès, pour que `git log -p` sur
les commits voisins reste lisible.
`quote-style = "preserve"` etait deja pose dans pyproject.toml, ce qui
evite le brassage guillemets simples / doubles : le diff porte sur les
retours a la ligne, l indentation des appels longs et les virgules
finales, pas sur le style de chaine.
Verification : 263 tests passent avant et apres, ruff check propre.
L activation en CI arrive dans le commit suivant, separement, pour que ce
diff-ci ne contienne rien d autre.
Co-Authored-By: Claude Opus 5 <[email protected]>
198 appels flash dans les sept modules de routes, plus les 23 messages de
validation de app/validators.py. Le catalogue compte desormais 631 chaines,
aucune non traduite.
validators.py utilise lazy_gettext : les champs de schema sont construits a
l'import, donc avant qu'une requete existe. Un gettext ordinaire s'y
resoudrait une seule fois, dans la langue active au demarrage.
Un bug introduit par la conversion, puis corrige
Le convertisseur automatique ne voyait que le premier litteral d'un appel
flash, ce qui a casse deux chaines concatenees sur plusieurs lignes dans
users.py -- le resultat n'etait meme pas du Python valide. Ma premiere
verification ne l'a pas vu : elle enchainait py_compile sur head, or head
reussit toujours, donc le "OK" s'affichait quoi qu'il arrive. Les deux
appels sont reecrits et la verification refaite correctement.
Un bug plus interessant, revele par le test de fumee
La langue choisie ne survivait pas a la connexion. login() et logout()
appellent tous deux session.clear() -- l'un contre la fixation de session,
l'autre pour terminer la session -- et le choix de langue partait avec le
reste. Concretement : quelqu'un qui lisait la page de connexion en anglais
se retrouvait en francais des qu'il se connectait.
La langue est une preference d'affichage, pas un etat appartenant au
compte. Les deux endroits la reportent maintenant explicitement, a cote du
jeton CSRF. Quatre tests couvrent le cas, dont un qui verifie que corriger
une cle preservee n'a pas fait tomber l'autre.
Detail de nommage : le convertisseur avait genere %(value)s pour une
expression conditionnelle, ce qui n'aide pas un traducteur. Renomme en
%(player)s.
Les 14 traductions ecrites avec une apostrophe droite sont normalisees en
apostrophe typographique. Sans consequence en HTML, ou ' s'affiche
correctement -- mais les blocs <script> ne decodent pas les entites, et
autant que le catalogue soit homogene.
200 tests.
Co-Authored-By: Claude Opus 5 <[email protected]>