From bb17b1aea0c8db8ef1bd3758b805c0e95ca7731b Mon Sep 17 00:00:00 2001 From: Pasit Sangprachathanarak Date: Wed, 23 Jul 2025 20:50:45 +0700 Subject: [PATCH 01/10] Add a Score counter to task overview Add a Score counter to task overview --- cms/server/contest/handlers/main.py | 48 ++- cms/server/contest/static/cws_style.css | 18 +- cms/server/contest/templates/overview.html | 452 +++++++++++---------- 3 files changed, 298 insertions(+), 220 deletions(-) diff --git a/cms/server/contest/handlers/main.py b/cms/server/contest/handlers/main.py index 4f34f81c16..98d3d0300c 100644 --- a/cms/server/contest/handlers/main.py +++ b/cms/server/contest/handlers/main.py @@ -10,6 +10,7 @@ # Copyright © 2014 Fabian Gundlach <320pointsguy@gmail.com> # Copyright © 2015-2018 William Di Luigi # Copyright © 2021 Grace Hawkins +# Copyright © 2025 Pasit Sangprachathanarak # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -49,11 +50,13 @@ except ImportError: import tornado.web as tornado_web from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.orm import joinedload from cms import config -from cms.db import PrintJob, User, Participation, Team +from cms.db import Contest, PrintJob, User, Participation, Team 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 @@ -79,8 +82,49 @@ 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('user'), + joinedload('contest'), + joinedload('submissions').joinedload('token'), + joinedload('submissions').joinedload('results'), + )\ + .first() + + self.contest = self.sql_session.query(Contest)\ + .filter(Contest.id == participation.contest.id)\ + .options( + joinedload('tasks') + .joinedload('active_dataset') + )\ + .first() + + ret["participation"] = participation + + # Compute public scores for all tasks + 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) + public_score = round(public_score, task.score_precision) + 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. @@ -384,7 +428,7 @@ def get(self): language_docs = [] if config.docs_path is not None: for language in languages: - ext = language.source_extensions[0][1:] # remove dot + ext = language.source_extensions[0][1:] # remove dot path = os.path.join(config.docs_path, ext) if os.path.exists(path): language_docs.append((language.name, ext)) diff --git a/cms/server/contest/static/cws_style.css b/cms/server/contest/static/cws_style.css index b5786b443f..5baef55d6a 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 37fe202e3e..d2c31c81d6 100644 --- a/cms/server/contest/templates/overview.html +++ b/cms/server/contest/templates/overview.html @@ -2,188 +2,211 @@ {% set page = "overview" %} +{% block additional_js %} + +{% endblock additional_js %} + {% block core %}
- + -

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

-
-
-

-{% if phase == -1 %} - {% trans %}The contest hasn't started yet.{% endtrans %} -

-

- {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, - stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} - The contest will start at {{ start_time }} and will end at {{ stop_time }}. - {% endtrans %} -{% elif phase == 0 %} - {% trans %}The contest is currently running.{% endtrans %} -

-

- {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, - stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} - The contest started at {{ start_time }} and will end at {{ stop_time }}. - {% endtrans %} -{% elif phase >= +1 %} - {% trans %}The contest has already ended.{% endtrans %} -

-

- {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, - stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} - The contest started at {{ start_time }} and ended at {{ stop_time }}. - {% endtrans %} -{% endif %} -

-{% if contest.analysis_enabled %} -

- {% if phase == +1 %} - {% trans %}The analysis mode hasn't started yet.{% endtrans %} -

-

- {% trans start_time=contest.analysis_start|format_datetime_smart, - stop_time=contest.analysis_stop|format_datetime_smart %} - The analysis mode will start at {{ start_time }} and will end at {{ stop_time }}. - {% endtrans %} - {% elif phase == +2 %} - {% trans %}The analysis mode is currently running.{% endtrans %} -

-

- {% trans start_time=contest.analysis_start|format_datetime_smart, - stop_time=contest.analysis_stop|format_datetime_smart %} - The analysis mode started at {{ start_time }} and will end at {{ stop_time }}. - {% endtrans %} - {% elif phase == +3 %} - {% trans %}The analysis mode has already ended.{% endtrans %} -

-

- {% trans start_time=contest.analysis_start|format_datetime_smart, - stop_time=contest.analysis_stop|format_datetime_smart %} - The analysis mode started at {{ start_time }} and ended at {{ stop_time }}. - {% endtrans %} - {% endif %} -

- -{% endif %} - - - -{% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} - {% if tokens_contest == TOKEN_MODE_INFINITE and tokens_tasks == TOKEN_MODE_INFINITE %} -

- {% trans %}You have an infinite number of tokens.{% endtrans %} -

- -

- {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} - {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} -

- {% elif tokens_contest == TOKEN_MODE_INFINITE %} -

- {% trans %}You have a distinct set of tokens for each task.{% endtrans %} - {%+ trans type_pl=_("tokens") %}You can find the rules for the {{ type_pl }} on each task's description page.{% endtrans %} -

- -

- {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} - {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} -

- {% elif tokens_tasks == TOKEN_MODE_INFINITE %} -

- {% trans %}You have a set of tokens shared among all tasks.{% endtrans %} - {{ contest|extract_token_params|format_token_rules }} -

- -

- {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} - {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} -

- {% else %} -

- {% trans %}You have two types of tokens: a set of contest-tokens shared among all tasks and a distinct set of task-tokens for each task.{% endtrans %} - {{ contest|extract_token_params|format_token_rules(t_type="contest") }} - {% trans type_pl=_("task-tokens") %}You can find the rules for the {{ type_pl }} on each task's description page.{% endtrans %} -

- -

- {% trans %}You can see the detailed result of a submission by using two tokens on it, one of each type.{% endtrans %} - {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} -

- {% endif %} -{% endif %} +

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

+
+
+

+ {% if phase == -1 %} + {% trans %}The contest hasn't started yet.{% endtrans %} +

+

+ {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, + stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} + The contest will start at {{ start_time }} and will end at {{ stop_time }}. + {% endtrans %} + {% elif phase == 0 %} + {% trans %}The contest is currently running.{% endtrans %} +

+

+ {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, + stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} + The contest started at {{ start_time }} and will end at {{ stop_time }}. + {% endtrans %} + {% elif phase >= +1 %} + {% trans %}The contest has already ended.{% endtrans %} +

+

+ {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, + stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} + The contest started at {{ start_time }} and ended at {{ stop_time }}. + {% endtrans %} + {% endif %} +

+ {% if contest.analysis_enabled %} +

+ {% if phase == +1 %} + {% trans %}The analysis mode hasn't started yet.{% endtrans %} +

+

+ {% trans start_time=contest.analysis_start|format_datetime_smart, + stop_time=contest.analysis_stop|format_datetime_smart %} + The analysis mode will start at {{ start_time }} and will end at {{ stop_time }}. + {% endtrans %} + {% elif phase == +2 %} + {% trans %}The analysis mode is currently running.{% endtrans %} +

+

+ {% trans start_time=contest.analysis_start|format_datetime_smart, + stop_time=contest.analysis_stop|format_datetime_smart %} + The analysis mode started at {{ start_time }} and will end at {{ stop_time }}. + {% endtrans %} + {% elif phase == +3 %} + {% trans %}The analysis mode has already ended.{% endtrans %} +

+

+ {% trans start_time=contest.analysis_start|format_datetime_smart, + stop_time=contest.analysis_stop|format_datetime_smart %} + The analysis mode started at {{ start_time }} and ended at {{ stop_time }}. + {% endtrans %} + {% endif %} +

-{% if contest.max_submission_number is not none %} -

- {% trans submissions=contest.max_submission_number %}You can submit at most {{ submissions }} solutions during this contest.{% endtrans %} -

-{% endif %} + {% endif %} -{% if contest.max_user_test_number is not none %} -

- {% trans user_tests=contest.max_user_test_number %}You can submit at most {{ user_tests }} user tests during this contest.{% endtrans %} -

-{% endif %} -
-{% if contest.per_user_time is not none %} -
-
-

- {# TODO would be very nice to write something like "just for 3 consecutive hours"... #} - {% trans per_user_time=contest.per_user_time|format_timedelta %}Every user is allowed to compete (i.e. submit solutions) for a uninterrupted time frame of {{ per_user_time }}.{% endtrans %} -

- -

- {% if actual_phase == -2 %} - {% trans %}As soon as the contest starts you can choose to start your time frame.{% endtrans %} - {%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} - {% elif actual_phase == -1 %} - {% trans %}By clicking on the button below you can start your time frame.{% endtrans %} - {%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} - {% elif actual_phase == 0 %} - {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame at {{ start_time }}.{% endtrans %} - {%+ trans %}You can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} - {% elif actual_phase == +1 %} - {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame at {{ start_time }} and you already finished it.{% endtrans %} - {%+ trans %}There's nothing you can do now.{% endtrans %} - {% elif actual_phase >= +2 %} - {% if participation.starting_time is none %} - {% trans %}You never started your time frame. Now it's too late.{% endtrans %} - {% else %} - {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame at {{ start_time }} and you already finished it.{% endtrans %} - {% endif %} - {% if actual_phase != +3 %} - {%+ trans %}There's nothing you can do now.{% endtrans %} - {% endif %} - {% endif %} + + {% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} + {% if tokens_contest == TOKEN_MODE_INFINITE and tokens_tasks == TOKEN_MODE_INFINITE %} +

+ {% trans %}You have an infinite number of tokens.{% endtrans %}

- {% if actual_phase == -1 %} -
- {{ xsrf_form_html|safe }} - - -
- {% endif %} +

+ {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last + one.{% endtrans %} +

+ {% elif tokens_contest == TOKEN_MODE_INFINITE %} +

+ {% trans %}You have a distinct set of tokens for each task.{% endtrans %} + {%+ trans type_pl=_("tokens") %}You can find the rules for the {{ type_pl }} on each task's description + page.{% endtrans %} +

+ +

+ {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last + one.{% endtrans %} +

+ {% elif tokens_tasks == TOKEN_MODE_INFINITE %} +

+ {% trans %}You have a set of tokens shared among all tasks.{% endtrans %} + {{ contest|extract_token_params|format_token_rules }} +

+ +

+ {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last + one.{% endtrans %} +

+ {% else %} +

+ {% trans %}You have two types of tokens: a set of contest-tokens shared among all tasks and a + distinct set of task-tokens for each task.{% endtrans %} + {{ contest|extract_token_params|format_token_rules(t_type="contest") }} + {% trans type_pl=_("task-tokens") %}You can find the rules for the {{ type_pl }} on each task's + description page.{% endtrans %} +

+ +

+ {% trans %}You can see the detailed result of a submission by using two tokens on it, one of each + type.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last + one.{% endtrans %} +

+ {% endif %} + {% endif %} + + {% if contest.max_submission_number is not none %} +

+ {% trans submissions=contest.max_submission_number %}You can submit at most {{ submissions }} solutions + during this contest.{% endtrans %} +

+ {% endif %} + + {% if contest.max_user_test_number is not none %} +

+ {% trans user_tests=contest.max_user_test_number %}You can submit at most {{ user_tests }} user tests + during this contest.{% endtrans %} +

+ {% endif %}
+ {% if contest.per_user_time is not none %} +
+
+

+ {# TODO would be very nice to write something like "just for 3 consecutive hours"... #} + {% trans per_user_time=contest.per_user_time|format_timedelta %}Every user is allowed to compete + (i.e. submit solutions) for a uninterrupted time frame of {{ per_user_time }}.{% endtrans %} +

+ +

+ {% if actual_phase == -2 %} + {% trans %}As soon as the contest starts you can choose to start your time frame.{% endtrans %} + {%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the + end of the contest, whatever comes first.{% endtrans %} + {% elif actual_phase == -1 %} + {% trans %}By clicking on the button below you can start your time frame.{% endtrans %} + {%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the + end of the contest, whatever comes first.{% endtrans %} + {% elif actual_phase == 0 %} + {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame + at {{ start_time }}.{% endtrans %} + {%+ trans %}You can submit solutions until the end of the time frame or until the end of the + contest, whatever comes first.{% endtrans %} + {% elif actual_phase == +1 %} + {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame + at {{ start_time }} and you already finished it.{% endtrans %} + {%+ trans %}There's nothing you can do now.{% endtrans %} + {% elif actual_phase >= +2 %} + {% if participation.starting_time is none %} + {% trans %}You never started your time frame. Now it's too late.{% endtrans %} + {% else %} + {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame + at {{ start_time }} and you already finished it.{% endtrans %} + {% endif %} + {% if actual_phase != +3 %} + {%+ trans %}There's nothing you can do now.{% endtrans %} + {% endif %} + {% endif %} +

+ + {% if actual_phase == -1 %} +
+ {{ xsrf_form_html|safe }} + + +
+ {% endif %} + +
+
+ {% endif %}
-{% endif %} -
-{% if actual_phase == 0 or actual_phase == 3 or participation.unrestricted or (0 <= actual_phase <= 3 and contest.allow_unofficial_submission_before_analysis_mode)%} -

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

+ {% if actual_phase == 0 or actual_phase == 3 or participation.unrestricted or (0 <= actual_phase <= 3 and contest.allow_unofficial_submission_before_analysis_mode)%} +

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

- - - - - - - - - - -{% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} - -{% endif %} - - - -{% set extensions = "[%s]"|format(contest.languages|map("to_language")|map(attribute="source_extension")|unique|join("|")) %} -{% for t_iter in contest.tasks %} - - - - - - - - {% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} - + + + + + + + + + + {% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} + + {% endif %} + + + + {% set extensions = + "[%s]"|format(contest.languages|map("to_language")|map(attribute="source_extension")|unique|join("|")) %} + {% for t_iter in contest.tasks %} + + + + + + + + + {% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} + + {% endif %} + + {% endfor %} + +
{% trans %}Task{% endtrans %}{% trans %}Name{% endtrans %}{% trans %}Time limit{% endtrans %}{% trans %}Memory limit{% endtrans %}{% trans %}Type{% endtrans %}{% trans %}Files{% endtrans %}{% trans %}Tokens{% endtrans %}
{{ t_iter.name }}{{ t_iter.title }} - {% if t_iter.active_dataset.time_limit is not none %} - {{ t_iter.active_dataset.time_limit|format_duration(length="long") }} - {% else %} - {% trans %}N/A{% endtrans %} - {% endif %} - - {% if t_iter.active_dataset.memory_limit is not none %} - {{ t_iter.active_dataset.memory_limit|format_size }} - {% else %} - {% trans %}N/A{% endtrans %} - {% endif %} - {{ get_task_type(dataset=t_iter.active_dataset).name }}{{ t_iter.submission_format|map("replace", ".%l", extensions)|join(" ") }} - {% if t_iter.token_mode == TOKEN_MODE_FINITE or t_iter.token_mode == TOKEN_MODE_INFINITE %} - {% trans %}Yes{% endtrans %} - {% else %} - {% trans %}No{% endtrans %} - {% endif %} -
{% trans %}Score{% endtrans %}{% trans %}Task{% endtrans %}{% trans %}Name{% endtrans %}{% trans %}Time limit{% endtrans %}{% trans %}Memory limit{% endtrans %}{% trans %}Type{% endtrans %}{% trans %}Files{% endtrans %}{% trans %}Tokens{% endtrans %}
+ {{ task_scores[t_iter.id][2] }}{{ t_iter.name }}{{ t_iter.title }} + {% if t_iter.active_dataset.time_limit is not none %} + {{ t_iter.active_dataset.time_limit|format_duration(length="long") }} + {% else %} + {% trans %}N/A{% endtrans %} + {% endif %} + + {% if t_iter.active_dataset.memory_limit is not none %} + {{ t_iter.active_dataset.memory_limit|format_size }} + {% else %} + {% trans %}N/A{% endtrans %} + {% endif %} + {{ get_task_type(dataset=t_iter.active_dataset).name }}{{ t_iter.submission_format|map("replace", ".%l", extensions)|join(" ") }} + {% if t_iter.token_mode == TOKEN_MODE_FINITE or t_iter.token_mode == TOKEN_MODE_INFINITE %} + {% trans %}Yes{% endtrans %} + {% else %} + {% trans %}No{% endtrans %} + {% endif %} +
{% endif %} - -{% endfor %} - - -{% endif %}
{% endblock core %} From 4ae18e9a95083ce13ea5d4dc28380aaf3609ca66 Mon Sep 17 00:00:00 2001 From: Pasit Sangprachathanarak Date: Fri, 1 Aug 2025 17:22:53 +0700 Subject: [PATCH 02/10] Restore Formatting, Make it toggleable --- cms/db/contest.py | 3 + cms/server/admin/handlers/contest.py | 1 + cms/server/admin/templates/contest.html | 9 + cms/server/contest/handlers/main.py | 37 +- cms/server/contest/templates/overview.html | 458 ++++++++++----------- cmscontrib/updaters/update_from_1.5.sql | 3 + 6 files changed, 258 insertions(+), 253 deletions(-) diff --git a/cms/db/contest.py b/cms/db/contest.py index c245a3534f..d3c0c231d5 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 df6c6f930d..dcbd2bf3bd 100644 --- a/cms/server/contest/handlers/main.py +++ b/cms/server/contest/handlers/main.py @@ -107,19 +107,30 @@ def render_params(self): ret["participation"] = participation - # Compute public scores for all tasks - 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) - public_score = round(public_score, task.score_precision) - 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 + # 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 + ) + public_score = round(public_score, task.score_precision) + 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 diff --git a/cms/server/contest/templates/overview.html b/cms/server/contest/templates/overview.html index d2c31c81d6..170658946e 100644 --- a/cms/server/contest/templates/overview.html +++ b/cms/server/contest/templates/overview.html @@ -2,211 +2,188 @@ {% set page = "overview" %} -{% block additional_js %} - -{% endblock additional_js %} - {% block core %}
- - -

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

-
-
-

- {% if phase == -1 %} - {% trans %}The contest hasn't started yet.{% endtrans %} -

-

- {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, - stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} - The contest will start at {{ start_time }} and will end at {{ stop_time }}. - {% endtrans %} - {% elif phase == 0 %} - {% trans %}The contest is currently running.{% endtrans %} -

-

- {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, - stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} - The contest started at {{ start_time }} and will end at {{ stop_time }}. - {% endtrans %} - {% elif phase >= +1 %} - {% trans %}The contest has already ended.{% endtrans %} -

-

- {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, - stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} - The contest started at {{ start_time }} and ended at {{ stop_time }}. - {% endtrans %} - {% endif %} -

- {% if contest.analysis_enabled %} -

- {% if phase == +1 %} - {% trans %}The analysis mode hasn't started yet.{% endtrans %} -

-

- {% trans start_time=contest.analysis_start|format_datetime_smart, - stop_time=contest.analysis_stop|format_datetime_smart %} - The analysis mode will start at {{ start_time }} and will end at {{ stop_time }}. - {% endtrans %} - {% elif phase == +2 %} - {% trans %}The analysis mode is currently running.{% endtrans %} -

-

- {% trans start_time=contest.analysis_start|format_datetime_smart, - stop_time=contest.analysis_stop|format_datetime_smart %} - The analysis mode started at {{ start_time }} and will end at {{ stop_time }}. - {% endtrans %} - {% elif phase == +3 %} - {% trans %}The analysis mode has already ended.{% endtrans %} -

-

- {% trans start_time=contest.analysis_start|format_datetime_smart, - stop_time=contest.analysis_stop|format_datetime_smart %} - The analysis mode started at {{ start_time }} and ended at {{ stop_time }}. - {% endtrans %} - {% endif %} -

- - {% endif %} - - - - {% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} - {% if tokens_contest == TOKEN_MODE_INFINITE and tokens_tasks == TOKEN_MODE_INFINITE %} -

- {% trans %}You have an infinite number of tokens.{% endtrans %} -

- -

- {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} - {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last - one.{% endtrans %} -

- {% elif tokens_contest == TOKEN_MODE_INFINITE %} -

- {% trans %}You have a distinct set of tokens for each task.{% endtrans %} - {%+ trans type_pl=_("tokens") %}You can find the rules for the {{ type_pl }} on each task's description - page.{% endtrans %} -

- -

- {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} - {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last - one.{% endtrans %} -

- {% elif tokens_tasks == TOKEN_MODE_INFINITE %} -

- {% trans %}You have a set of tokens shared among all tasks.{% endtrans %} - {{ contest|extract_token_params|format_token_rules }} -

+ -

- {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} - {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last - one.{% endtrans %} -

- {% else %} -

- {% trans %}You have two types of tokens: a set of contest-tokens shared among all tasks and a - distinct set of task-tokens for each task.{% endtrans %} - {{ contest|extract_token_params|format_token_rules(t_type="contest") }} - {% trans type_pl=_("task-tokens") %}You can find the rules for the {{ type_pl }} on each task's - description page.{% endtrans %} -

+

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

+
+
+

+{% if phase == -1 %} + {% trans %}The contest hasn't started yet.{% endtrans %} +

+

+ {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, + stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} + The contest will start at {{ start_time }} and will end at {{ stop_time }}. + {% endtrans %} +{% elif phase == 0 %} + {% trans %}The contest is currently running.{% endtrans %} +

+

+ {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, + stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} + The contest started at {{ start_time }} and will end at {{ stop_time }}. + {% endtrans %} +{% elif phase >= +1 %} + {% trans %}The contest has already ended.{% endtrans %} +

+

+ {% trans start_time=(contest.start + participation.delay_time)|format_datetime_smart, + stop_time=(contest.stop + participation.delay_time + participation.extra_time)|format_datetime_smart %} + The contest started at {{ start_time }} and ended at {{ stop_time }}. + {% endtrans %} +{% endif %} +

+{% if contest.analysis_enabled %} +

+ {% if phase == +1 %} + {% trans %}The analysis mode hasn't started yet.{% endtrans %} +

+

+ {% trans start_time=contest.analysis_start|format_datetime_smart, + stop_time=contest.analysis_stop|format_datetime_smart %} + The analysis mode will start at {{ start_time }} and will end at {{ stop_time }}. + {% endtrans %} + {% elif phase == +2 %} + {% trans %}The analysis mode is currently running.{% endtrans %} +

+

+ {% trans start_time=contest.analysis_start|format_datetime_smart, + stop_time=contest.analysis_stop|format_datetime_smart %} + The analysis mode started at {{ start_time }} and will end at {{ stop_time }}. + {% endtrans %} + {% elif phase == +3 %} + {% trans %}The analysis mode has already ended.{% endtrans %} +

+

+ {% trans start_time=contest.analysis_start|format_datetime_smart, + stop_time=contest.analysis_stop|format_datetime_smart %} + The analysis mode started at {{ start_time }} and ended at {{ stop_time }}. + {% endtrans %} + {% endif %} +

+ +{% endif %} + + + +{% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} + {% if tokens_contest == TOKEN_MODE_INFINITE and tokens_tasks == TOKEN_MODE_INFINITE %} +

+ {% trans %}You have an infinite number of tokens.{% endtrans %} +

+ +

+ {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} +

+ {% elif tokens_contest == TOKEN_MODE_INFINITE %} +

+ {% trans %}You have a distinct set of tokens for each task.{% endtrans %} + {%+ trans type_pl=_("tokens") %}You can find the rules for the {{ type_pl }} on each task's description page.{% endtrans %} +

+ +

+ {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} +

+ {% elif tokens_tasks == TOKEN_MODE_INFINITE %} +

+ {% trans %}You have a set of tokens shared among all tasks.{% endtrans %} + {{ contest|extract_token_params|format_token_rules }} +

+ +

+ {% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} +

+ {% else %} +

+ {% trans %}You have two types of tokens: a set of contest-tokens shared among all tasks and a distinct set of task-tokens for each task.{% endtrans %} + {{ contest|extract_token_params|format_token_rules(t_type="contest") }} + {% trans type_pl=_("task-tokens") %}You can find the rules for the {{ type_pl }} on each task's description page.{% endtrans %} +

+ +

+ {% trans %}You can see the detailed result of a submission by using two tokens on it, one of each type.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} +

+ {% endif %} +{% endif %} -

- {% trans %}You can see the detailed result of a submission by using two tokens on it, one of each - type.{% endtrans %} - {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last - one.{% endtrans %} -

- {% endif %} - {% endif %} +{% if contest.max_submission_number is not none %} +

+ {% trans submissions=contest.max_submission_number %}You can submit at most {{ submissions }} solutions during this contest.{% endtrans %} +

+{% endif %} - {% if contest.max_submission_number is not none %} -

- {% trans submissions=contest.max_submission_number %}You can submit at most {{ submissions }} solutions - during this contest.{% endtrans %} -

- {% endif %} +{% if contest.max_user_test_number is not none %} +

+ {% trans user_tests=contest.max_user_test_number %}You can submit at most {{ user_tests }} user tests during this contest.{% endtrans %} +

+{% endif %} - {% if contest.max_user_test_number is not none %} -

- {% trans user_tests=contest.max_user_test_number %}You can submit at most {{ user_tests }} user tests - during this contest.{% endtrans %} +

+{% if contest.per_user_time is not none %} +
+
+

+ {# TODO would be very nice to write something like "just for 3 consecutive hours"... #} + {% trans per_user_time=contest.per_user_time|format_timedelta %}Every user is allowed to compete (i.e. submit solutions) for a uninterrupted time frame of {{ per_user_time }}.{% endtrans %} +

+ +

+ {% if actual_phase == -2 %} + {% trans %}As soon as the contest starts you can choose to start your time frame.{% endtrans %} + {%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} + {% elif actual_phase == -1 %} + {% trans %}By clicking on the button below you can start your time frame.{% endtrans %} + {%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} + {% elif actual_phase == 0 %} + {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame at {{ start_time }}.{% endtrans %} + {%+ trans %}You can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} + {% elif actual_phase == +1 %} + {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame at {{ start_time }} and you already finished it.{% endtrans %} + {%+ trans %}There's nothing you can do now.{% endtrans %} + {% elif actual_phase >= +2 %} + {% if participation.starting_time is none %} + {% trans %}You never started your time frame. Now it's too late.{% endtrans %} + {% else %} + {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame at {{ start_time }} and you already finished it.{% endtrans %} + {% endif %} + {% if actual_phase != +3 %} + {%+ trans %}There's nothing you can do now.{% endtrans %} + {% endif %} + {% endif %}

- {% endif %} - -
- {% if contest.per_user_time is not none %} -
-
-

- {# TODO would be very nice to write something like "just for 3 consecutive hours"... #} - {% trans per_user_time=contest.per_user_time|format_timedelta %}Every user is allowed to compete - (i.e. submit solutions) for a uninterrupted time frame of {{ per_user_time }}.{% endtrans %} -

-

- {% if actual_phase == -2 %} - {% trans %}As soon as the contest starts you can choose to start your time frame.{% endtrans %} - {%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the - end of the contest, whatever comes first.{% endtrans %} - {% elif actual_phase == -1 %} - {% trans %}By clicking on the button below you can start your time frame.{% endtrans %} - {%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the - end of the contest, whatever comes first.{% endtrans %} - {% elif actual_phase == 0 %} - {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame - at {{ start_time }}.{% endtrans %} - {%+ trans %}You can submit solutions until the end of the time frame or until the end of the - contest, whatever comes first.{% endtrans %} - {% elif actual_phase == +1 %} - {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame - at {{ start_time }} and you already finished it.{% endtrans %} - {%+ trans %}There's nothing you can do now.{% endtrans %} - {% elif actual_phase >= +2 %} - {% if participation.starting_time is none %} - {% trans %}You never started your time frame. Now it's too late.{% endtrans %} - {% else %} - {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame - at {{ start_time }} and you already finished it.{% endtrans %} - {% endif %} - {% if actual_phase != +3 %} - {%+ trans %}There's nothing you can do now.{% endtrans %} - {% endif %} - {% endif %} -

- - {% if actual_phase == -1 %} -
- {{ xsrf_form_html|safe }} - - -
- {% endif %} + {% if actual_phase == -1 %} +
+ {{ xsrf_form_html|safe }} + + +
+ {% endif %} -
- {% endif %}
+{% endif %} +
- {% if actual_phase == 0 or actual_phase == 3 or participation.unrestricted or (0 <= actual_phase <= 3 and contest.allow_unofficial_submission_before_analysis_mode)%} -

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

+{% if actual_phase == 0 or actual_phase == 3 or participation.unrestricted or (0 <= actual_phase <= 3 and contest.allow_unofficial_submission_before_analysis_mode)%} +

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

- - - - - - - - - - - - {% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} - - {% endif %} - - - - {% set extensions = - "[%s]"|format(contest.languages|map("to_language")|map(attribute="source_extension")|unique|join("|")) %} - {% for t_iter in contest.tasks %} - - - - - - - - - {% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} - - {% endif %} - - {% endfor %} - -
{% trans %}Score{% endtrans %}{% trans %}Task{% endtrans %}{% trans %}Name{% endtrans %}{% trans %}Time limit{% endtrans %}{% trans %}Memory limit{% endtrans %}{% trans %}Type{% endtrans %}{% trans %}Files{% endtrans %}{% trans %}Tokens{% endtrans %}
- {{ task_scores[t_iter.id][2] }}{{ t_iter.name }}{{ t_iter.title }} - {% if t_iter.active_dataset.time_limit is not none %} - {{ t_iter.active_dataset.time_limit|format_duration(length="long") }} - {% else %} - {% trans %}N/A{% endtrans %} - {% endif %} - - {% if t_iter.active_dataset.memory_limit is not none %} - {{ t_iter.active_dataset.memory_limit|format_size }} - {% else %} - {% trans %}N/A{% endtrans %} - {% endif %} - {{ get_task_type(dataset=t_iter.active_dataset).name }}{{ t_iter.submission_format|map("replace", ".%l", extensions)|join(" ") }} - {% if t_iter.token_mode == TOKEN_MODE_FINITE or t_iter.token_mode == TOKEN_MODE_INFINITE %} - {% trans %}Yes{% endtrans %} - {% else %} - {% trans %}No{% endtrans %} - {% endif %} -
+ + +{% if contest.show_task_scores_in_overview %} + {% trans %}Score{% endtrans %} +{% endif %} + {% trans %}Task{% endtrans %} + {% trans %}Name{% endtrans %} + {% trans %}Time limit{% endtrans %} + {% trans %}Memory limit{% endtrans %} + {% trans %}Type{% endtrans %} + {% trans %}Files{% endtrans %} +{% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} + {% trans %}Tokens{% endtrans %} +{% endif %} + + + +{% set extensions = "[%s]"|format(contest.languages|map("to_language")|map(attribute="source_extension")|unique|join("|")) %} +{% for t_iter in contest.tasks %} + +{% if contest.show_task_scores_in_overview and task_scores is defined %} + {{ task_scores[t_iter.id][2] }} +{% endif %} + {{ t_iter.name }} + {{ t_iter.title }} + + {% if t_iter.active_dataset.time_limit is not none %} + {{ t_iter.active_dataset.time_limit|format_duration(length="long") }} + {% else %} + {% trans %}N/A{% endtrans %} + {% endif %} + + + {% if t_iter.active_dataset.memory_limit is not none %} + {{ t_iter.active_dataset.memory_limit|format_size }} + {% else %} + {% trans %}N/A{% endtrans %} + {% endif %} + + {{ get_task_type(dataset=t_iter.active_dataset).name }} + {{ t_iter.submission_format|map("replace", ".%l", extensions)|join(" ") }} + {% if tokens_contest != TOKEN_MODE_DISABLED and tokens_tasks != TOKEN_MODE_DISABLED %} + + {% if t_iter.token_mode == TOKEN_MODE_FINITE or t_iter.token_mode == TOKEN_MODE_INFINITE %} + {% trans %}Yes{% endtrans %} + {% else %} + {% trans %}No{% endtrans %} + {% endif %} + {% endif %} + +{% endfor %} + + +{% endif %}
{% endblock core %} diff --git a/cmscontrib/updaters/update_from_1.5.sql b/cmscontrib/updaters/update_from_1.5.sql index c22b736c6a..10ddf0b4ce 100644 --- a/cmscontrib/updaters/update_from_1.5.sql +++ b/cmscontrib/updaters/update_from_1.5.sql @@ -42,4 +42,7 @@ 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; + COMMIT; From cbe5f8ea3a9ea4cce6a2bc88acd56299bbe9d128 Mon Sep 17 00:00:00 2001 From: Pasit Sangprachathanarak Date: Fri, 1 Aug 2025 17:37:57 +0700 Subject: [PATCH 03/10] Fix color not displaying --- cms/server/contest/templates/overview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/server/contest/templates/overview.html b/cms/server/contest/templates/overview.html index 170658946e..850683da08 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 actual_phase == 3 or participation.unrestricted or (0 <= actual_phase <= 3 and contest.allow_unofficial_submission_before_analysis_mode)%}

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

- +