refactor(validation): un schema aux frontieres tryout et evaluation

ARCH-005, seconde moitie. Meme forme que pour les matchs : des champs lus
a la main sur request.form, deux verifies et le reste cru sur parole.

Cote tryout :
- game pilote la liste des postes et les champs de gamertag montres au
  joueur qui s inscrit. Il etait accepte tel quel : une faute de frappe
  produisait une selection pour laquelle personne ne pouvait etre evalue ;
- max_players etait int(x) if x else None — un 500 sur « twelve », et un
  -3 accepte sans broncher ;
- coach_ids etait charge par User.id.in_(...) sans filtre de role. Une
  soumission fabriquee a la main pouvait donc nommer un joueur coach d une
  selection, ce qui est une attribution de droits : gerer la selection et
  evaluer ses joueurs. Ce n est pas un formulaire que l interface propose,
  et ca marchait.

Cote evaluation, validate_score transformait tout ce qui sortait de 1..10
— 11, 0, « bien » — en None. Le critere disparaissait de la moyenne et la
page annoncait l evaluation enregistree. Rien ne distinguait « non
evalue » de « evalue, refuse, et oublie ».

Le calcul de la moyenne remonte sur le modele, en Evaluation.overall_from
et apply_scores. Il vivait dans la route, additionnant neuf variables
locales, et ne pouvait pas etre exerce sans requete HTTP, session
authentifiee et base — c est TEST-002, et c est pourquoi le calcul des
scores n avait aucun test. Il en a maintenant six, sans rien monter.

Une precision qui compte : aucun critere rempli donne None, pas 0. La
grille commence a 1, donc un zero serait une note qu aucun joueur ne peut
recevoir, et qui le classerait sous tout le monde dans la liste.

Les neuf criteres sont ecrits en toutes lettres dans le schema plutot que
generes depuis le modele — un schema se lit — et un test verifie que les
deux listes coincident. C est la garde qui empeche la derive, pas
l astuce.

Douze chaines traduites, dont trois que pybabel avait devinees en fuzzy :
une entree fuzzy est ignoree a l execution, le piege est consigne dans
docs/translations.md.

30 tests neufs. 477 au total.
This commit is contained in:
GGThed
2026-08-11 13:35:05 -04:00
parent 0308eb9eef
commit c5e5cfa014
10 changed files with 866 additions and 454 deletions
+151 -114
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: team-tryouts VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-08-11 12:05-0400\n"
"POT-Creation-Date: 2026-08-11 13:32-0400\n"
"PO-Revision-Date: 2026-08-07 20:22-0400\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: fr\n"
@@ -19,6 +19,12 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: app/forms.py:37 app/routes/auth.py:224 app/routes/auth.py:374
#: app/routes/users/contracts.py:95
#, python-format
msgid "%(field)s: %(msg)s"
msgstr "%(field)s : %(msg)s"
#: app/validators.py:50
msgid ""
"Password must be at least 8 characters with uppercase, lowercase, and a "
@@ -45,68 +51,134 @@ msgstr "Lidentifiant Discord doit être un nombre de 17 à 20 chiffres."
msgid "Invalid phone number format."
msgstr "Format de numéro de téléphone invalide."
#: app/validators.py:164
#: app/validators.py:201
msgid "Username is required."
msgstr "Le nom dutilisateur est obligatoire."
#: app/validators.py:168
#: app/validators.py:205
msgid "Password is required."
msgstr "Le mot de passe est obligatoire."
#: app/validators.py:190 app/validators.py:262
#: app/validators.py:227 app/validators.py:299
msgid "Username must be 3-80 characters."
msgstr "Le nom dutilisateur doit compter de 3 à 80 caractères."
#: app/validators.py:196
#: app/validators.py:233
msgid "Email must be 120 characters or less."
msgstr "Ladresse courriel ne doit pas dépasser 120 caractères."
#: app/validators.py:209 app/validators.py:277 app/validators.py:308
#: app/validators.py:372
#: app/validators.py:246 app/validators.py:314 app/validators.py:345
#: app/validators.py:409
msgid "Full name is required."
msgstr "Le nom complet est obligatoire."
#: app/validators.py:244
#: app/validators.py:281
msgid "Passwords do not match."
msgstr "Les mots de passe ne concordent pas."
#: app/validators.py:281 app/validators.py:316
#: app/validators.py:318 app/validators.py:353
msgid "Invalid role selected."
msgstr "Rôle sélectionné invalide."
#: app/validators.py:417
#: app/validators.py:454
msgid "Player must be selected."
msgstr "Vous devez choisir un joueur."
#: app/validators.py:420
#: app/validators.py:457
msgid "Notes must be 2000 characters or less."
msgstr "Les notes ne doivent pas dépasser 2000 caractères."
#: app/validators.py:439
#: app/validators.py:476
msgid "Date must be in YYYY-MM-DD format."
msgstr "La date doit être au format AAAA-MM-JJ."
#: app/validators.py:444 app/validators.py:471
#: app/validators.py:481 app/validators.py:508
msgid "Start time must be in HH:MM format."
msgstr "Lheure de début doit être au format HH:MM."
#: app/validators.py:448
#: app/validators.py:485
msgid "End time must be in HH:MM format."
msgstr "Lheure de fin doit être au format HH:MM."
#: app/validators.py:451
#: app/validators.py:488
msgid "Points must be 2000 characters or less."
msgstr "Les points ne doivent pas dépasser 2000 caractères."
#: app/validators.py:467
#: app/validators.py:504
msgid "Day must be 0 (Monday) to 6 (Sunday)."
msgstr "Le jour doit aller de 0 (lundi) à 6 (dimanche)."
#: app/routes/auth.py:224 app/routes/auth.py:374 app/routes/users/_shared.py:64
#: app/routes/users/contracts.py:95
#, python-format
msgid "%(field)s: %(msg)s"
msgstr "%(field)s : %(msg)s"
#: app/validators.py:535
msgid "Player selection is malformed."
msgstr "La sélection de joueurs est mal formée."
#: app/validators.py:561 app/validators.py:671
msgid "A title is required."
msgstr "Un titre est requis."
#: app/validators.py:570
msgid "Invalid date format."
msgstr "Format de date invalide."
#: app/validators.py:575 app/validators.py:582
msgid "Invalid time format."
msgstr "Format dheure invalide."
#: app/validators.py:576
msgid "Start time is required. Please select a time slot."
msgstr "Lheure de début est obligatoire. Choisissez une plage horaire."
#: app/validators.py:590
msgid "Unknown match status."
msgstr "Statut de match inconnu."
#: app/validators.py:606
msgid "The end time must come after the start time."
msgstr "L'heure de fin doit être postérieure à l'heure de début."
#: app/validators.py:620
msgid "Unknown match type."
msgstr "Type de match inconnu."
#: app/validators.py:632
msgid "A team cannot play against itself."
msgstr "Une équipe ne peut pas jouer contre elle-même."
#: app/validators.py:680
msgid "Unknown game."
msgstr "Jeu inconnu."
#: app/validators.py:685
msgid "Invalid start date format."
msgstr "Format de date de début invalide."
#: app/validators.py:686
msgid "A start date is required."
msgstr "Une date de début est requise."
#: app/validators.py:692
msgid "Invalid end date format."
msgstr "Format de date de fin invalide."
#: app/validators.py:700
msgid "A tryout must allow at least one player."
msgstr "Une sélection doit accepter au moins un joueur."
#: app/validators.py:703
msgid "The player limit must be a whole number."
msgstr "La limite de joueurs doit être un nombre entier."
#: app/validators.py:715
msgid "End date cannot be before start date."
msgstr "La date de fin ne peut pas précéder la date de début."
#: app/validators.py:724
msgid "Scores run from 1 to 10."
msgstr "Les notes vont de 1 à 10."
#: app/validators.py:725
msgid "A score must be a whole number from 1 to 10."
msgstr "Une note doit être un nombre entier de 1 à 10."
#: app/routes/auth.py:241
msgid "This account has been deactivated."
@@ -177,40 +249,40 @@ msgstr "Compte Discord connecté. Votre profil a été pré-rempli."
msgid "You have been logged out."
msgstr "Vous avez été déconnecté."
#: app/routes/evaluations.py:46
#: app/routes/evaluations.py:36
msgid "You do not have permission to view evaluations."
msgstr "Vous navez pas les droits pour consulter les évaluations."
#: app/routes/evaluations.py:136
#: app/routes/evaluations.py:126
msgid "You do not have permission to evaluate players."
msgstr "Vous navez pas les droits pour évaluer des joueurs."
#: app/routes/evaluations.py:141 app/routes/evaluations.py:264
#: app/routes/evaluations.py:131 app/routes/evaluations.py:215
msgid "You do not have permission to evaluate players in this tryout."
msgstr "Vous navez pas les droits pour évaluer des joueurs dans cette sélection."
#: app/routes/evaluations.py:152
#: app/routes/evaluations.py:142
msgid "Player is not registered for this tryout."
msgstr "Ce joueur nest pas inscrit à cette sélection."
#: app/routes/evaluations.py:157
#: app/routes/evaluations.py:147
msgid "Can only evaluate players."
msgstr "Seuls des joueurs peuvent être évalués."
#: app/routes/evaluations.py:209
msgid "Evaluation updated!"
msgstr "Évaluation mise à jour."
#: app/routes/evaluations.py:229
#: app/routes/evaluations.py:191
msgid "Evaluation submitted successfully!"
msgstr "Évaluation enregistrée."
#: app/routes/evaluations.py:259 app/routes/teams.py:270
#: app/routes/evaluations.py:193
msgid "Evaluation updated!"
msgstr "Évaluation mise à jour."
#: app/routes/evaluations.py:210 app/routes/teams.py:270
#: app/routes/teams.py:311 app/routes/teams.py:352 app/routes/teams.py:377
#: app/routes/teams.py:402 app/routes/teams.py:437 app/routes/tryouts.py:505
#: app/routes/tryouts.py:521 app/routes/tryouts.py:541
#: app/routes/tryouts.py:580 app/routes/tryouts.py:616
#: app/routes/tryouts.py:635
#: app/routes/teams.py:402 app/routes/teams.py:437 app/routes/tryouts.py:437
#: app/routes/tryouts.py:453 app/routes/tryouts.py:473
#: app/routes/tryouts.py:512 app/routes/tryouts.py:548
#: app/routes/tryouts.py:567
msgid "Permission denied."
msgstr "Accès refusé."
@@ -218,78 +290,49 @@ msgstr "Accès refusé."
msgid "That language is not available."
msgstr "Cette langue nest pas disponible."
#: app/routes/matches.py:307
#: app/routes/matches.py:361
msgid "You do not have permission to schedule matches for this tryout."
msgstr "Vous navez pas les droits pour planifier des matchs pour cette sélection."
#: app/routes/matches.py:311 app/routes/matches.py:473
#: app/routes/matches.py:365 app/routes/matches.py:445
msgid "This tryout has ended. Matches can no longer be created or modified."
msgstr ""
"Cette sélection est terminée. Les matchs ne peuvent plus être créés ni "
"modifiés."
#: app/routes/matches.py:332
msgid "Start time is required. Please select a time slot."
msgstr "Lheure de début est obligatoire. Choisissez une plage horaire."
#: app/routes/matches.py:344 app/routes/matches.py:496
#: app/routes/team_matches.py:153 app/routes/team_matches.py:247
msgid "Invalid date format."
msgstr "Format de date invalide."
#: app/routes/matches.py:364 app/routes/team_matches.py:174
msgid "Invalid time format."
msgstr "Format dheure invalide."
#: app/routes/matches.py:449
#: app/routes/matches.py:427
msgid "Match scheduled successfully!"
msgstr "Match planifié."
#: app/routes/matches.py:469 app/routes/team_matches.py:234
#: app/routes/matches.py:441 app/routes/team_matches.py:211
msgid "You do not have permission to edit this match."
msgstr "Vous navez pas les droits pour modifier ce match."
#: app/routes/matches.py:507
msgid "Start time is required."
msgstr "Lheure de début est obligatoire."
#: app/routes/matches.py:616 app/routes/team_matches.py:276
#: app/routes/matches.py:536 app/routes/team_matches.py:241
msgid "Match updated successfully!"
msgstr "Match mis à jour."
#: app/routes/matches.py:669 app/routes/team_matches.py:291
#: app/routes/matches.py:571 app/routes/team_matches.py:256
msgid "You do not have permission to delete this match."
msgstr "Vous navez pas les droits pour supprimer ce match."
#: app/routes/matches.py:672
#: app/routes/matches.py:574
msgid "This tryout has ended. Matches can no longer be deleted."
msgstr "Cette sélection est terminée. Les matchs ne peuvent plus être supprimés."
#: app/routes/matches.py:685 app/routes/team_matches.py:295
#: app/routes/matches.py:587 app/routes/team_matches.py:260
msgid "Match deleted successfully."
msgstr "Match supprimé."
#: app/routes/team_matches.py:100
#: app/routes/team_matches.py:104
msgid "You do not have permission to schedule matches for this team."
msgstr "Vous navez pas les droits pour planifier des matchs pour cette équipe."
#: app/routes/team_matches.py:142
msgid "Date is required."
msgstr "La date est obligatoire."
#: app/routes/team_matches.py:220
#: app/routes/team_matches.py:196
#, python-format
msgid "Team match \"%(title)s\" scheduled successfully!"
msgstr "Match d’équipe « %(title)s » planifié."
#: app/routes/team_matches.py:259
msgid "Invalid start time format."
msgstr "Format dheure de début invalide."
#: app/routes/team_matches.py:267
msgid "Invalid end time format."
msgstr "Format dheure de fin invalide."
#: app/routes/teams.py:40
msgid "Use My Team(s) to view your teams."
msgstr "Utilisez « Mon ou mes équipes » pour consulter vos équipes."
@@ -384,7 +427,7 @@ msgstr "Coach retiré de %(name)s."
msgid "Manager removed from %(name)s."
msgstr "Gérant retiré de %(name)s."
#: app/routes/teams.py:408 app/routes/tryouts.py:545 app/routes/tryouts.py:646
#: app/routes/teams.py:408 app/routes/tryouts.py:477 app/routes/tryouts.py:578
msgid "Please select a player."
msgstr "Veuillez choisir un joueur."
@@ -430,124 +473,112 @@ msgstr "Il nest possible dajouter des notes que pour des joueurs."
msgid "Note added for %(username)s!"
msgstr "Note ajoutée pour %(username)s."
#: app/routes/tryouts.py:77
#: app/routes/tryouts.py:98
msgid "You do not have permission to create tryouts."
msgstr "Vous navez pas les droits pour créer une sélection."
#: app/routes/tryouts.py:103 app/routes/tryouts.py:210
msgid "Invalid start date format."
msgstr "Format de date de début invalide."
#: app/routes/tryouts.py:118 app/routes/tryouts.py:225
msgid "End date cannot be before start date."
msgstr "La date de fin ne peut pas précéder la date de début."
#: app/routes/tryouts.py:128 app/routes/tryouts.py:235
msgid "Invalid end date format."
msgstr "Format de date de fin invalide."
#: app/routes/tryouts.py:160
#: app/routes/tryouts.py:145
msgid "Tryout created successfully!"
msgstr "Sélection créée."
#: app/routes/tryouts.py:180
#: app/routes/tryouts.py:158
msgid "You do not have permission to edit this tryout."
msgstr "Vous navez pas les droits pour modifier cette sélection."
#: app/routes/tryouts.py:184
#: app/routes/tryouts.py:162
msgid "This tryout has ended and can no longer be modified."
msgstr "Cette sélection est terminée et ne peut plus être modifiée."
#: app/routes/tryouts.py:263
#: app/routes/tryouts.py:202
msgid "Tryout updated successfully!"
msgstr "Sélection mise à jour."
#: app/routes/tryouts.py:310
#: app/routes/tryouts.py:242
msgid "You do not have permission to view this tryout."
msgstr "Vous navez pas les droits pour consulter cette sélection."
#: app/routes/tryouts.py:472
#: app/routes/tryouts.py:404
msgid "Only players can register for tryouts."
msgstr "Seuls les joueurs peuvent sinscrire à une sélection."
#: app/routes/tryouts.py:476
#: app/routes/tryouts.py:408
msgid "This tryout is not accepting registrations."
msgstr "Cette sélection naccepte pas dinscriptions."
#: app/routes/tryouts.py:483
#: app/routes/tryouts.py:415
msgid "You are already registered for this tryout."
msgstr "Vous êtes déjà inscrit à cette sélection."
#: app/routes/tryouts.py:489 app/routes/tryouts.py:564
#: app/routes/tryouts.py:421 app/routes/tryouts.py:496
msgid "This tryout is full."
msgstr "Cette sélection est complète."
#: app/routes/tryouts.py:495
#: app/routes/tryouts.py:427
msgid "Successfully registered for tryout!"
msgstr "Inscription à la sélection réussie."
#: app/routes/tryouts.py:511
#: app/routes/tryouts.py:443
#, python-format
msgid "Tryout status updated to %(new_status)s."
msgstr "Statut de la sélection mis à jour : %(new_status)s."
#: app/routes/tryouts.py:531
#: app/routes/tryouts.py:463
msgid "Registration status updated."
msgstr "Statut dinscription mis à jour."
#: app/routes/tryouts.py:550
#: app/routes/tryouts.py:482
msgid "Can only register players."
msgstr "Seuls des joueurs peuvent être inscrits."
#: app/routes/tryouts.py:556
#: app/routes/tryouts.py:488
#, python-format
msgid "%(username)s is already registered for this tryout."
msgstr "%(username)s est déjà inscrit à cette sélection."
#: app/routes/tryouts.py:570
#: app/routes/tryouts.py:502
#, python-format
msgid "%(username)s registered for tryout!"
msgstr "%(username)s est inscrit à la sélection."
#: app/routes/tryouts.py:606
#: app/routes/tryouts.py:538
#, python-format
msgid "%(username)s removed from tryout."
msgstr "%(username)s a été retiré de la sélection."
#: app/routes/tryouts.py:624
#: app/routes/tryouts.py:556
#, python-format
msgid "Team \"%(team_name)s\" created!"
msgstr "Équipe « %(team_name)s » créée."
#: app/routes/tryouts.py:655
#: app/routes/tryouts.py:587
msgid "That player is not registered for this tryout."
msgstr "Ce joueur nest pas inscrit à cette sélection."
#: app/routes/tryouts.py:661
#: app/routes/tryouts.py:593
msgid "Player is already on this team."
msgstr "Ce joueur est déjà dans cette équipe."
#: app/routes/tryouts.py:666
#: app/routes/tryouts.py:598
msgid "Player added to team!"
msgstr "Joueur ajouté à l’équipe."
#: app/routes/tryouts.py:676
#: app/routes/tryouts.py:608
msgid "You do not have permission to delete this tryout."
msgstr "Vous navez pas les droits pour supprimer cette sélection."
#: app/routes/tryouts.py:712
#: app/routes/tryouts.py:644
msgid "Tryout deleted successfully."
msgstr "Sélection supprimée."
#: app/routes/users/_shared.py:46
#: app/routes/users/_shared.py:51
msgid "No file selected."
msgstr "Aucun fichier sélectionné."
#: app/routes/users/_shared.py:50
#: app/routes/users/_shared.py:55
msgid "Only PDF files are allowed for contracts."
msgstr "Seuls les fichiers PDF sont acceptés pour les contrats."
#: app/routes/users/_shared.py:55
#: app/routes/users/_shared.py:60
msgid "That file is not a PDF, whatever its name says."
msgstr "Ce fichier nest pas un PDF, quel que soit son nom."
@@ -2904,3 +2935,9 @@ msgstr "Voir le profil"
#~ msgid "Answer"
#~ msgstr "Réponse"
#~ msgid "Invalid start time format."
#~ msgstr "Format dheure de début invalide."
#~ msgid "Invalid end time format."
#~ msgstr "Format dheure de fin invalide."