diff --git a/cms/db/contest.py b/cms/db/contest.py index 13c84c263c..e470e31e8b 100644 --- a/cms/db/contest.py +++ b/cms/db/contest.py @@ -108,6 +108,9 @@ class Contest(Base): nullable=False, default=False) + # Whether to show task scores in the overview page + show_task_scores_in_overview: bool = Column(Boolean, nullable=False, default=True) + # Whether to prevent hidden participations to log in. block_hidden_participations: bool = Column( Boolean, diff --git a/cms/server/admin/handlers/contest.py b/cms/server/admin/handlers/contest.py index 1a4c8e8ea6..1038c6ff51 100644 --- a/cms/server/admin/handlers/contest.py +++ b/cms/server/admin/handlers/contest.py @@ -97,6 +97,7 @@ def post(self, contest_id: str): self.get_bool(attrs, "allow_questions") self.get_bool(attrs, "allow_user_tests") self.get_bool(attrs, "allow_unofficial_submission_before_analysis_mode") + self.get_bool(attrs, "show_task_scores_in_overview") self.get_bool(attrs, "block_hidden_participations") self.get_bool(attrs, "allow_password_authentication") self.get_bool(attrs, "allow_registration") diff --git a/cms/server/admin/templates/contest.html b/cms/server/admin/templates/contest.html index 43f57c0b60..e9cfbdfa58 100644 --- a/cms/server/admin/templates/contest.html +++ b/cms/server/admin/templates/contest.html @@ -90,6 +90,15 @@

Contest configuration

+ + + + + + + + +

Logging in

diff --git a/cms/server/contest/handlers/main.py b/cms/server/contest/handlers/main.py index 93402e2b0c..49d637d48d 100644 --- a/cms/server/contest/handlers/main.py +++ b/cms/server/contest/handlers/main.py @@ -10,6 +10,8 @@ # Copyright © 2014 Fabian Gundlach <320pointsguy@gmail.com> # Copyright © 2015-2018 William Di Luigi # Copyright © 2021 Grace Hawkins +# Copyright © 2025 Pasit Sangprachathanarak +# Copyright © 2025 kk@cscmu-cnx # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -46,11 +48,13 @@ import tornado.web from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.orm import joinedload from cms import config -from cms.db import User, Participation, Team +from cms.db import User, Participation, Team, Submission, Token, Task, Dataset from cms.grading.languagemanager import get_language from cms.grading.steps import COMPILATION_MESSAGES, EVALUATION_MESSAGES +from cms.grading.scoring import task_score from cms.server import multi_contest from cms.server.contest.authentication import validate_login from cms.server.contest.communication import get_communications @@ -74,8 +78,62 @@ class MainHandler(ContestHandler): """ @multi_contest def get(self): + self.r_params = self.render_params() self.render("overview.html", **self.r_params) + def render_params(self): + ret = super().render_params() + + if self.current_user is not None: + # This massive joined load gets all the information which we will need + participation = ( + self.sql_session.query(Participation) + .filter(Participation.id == self.current_user.id) + .options( + joinedload(Participation.user), + joinedload(Participation.contest), + joinedload(Participation.submissions).joinedload(Submission.token), + joinedload(Participation.submissions).joinedload( + Submission.results + ), + ) + .first() + ) + + self.contest = ( + self.sql_session.query(Contest) + .filter(Contest.id == participation.contest.id) + .options(joinedload(Contest.tasks).joinedload(Task.active_dataset)) + .first() + ) + + ret["participation"] = participation + + # Compute public scores for all tasks only if they will be shown + if self.contest.show_task_scores_in_overview: + task_scores = {} + for task in self.contest.tasks: + score_type = task.active_dataset.score_type_object + max_public_score = round( + score_type.max_public_score, task.score_precision + ) + public_score, _ = task_score( + participation, task, public=True, rounded=True + ) + task_scores[task.id] = ( + public_score, + max_public_score, + score_type.format_score( + public_score, + score_type.max_public_score, + None, + task.score_precision, + translation=self.translation, + ), + ) + ret["task_scores"] = task_scores + + return ret class RegistrationHandler(ContestHandler): """Registration handler. diff --git a/cms/server/contest/static/cws_style.css b/cms/server/contest/static/cws_style.css index d896a00f7a..4bfefa4dfb 100644 --- a/cms/server/contest/static/cws_style.css +++ b/cms/server/contest/static/cws_style.css @@ -559,27 +559,33 @@ td.token_rules p:last-child { color: #AAA; } -.submission_list td.public_score.score_0 { +.submission_list td.public_score.score_0, +.main_task_list td.public_score.score_0 { background-color: hsla(0, 100%, 50%, 0.4); } -.submission_list tr:hover td.public_score.score_0 { +.submission_list tr:hover td.public_score.score_0, +.main_task_list tr:hover td.public_score.score_0 { background-color: hsla(0, 100%, 50%, 0.5); } -.submission_list td.public_score.score_0_100 { +.submission_list td.public_score.score_0_100, +.main_task_list td.public_score.score_0_100 { background-color: hsla(60, 100%, 50%, 0.4); } -.submission_list tr:hover td.public_score.score_0_100 { +.submission_list tr:hover td.public_score.score_0_100, +.main_task_list tr:hover td.public_score.score_0_100 { background-color: hsla(60, 100%, 50%, 0.5); } -.submission_list td.public_score.score_100 { +.submission_list td.public_score.score_100, +.main_task_list td.public_score.score_100 { background-color: hsla(120, 100%, 50%, 0.4); } -.submission_list tr:hover td.public_score.score_100 { +.submission_list tr:hover td.public_score.score_100, +.main_task_list tr:hover td.public_score.score_100 { background-color: hsla(120, 100%, 50%, 0.5); } diff --git a/cms/server/contest/templates/overview.html b/cms/server/contest/templates/overview.html index e086e16fb6..f881b86a81 100644 --- a/cms/server/contest/templates/overview.html +++ b/cms/server/contest/templates/overview.html @@ -182,7 +182,7 @@

{% trans %}General information{% endtrans %}

{% if actual_phase >= 0 or participation.unrestricted %}

{% trans %}Task overview{% endtrans %}

- +
+{% if contest.show_task_scores_in_overview %} + +{% endif %} @@ -209,6 +212,9 @@

{% trans %}Task overview{% endtrans %}

{% set task_allowed_languages = t_iter.get_allowed_languages() %} {% set extensions = "[%s]"|format(task_allowed_languages|map("to_language")|map(attribute="source_extension")|unique|join("|")) %} +{% if contest.show_task_scores_in_overview and task_scores is defined %} + +{% endif %}
{% trans %}Score{% endtrans %}{% trans %}Task{% endtrans %} {% trans %}Name{% endtrans %} {% trans %}Time limit{% endtrans %}
{{ task_scores[t_iter.id][2] }}{{ t_iter.name }} {{ t_iter.title }} diff --git a/cmscontrib/updaters/update_from_1.5.sql b/cmscontrib/updaters/update_from_1.5.sql index f35188a064..4a55396bde 100644 --- a/cmscontrib/updaters/update_from_1.5.sql +++ b/cmscontrib/updaters/update_from_1.5.sql @@ -42,6 +42,10 @@ ALTER TABLE user_test_results ADD COLUMN evaluation_sandbox_digests VARCHAR[]; UPDATE user_test_results SET evaluation_sandbox_paths = string_to_array(evaluation_sandbox, ':'); ALTER TABLE user_test_results DROP COLUMN evaluation_sandbox; +-- https://github.com/cms-dev/cms/pull/1476 +ALTER TABLE contests ADD COLUMN show_task_scores_in_overview boolean NOT NULL DEFAULT true; +ALTER TABLE contests ALTER COLUMN show_task_scores_in_overview DROP DEFAULT; + -- https://github.com/cms-dev/cms/pull/1486 ALTER TABLE public.tasks ADD COLUMN allowed_languages varchar[];