Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bb17b1a
Add a Score counter to task overview
pxsit Jul 23, 2025
6653f15
Merge branch 'main' into add-counter
pxsit Jul 23, 2025
3d62b5d
Merge branch 'main' into add-counter
pxsit Jul 23, 2025
6b52ff7
Merge branch 'main' into add-counter
pxsit Jul 28, 2025
8428046
Merge branch 'main' into add-counter
pxsit Aug 1, 2025
4ae18e9
Restore Formatting, Make it toggleable
pxsit Aug 1, 2025
cbe5f8e
Fix color not displaying
pxsit Aug 1, 2025
8055bf6
Fix
pxsit Aug 3, 2025
fc50113
Fix the Fix
pxsit Aug 4, 2025
251c8b5
Fix the Fix that Fix the Fix
pxsit Aug 4, 2025
527bdea
Merge branch 'main' into add-counter
pxsit Aug 5, 2025
95cc100
Update cws_style.css
pxsit Aug 5, 2025
e60f328
Merge branch 'main' into add-counter
pxsit Aug 5, 2025
13c491e
Add credit for original owner?
pxsit Aug 5, 2025
95349b0
Merge branch 'main' into add-counter
pxsit Aug 6, 2025
28c1ae1
Merge branch 'main' into add-counter
pxsit Aug 6, 2025
e0840f6
Update update_from_1.5.sql
pxsit Aug 6, 2025
a958b48
Merge branch 'main' into add-counter
pxsit Aug 8, 2025
7a3b7b7
Merge branch 'main' into add-counter
pxsit Aug 11, 2025
fa4e7ca
Merge branch 'main' into add-counter
pxsit Aug 15, 2025
4b0bd83
Merge branch 'main' into add-counter
pxsit Aug 16, 2025
d12ba11
Merge branch 'cms-dev:main' into add-counter
pxsit Sep 21, 2025
85d0eb8
Merge branch 'cms-dev:main' into add-counter
pxsit Sep 30, 2025
3181514
Merge branch 'cms-dev:main' into add-counter
pxsit Oct 8, 2025
003a7f9
Merge branch 'cms-dev:main' into add-counter
pxsit Oct 23, 2025
7aac12b
Remove unused PrintJob import from main.py
pxsit Nov 17, 2025
1134711
Merge branch 'main' into add-counter
pxsit Nov 17, 2025
6411ec3
Merge branch 'main' into add-counter
pxsit Jan 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cms/db/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions cms/server/admin/handlers/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
9 changes: 9 additions & 0 deletions cms/server/admin/templates/contest.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ <h1>Contest configuration</h1>
<input type="checkbox" id="allow_unofficial_submission_before_analysis_mode" name="allow_unofficial_submission_before_analysis_mode" {{ "checked" if contest.allow_unofficial_submission_before_analysis_mode else "" }}/>
</td>
</tr>
<tr>
<td>
<span class="info" title="Whether to show task scores in the overview page for contestants."></span>
<label for="show_task_scores_in_overview">Show task scores in overview page</label>
</td>
<td>
<input type="checkbox" id="show_task_scores_in_overview" name="show_task_scores_in_overview" {{ "checked" if contest.show_task_scores_in_overview else "" }}/>
</td>
</tr>

<tr><td colspan=2><h2>Logging in</h2></td></tr>
<tr>
Expand Down
60 changes: 59 additions & 1 deletion cms/server/contest/handlers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
# Copyright © 2014 Fabian Gundlach <[email protected]>
# Copyright © 2015-2018 William Di Luigi <[email protected]>
# Copyright © 2021 Grace Hawkins <[email protected]>
# Copyright © 2025 Pasit Sangprachathanarak <[email protected]>
# 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
Expand Down Expand Up @@ -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
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'Token' is not used.
Import of 'Dataset' is not used.

Suggested change
from cms.db import User, Participation, Team, Submission, Token, Task, Dataset
from cms.db import User, Participation, Team, Submission, Task

Copilot uses AI. Check for mistakes.
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
Expand All @@ -74,8 +78,62 @@ class MainHandler(ContestHandler):
"""
@multi_contest
def get(self):
self.r_params = self.render_params()
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assignment to self.r_params in the get method is unnecessary and could cause confusion. The parent class ContestHandler.prepare() method already calls self.render_params() and assigns the result to self.r_params at line 104 of contest.py. This line should be removed and the code should simply call self.render("overview.html", **self.r_params) directly.

Suggested change
self.r_params = self.render_params()

Copilot uses AI. Check for mistakes.
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()
)
Comment on lines +88 to +101
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code queries the Participation object again even though it should already be available via self.current_user (which is set by the parent class's get_current_user() method). Additionally, the parent class's render_params() method already queries the participation and sets it in the render params at line 210-211 of contest.py. This duplicate query with extensive joins could negatively impact performance and is unnecessary.

Suggested change
# 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()
)
# Reuse the participation already loaded by the parent class instead of
# issuing another heavy query here.
participation = ret.get("participation") or self.current_user

Copilot uses AI. Check for mistakes.

self.contest = (
self.sql_session.query(Contest)
.filter(Contest.id == participation.contest.id)
.options(joinedload(Contest.tasks).joinedload(Task.active_dataset))
.first()
)
Comment on lines +103 to +108
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code queries the Contest object again and assigns it to self.contest, even though self.contest is already set by the parent class's choose_contest() method (called in prepare() at line 85 of contest.py). This duplicate query is unnecessary and could cause confusion about which contest object is being used. The existing self.contest should be used instead, and if additional related data needs to be loaded, it should be done through appropriate joins or queries without reassigning self.contest.

Copilot uses AI. Check for mistakes.

ret["participation"] = participation

Comment on lines +110 to +111
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code overwrites ret["participation"] which is already set by the parent class's render_params() method at line 211 of contest.py. This is redundant and could cause confusion about which participation object is being used in the template.

Suggested change
ret["participation"] = participation

Copilot uses AI. Check for mistakes.
# 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.
Expand Down
18 changes: 12 additions & 6 deletions cms/server/contest/static/cws_style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
8 changes: 7 additions & 1 deletion cms/server/contest/templates/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ <h2>{% trans %}General information{% endtrans %}</h2>
{% if actual_phase >= 0 or participation.unrestricted %}
<h2>{% trans %}Task overview{% endtrans %}</h2>

<table class="table table-bordered table-striped">
<table class="main_task_list table table-bordered table-striped">
<!-- <colgroup>
<col class="task"/>
<col class="time_limit"/>
Expand All @@ -193,6 +193,9 @@ <h2>{% trans %}Task overview{% endtrans %}</h2>
</colgroup> -->
<thead>
<tr>
{% if contest.show_task_scores_in_overview %}
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an inconsistency in the conditional logic that could cause table column misalignment. Line 196 adds a Score header column when contest.show_task_scores_in_overview is true, but line 215 only adds the score data cell when both contest.show_task_scores_in_overview and task_scores is defined are true. This means if show_task_scores_in_overview is enabled but task_scores is not defined (e.g., when current_user is None), the table will have a Score header but no corresponding data cells, causing a column mismatch. Both conditions should use the same check: contest.show_task_scores_in_overview and task_scores is defined.

Suggested change
{% if contest.show_task_scores_in_overview %}
{% if contest.show_task_scores_in_overview and task_scores is defined %}

Copilot uses AI. Check for mistakes.
<th>{% trans %}Score{% endtrans %}</th>
{% endif %}
<th>{% trans %}Task{% endtrans %}</th>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Time limit{% endtrans %}</th>
Expand All @@ -209,6 +212,9 @@ <h2>{% trans %}Task overview{% endtrans %}</h2>
{% 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("|")) %}
<tr>
{% if contest.show_task_scores_in_overview and task_scores is defined %}
<td class="public_score {{ get_score_class(task_scores[t_iter.id][0], task_scores[t_iter.id][1], t_iter.score_precision) }}">{{ task_scores[t_iter.id][2] }}</td>
{% endif %}
<th>{{ t_iter.name }}</th>
<td>{{ t_iter.title }}</td>
<td>
Expand Down
4 changes: 4 additions & 0 deletions cmscontrib/updaters/update_from_1.5.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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[];

Expand Down
Loading