From d994d88c121e1fc13c0928d28d886e84ee403a71 Mon Sep 17 00:00:00 2001 From: cedrick2711 Date: Wed, 9 Sep 2026 09:25:17 -0400 Subject: [PATCH] Fix probleme d'evaluation --- app/routes/evaluations.py | 77 +++++++++++++++++++++++++++++---------- tests/test_evaluations.py | 58 +++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 20 deletions(-) diff --git a/app/routes/evaluations.py b/app/routes/evaluations.py index a8a8798..83b06ad 100644 --- a/app/routes/evaluations.py +++ b/app/routes/evaluations.py @@ -37,6 +37,32 @@ def _users_by_id(user_ids): return {user.id: user for user in User.query.filter(User.id.in_(wanted)).all()} +def validate_score(score_value): + """Turn a raw form value into a criterion score (1..10), or None. + + A blank value means "not assessed". Anything else outside 1..10 is a + validation error, matching the single-player form's ``EvaluationSchema`` + -- a coach must not be able to quietly turn 11 or 0 into "no score" + (QUA-003). + """ + if score_value is None or str(score_value).strip() == '': + return None + try: + score = int(score_value) + except (TypeError, ValueError) as exc: + raise ValidationError('A score must be a whole number from 1 to 10.') from exc + if not 1 <= score <= 10: + raise ValidationError('Scores run from 1 to 10.') + return score + + +def _apply_evaluation(evaluation, scores, comments, position): + """Write validated scores, comments and position onto an Evaluation.""" + evaluation.apply_scores(scores) + evaluation.comments = comments + evaluation.position_recommendation = position + + @evaluations_bp.route('') @login_required def list_evaluations(): @@ -317,27 +343,38 @@ def batch_evaluate(tryout_id): if request.method == 'POST': saved = 0 - for entry in players: - pid = entry['player'].id - scores = { - field_name: validate_score(request.form.get(f'{field_name}_{pid}')) - for field_name, _ in EVALUATION_CRITERIA - } - comments = request.form.get(f'comments_{pid}') - position = request.form.get(f'position_recommendation_{pid}') + try: + for entry in players: + pid = entry['player'].id + scores = { + field_name: validate_score(request.form.get(f'{field_name}_{pid}')) + for field_name, _ in EVALUATION_CRITERIA + } + comments = request.form.get(f'comments_{pid}') + position = request.form.get(f'position_recommendation_{pid}') - existing = entry['existing'] - if existing: - _apply_evaluation(existing, scores, comments, position) - else: - evaluation = Evaluation( - tryout_id=tryout_id, - player_id=pid, - evaluator_id=current_user.id, - ) - _apply_evaluation(evaluation, scores, comments, position) - db.session.add(evaluation) - saved += 1 + existing = entry['existing'] + if existing: + _apply_evaluation(existing, scores, comments, position) + else: + evaluation = Evaluation( + tryout_id=tryout_id, + player_id=pid, + evaluator_id=current_user.id, + ) + _apply_evaluation(evaluation, scores, comments, position) + db.session.add(evaluation) + saved += 1 + except ValidationError: + db.session.rollback() + flash(_('Scores must be whole numbers from 1 to 10.'), 'danger') + return render_template( + 'pages/batch_evaluate.html', + tryout=tryout, + players=players, + evaluation_criteria=EVALUATION_CRITERIA, + game_positions=GAME_POSITIONS, + ) db.session.commit() flash(f'Saved evaluations for {saved} player(s).', 'success') diff --git a/tests/test_evaluations.py b/tests/test_evaluations.py index 65d5925..22971f4 100644 --- a/tests/test_evaluations.py +++ b/tests/test_evaluations.py @@ -179,3 +179,61 @@ class TestThroughTheForm: evaluation = Evaluation.query.one() assert evaluation.mecanics_score is None assert evaluation.overall_score == 4 + + +class TestBatchEvaluate: + """The batch page drives the same two helpers the single-player form does, + through ``{field}_{player_id}`` names. The helpers were lost once, in a + refactor, and the page 500'd on every POST until someone noticed.""" + + def _submit(self, client, setup, **fields): + data = {'player_ids': [str(setup['player_id'])]} + data.update(fields) + return client.post( + f'/evaluations/{setup["tryout_id"]}/batch', + data=data, + follow_redirects=True, + ) + + def test_a_valid_batch_evaluation_is_stored(self, app, client, evaluation_setup): + pid = evaluation_setup['player_id'] + self._submit( + client, + evaluation_setup, + **{ + f'mecanics_score_{pid}': '8', + f'mental_score_{pid}': '6', + f'comments_{pid}': 'Solid.', + }, + ) + + with app.app_context(): + evaluation = Evaluation.query.one() + assert evaluation.mecanics_score == 8 + assert evaluation.overall_score == 7 + assert evaluation.comments == 'Solid.' + assert evaluation.position_recommendation is None + + def test_an_out_of_range_batch_score_stores_nothing(self, app, client, evaluation_setup): + pid = evaluation_setup['player_id'] + self._submit( + client, + evaluation_setup, + **{f'mecanics_score_{pid}': '11', f'mental_score_{pid}': '6'}, + ) + + with app.app_context(): + assert Evaluation.query.count() == 0 + + def test_a_second_batch_submission_replaces_the_first(self, app, client, evaluation_setup): + pid = evaluation_setup['player_id'] + self._submit( + client, evaluation_setup, **{f'mecanics_score_{pid}': '8', f'mental_score_{pid}': '4'} + ) + self._submit( + client, evaluation_setup, **{f'mecanics_score_{pid}': '4', f'mental_score_{pid}': '4'} + ) + + with app.app_context(): + evaluation = Evaluation.query.one() + assert evaluation.overall_score == 4