From 074e00785007d01e259cbb0730aab5d1d2ce567f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:47:22 +0000 Subject: [PATCH 1/4] Improve notifications for training day attendance and questions - Add clear messages on announcements/questions pages indicating they only handle training program items, with links to training day pages - Add alerts to side menu showing total unanswered questions and pending delay requests across all training days - Add red circles with counts to each training day in the side menu - Training program questions page now shows alerts for training days with unanswered questions with direct links - Training program attendance page now shows alerts for training days with pending delay requests with direct links - Add orange color styling for delay request indicators to differentiate from question indicators (red) Co-Authored-By: Ron Ryvchin --- cms/server/admin/handlers/archive.py | 20 +++++++++ cms/server/admin/handlers/base.py | 42 +++++++++++++++++++ cms/server/admin/handlers/trainingprogram.py | 21 ++++++++++ cms/server/admin/static/aws_style.css | 4 ++ cms/server/admin/templates/announcements.html | 7 ++++ cms/server/admin/templates/base.html | 19 ++++++++- cms/server/admin/templates/questions.html | 19 +++++++++ .../training_program_attendance.html | 17 ++++++++ 8 files changed, 148 insertions(+), 1 deletion(-) diff --git a/cms/server/admin/handlers/archive.py b/cms/server/admin/handlers/archive.py index bb251c0dce..22d466fee2 100644 --- a/cms/server/admin/handlers/archive.py +++ b/cms/server/admin/handlers/archive.py @@ -700,6 +700,26 @@ def get(self, training_program_id: str): .filter(Question.reply_timestamp.is_(None))\ .filter(Question.ignored.is_(False))\ .count() + + # Get training days with pending delay requests + training_days_with_pending_delays: list[dict] = [] + for td in training_program.training_days: + if td.contest is None: + continue + pending_count = self.sql_session.query(DelayRequest)\ + .join(Participation)\ + .filter(Participation.contest_id == td.contest_id)\ + .filter(DelayRequest.status == "pending")\ + .count() + if pending_count > 0: + training_days_with_pending_delays.append({ + "contest_id": td.contest_id, + "name": td.contest.name, + "pending_count": pending_count, + }) + self.r_params["training_days_with_pending_delays"] = \ + training_days_with_pending_delays + self.render("training_program_attendance.html", **self.r_params) diff --git a/cms/server/admin/handlers/base.py b/cms/server/admin/handlers/base.py index 8057813338..451ff33b51 100644 --- a/cms/server/admin/handlers/base.py +++ b/cms/server/admin/handlers/base.py @@ -509,6 +509,48 @@ def render_params(self) -> dict: .order_by(TrainingProgram.name) .all() ) + + # If viewing a training program, add notification counts for training days + if "training_program" in params and params["training_program"] is not None: + training_program = params["training_program"] + training_day_notifications: dict[int, dict] = {} + total_td_unanswered_questions = 0 + total_td_pending_delay_requests = 0 + + for td in training_program.training_days: + if td.contest is None: + continue + + # Count unanswered questions for this training day + td_unanswered = ( + self.sql_session.query(Question) + .join(Participation) + .filter(Participation.contest_id == td.contest_id) + .filter(Question.reply_timestamp.is_(None)) + .filter(Question.ignored.is_(False)) + .count() + ) + + # Count pending delay requests for this training day + td_pending_delays = ( + self.sql_session.query(DelayRequest) + .join(Participation) + .filter(Participation.contest_id == td.contest_id) + .filter(DelayRequest.status == "pending") + .count() + ) + + training_day_notifications[td.id] = { + "unanswered_questions": td_unanswered, + "pending_delay_requests": td_pending_delays, + } + total_td_unanswered_questions += td_unanswered + total_td_pending_delay_requests += td_pending_delays + + params["training_day_notifications"] = training_day_notifications + params["total_td_unanswered_questions"] = total_td_unanswered_questions + params["total_td_pending_delay_requests"] = total_td_pending_delay_requests + return params def write_error(self, status_code, **kwargs): diff --git a/cms/server/admin/handlers/trainingprogram.py b/cms/server/admin/handlers/trainingprogram.py index e8704544f3..0d2dadaeb6 100644 --- a/cms/server/admin/handlers/trainingprogram.py +++ b/cms/server/admin/handlers/trainingprogram.py @@ -35,6 +35,7 @@ from cms.db import ( Contest, + DelayRequest, TrainingProgram, Participation, Submission, @@ -941,6 +942,26 @@ def get(self, training_program_id: str): .filter(Question.ignored.is_(False))\ .count() + # Get training days with unanswered questions + training_days_with_unanswered: list[dict] = [] + for td in training_program.training_days: + if td.contest is None: + continue + unanswered_count = self.sql_session.query(Question)\ + .join(Participation)\ + .filter(Participation.contest_id == td.contest_id)\ + .filter(Question.reply_timestamp.is_(None))\ + .filter(Question.ignored.is_(False))\ + .count() + if unanswered_count > 0: + training_days_with_unanswered.append({ + "contest_id": td.contest_id, + "name": td.contest.name, + "unanswered_count": unanswered_count, + }) + self.r_params["training_days_with_unanswered_questions"] = \ + training_days_with_unanswered + self.render("questions.html", **self.r_params) diff --git a/cms/server/admin/static/aws_style.css b/cms/server/admin/static/aws_style.css index 06b948f9cf..7386135d6e 100644 --- a/cms/server/admin/static/aws_style.css +++ b/cms/server/admin/static/aws_style.css @@ -547,6 +547,10 @@ body.admin .sidebar-contest a, body.admin .sidebar-item a { text-shadow: none; } +.unread.unread-delay { + background-color: #FF8C00; +} + .secret_notice { font-size: 0.8em; line-height: 1.125em; diff --git a/cms/server/admin/templates/announcements.html b/cms/server/admin/templates/announcements.html index 1880536007..62c3b08911 100644 --- a/cms/server/admin/templates/announcements.html +++ b/cms/server/admin/templates/announcements.html @@ -35,6 +35,13 @@

Announcements

+{% if training_program is defined %} +
+ Note: This page only handles announcements for the training program. + For training day announcements, go to the appropriate training day's announcements page. +
+{% endif %} +

Announcements

diff --git a/cms/server/admin/templates/base.html b/cms/server/admin/templates/base.html index 55500200a4..88ad9617c0 100644 --- a/cms/server/admin/templates/base.html +++ b/cms/server/admin/templates/base.html @@ -481,7 +481,13 @@