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
Binary file not shown.
+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: en\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 "
@@ -43,68 +49,134 @@ msgstr "Discord User ID must be a 17-20 digit number."
msgid "Invalid phone number format."
msgstr "Invalid phone number format."
#: app/validators.py:164
#: app/validators.py:201
msgid "Username is required."
msgstr "Username is required."
#: app/validators.py:168
#: app/validators.py:205
msgid "Password is required."
msgstr "Password is required."
#: app/validators.py:190 app/validators.py:262
#: app/validators.py:227 app/validators.py:299
msgid "Username must be 3-80 characters."
msgstr "Username must be 3-80 characters."
#: app/validators.py:196
#: app/validators.py:233
msgid "Email must be 120 characters or less."
msgstr "Email must be 120 characters or less."
#: 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 "Full name is required."
#: app/validators.py:244
#: app/validators.py:281
msgid "Passwords do not match."
msgstr "Passwords do not match."
#: app/validators.py:281 app/validators.py:316
#: app/validators.py:318 app/validators.py:353
msgid "Invalid role selected."
msgstr "Invalid role selected."
#: app/validators.py:417
#: app/validators.py:454
msgid "Player must be selected."
msgstr "Player must be selected."
#: app/validators.py:420
#: app/validators.py:457
msgid "Notes must be 2000 characters or less."
msgstr "Notes must be 2000 characters or less."
#: app/validators.py:439
#: app/validators.py:476
msgid "Date must be in YYYY-MM-DD format."
msgstr "Date must be in YYYY-MM-DD format."
#: 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 "Start time must be in HH:MM format."
#: app/validators.py:448
#: app/validators.py:485
msgid "End time must be in HH:MM format."
msgstr "End time must be in HH:MM format."
#: app/validators.py:451
#: app/validators.py:488
msgid "Points must be 2000 characters or less."
msgstr "Points must be 2000 characters or less."
#: app/validators.py:467
#: app/validators.py:504
msgid "Day must be 0 (Monday) to 6 (Sunday)."
msgstr "Day must be 0 (Monday) to 6 (Sunday)."
#: 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 "Player selection is malformed."
#: app/validators.py:561 app/validators.py:671
msgid "A title is required."
msgstr "A title is required."
#: app/validators.py:570
msgid "Invalid date format."
msgstr "Invalid date format."
#: app/validators.py:575 app/validators.py:582
msgid "Invalid time format."
msgstr "Invalid time format."
#: app/validators.py:576
msgid "Start time is required. Please select a time slot."
msgstr "Start time is required. Please select a time slot."
#: app/validators.py:590
msgid "Unknown match status."
msgstr "Unknown match status."
#: app/validators.py:606
msgid "The end time must come after the start time."
msgstr "The end time must come after the start time."
#: app/validators.py:620
msgid "Unknown match type."
msgstr "Unknown match type."
#: app/validators.py:632
msgid "A team cannot play against itself."
msgstr "A team cannot play against itself."
#: app/validators.py:680
msgid "Unknown game."
msgstr "Unknown game."
#: app/validators.py:685
msgid "Invalid start date format."
msgstr "Invalid start date format."
#: app/validators.py:686
msgid "A start date is required."
msgstr "A start date is required."
#: app/validators.py:692
msgid "Invalid end date format."
msgstr "Invalid end date format."
#: app/validators.py:700
msgid "A tryout must allow at least one player."
msgstr "A tryout must allow at least one player."
#: app/validators.py:703
msgid "The player limit must be a whole number."
msgstr "The player limit must be a whole number."
#: app/validators.py:715
msgid "End date cannot be before start date."
msgstr "End date cannot be before start date."
#: app/validators.py:724
msgid "Scores run from 1 to 10."
msgstr "Scores run from 1 to 10."
#: app/validators.py:725
msgid "A score must be a whole number from 1 to 10."
msgstr "A score must be a whole number from 1 to 10."
#: app/routes/auth.py:241
msgid "This account has been deactivated."
@@ -175,40 +247,40 @@ msgstr "Discord account connected! Your profile has been pre-filled."
msgid "You have been logged out."
msgstr "You have been logged out."
#: app/routes/evaluations.py:46
#: app/routes/evaluations.py:36
msgid "You do not have permission to view evaluations."
msgstr "You do not have permission to view evaluations."
#: app/routes/evaluations.py:136
#: app/routes/evaluations.py:126
msgid "You do not have permission to evaluate players."
msgstr "You do not have permission to evaluate players."
#: 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 "You do not have permission to evaluate players in this tryout."
#: app/routes/evaluations.py:152
#: app/routes/evaluations.py:142
msgid "Player is not registered for this tryout."
msgstr "Player is not registered for this tryout."
#: app/routes/evaluations.py:157
#: app/routes/evaluations.py:147
msgid "Can only evaluate players."
msgstr "Can only evaluate players."
#: app/routes/evaluations.py:209
msgid "Evaluation updated!"
msgstr "Evaluation updated!"
#: app/routes/evaluations.py:229
#: app/routes/evaluations.py:191
msgid "Evaluation submitted successfully!"
msgstr "Evaluation submitted successfully!"
#: app/routes/evaluations.py:259 app/routes/teams.py:270
#: app/routes/evaluations.py:193
msgid "Evaluation updated!"
msgstr "Evaluation updated!"
#: 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 "Permission denied."
@@ -216,76 +288,47 @@ msgstr "Permission denied."
msgid "That language is not available."
msgstr "That language is not available."
#: app/routes/matches.py:307
#: app/routes/matches.py:361
msgid "You do not have permission to schedule matches for this tryout."
msgstr "You do not have permission to schedule matches for this tryout."
#: 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 "This tryout has ended. Matches can no longer be created or modified."
#: app/routes/matches.py:332
msgid "Start time is required. Please select a time slot."
msgstr "Start time is required. Please select a time slot."
#: 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 "Invalid date format."
#: app/routes/matches.py:364 app/routes/team_matches.py:174
msgid "Invalid time format."
msgstr "Invalid time format."
#: app/routes/matches.py:449
#: app/routes/matches.py:427
msgid "Match scheduled successfully!"
msgstr "Match scheduled successfully!"
#: 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 "You do not have permission to edit this match."
#: app/routes/matches.py:507
msgid "Start time is required."
msgstr "Start time is required."
#: 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 updated successfully!"
#: 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 "You do not have permission to delete this match."
#: app/routes/matches.py:672
#: app/routes/matches.py:574
msgid "This tryout has ended. Matches can no longer be deleted."
msgstr "This tryout has ended. Matches can no longer be deleted."
#: 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 deleted successfully."
#: 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 "You do not have permission to schedule matches for this team."
#: app/routes/team_matches.py:142
msgid "Date is required."
msgstr "Date is required."
#: app/routes/team_matches.py:220
#: app/routes/team_matches.py:196
#, python-format
msgid "Team match \"%(title)s\" scheduled successfully!"
msgstr "Team match \"%(title)s\" scheduled successfully!"
#: app/routes/team_matches.py:259
msgid "Invalid start time format."
msgstr "Invalid start time format."
#: app/routes/team_matches.py:267
msgid "Invalid end time format."
msgstr "Invalid end time format."
#: app/routes/teams.py:40
msgid "Use My Team(s) to view your teams."
msgstr "Use My Team(s) to view your teams."
@@ -380,7 +423,7 @@ msgstr "Coach removed from %(name)s."
msgid "Manager removed from %(name)s."
msgstr "Manager removed from %(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 "Please select a player."
@@ -426,124 +469,112 @@ msgstr "Can only add notes for players."
msgid "Note added for %(username)s!"
msgstr "Note added for %(username)s!"
#: app/routes/tryouts.py:77
#: app/routes/tryouts.py:98
msgid "You do not have permission to create tryouts."
msgstr "You do not have permission to create tryouts."
#: app/routes/tryouts.py:103 app/routes/tryouts.py:210
msgid "Invalid start date format."
msgstr "Invalid start date format."
#: app/routes/tryouts.py:118 app/routes/tryouts.py:225
msgid "End date cannot be before start date."
msgstr "End date cannot be before start date."
#: app/routes/tryouts.py:128 app/routes/tryouts.py:235
msgid "Invalid end date format."
msgstr "Invalid end date format."
#: app/routes/tryouts.py:160
#: app/routes/tryouts.py:145
msgid "Tryout created successfully!"
msgstr "Tryout created successfully!"
#: app/routes/tryouts.py:180
#: app/routes/tryouts.py:158
msgid "You do not have permission to edit this tryout."
msgstr "You do not have permission to edit this tryout."
#: app/routes/tryouts.py:184
#: app/routes/tryouts.py:162
msgid "This tryout has ended and can no longer be modified."
msgstr "This tryout has ended and can no longer be modified."
#: app/routes/tryouts.py:263
#: app/routes/tryouts.py:202
msgid "Tryout updated successfully!"
msgstr "Tryout updated successfully!"
#: app/routes/tryouts.py:310
#: app/routes/tryouts.py:242
msgid "You do not have permission to view this tryout."
msgstr "You do not have permission to view this tryout."
#: app/routes/tryouts.py:472
#: app/routes/tryouts.py:404
msgid "Only players can register for tryouts."
msgstr "Only players can register for tryouts."
#: app/routes/tryouts.py:476
#: app/routes/tryouts.py:408
msgid "This tryout is not accepting registrations."
msgstr "This tryout is not accepting registrations."
#: app/routes/tryouts.py:483
#: app/routes/tryouts.py:415
msgid "You are already registered for this tryout."
msgstr "You are already registered for this tryout."
#: 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 "This tryout is full."
#: app/routes/tryouts.py:495
#: app/routes/tryouts.py:427
msgid "Successfully registered for tryout!"
msgstr "Successfully registered for tryout!"
#: app/routes/tryouts.py:511
#: app/routes/tryouts.py:443
#, python-format
msgid "Tryout status updated to %(new_status)s."
msgstr "Tryout status updated to %(new_status)s."
#: app/routes/tryouts.py:531
#: app/routes/tryouts.py:463
msgid "Registration status updated."
msgstr "Registration status updated."
#: app/routes/tryouts.py:550
#: app/routes/tryouts.py:482
msgid "Can only register players."
msgstr "Can only register players."
#: app/routes/tryouts.py:556
#: app/routes/tryouts.py:488
#, python-format
msgid "%(username)s is already registered for this tryout."
msgstr "%(username)s is already registered for this tryout."
#: app/routes/tryouts.py:570
#: app/routes/tryouts.py:502
#, python-format
msgid "%(username)s registered for tryout!"
msgstr "%(username)s registered for tryout!"
#: app/routes/tryouts.py:606
#: app/routes/tryouts.py:538
#, python-format
msgid "%(username)s removed from tryout."
msgstr "%(username)s removed from tryout."
#: app/routes/tryouts.py:624
#: app/routes/tryouts.py:556
#, python-format
msgid "Team \"%(team_name)s\" created!"
msgstr "Team \"%(team_name)s\" created!"
#: app/routes/tryouts.py:655
#: app/routes/tryouts.py:587
msgid "That player is not registered for this tryout."
msgstr "That player is not registered for this tryout."
#: app/routes/tryouts.py:661
#: app/routes/tryouts.py:593
msgid "Player is already on this team."
msgstr "Player is already on this team."
#: app/routes/tryouts.py:666
#: app/routes/tryouts.py:598
msgid "Player added to team!"
msgstr "Player added to team!"
#: app/routes/tryouts.py:676
#: app/routes/tryouts.py:608
msgid "You do not have permission to delete this tryout."
msgstr "You do not have permission to delete this tryout."
#: app/routes/tryouts.py:712
#: app/routes/tryouts.py:644
msgid "Tryout deleted successfully."
msgstr "Tryout deleted successfully."
#: app/routes/users/_shared.py:46
#: app/routes/users/_shared.py:51
msgid "No file selected."
msgstr "No file selected."
#: app/routes/users/_shared.py:50
#: app/routes/users/_shared.py:55
msgid "Only PDF files are allowed for contracts."
msgstr "Only PDF files are allowed for contracts."
#: app/routes/users/_shared.py:55
#: app/routes/users/_shared.py:60
msgid "That file is not a PDF, whatever its name says."
msgstr "That file is not a PDF, whatever its name says."
@@ -2880,3 +2911,9 @@ msgstr "View Profile"
#~ msgid "Answer"
#~ msgstr "Answer"
#~ msgid "Invalid start time format."
#~ msgstr "Invalid start time format."
#~ msgid "Invalid end time format."
#~ msgstr "Invalid end time format."