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 %}
+ | {% trans %}Score{% endtrans %} |
+{% endif %}
{% trans %}Task{% endtrans %} |
{% trans %}Name{% endtrans %} |
{% trans %}Time limit{% endtrans %} |
@@ -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 %}
+ | {{ task_scores[t_iter.id][2] }} |
+{% endif %}
{{ 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[];
|