From b06295e77b7e3878559ac87aa8ac4eb0f38e60a2 Mon Sep 17 00:00:00 2001 From: Keenan Gugeler Date: Mon, 21 Feb 2022 13:25:29 -0500 Subject: [PATCH] add fully hidden contest scoreboard type This new scoreboard type allows a contest's scoreboard to be permanently hidden to the public. --- judge/models/contest.py | 4 ++- judge/models/tests/test_contest.py | 45 ++++++++++++++++++++++++++++++ judge/views/api/api_v2.py | 3 +- judge/views/problem.py | 7 +++-- judge/views/submission.py | 4 ++- templates/contest/contest.html | 2 ++ 6 files changed, 60 insertions(+), 5 deletions(-) diff --git a/judge/models/contest.py b/judge/models/contest.py index 1bbfa6a1a4..126ae157c4 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -57,10 +57,12 @@ class Contest(models.Model): SCOREBOARD_VISIBLE = 'V' SCOREBOARD_AFTER_CONTEST = 'C' SCOREBOARD_AFTER_PARTICIPATION = 'P' + SCOREBOARD_HIDDEN = 'H' SCOREBOARD_VISIBILITY = ( (SCOREBOARD_VISIBLE, _('Visible')), (SCOREBOARD_AFTER_CONTEST, _('Hidden for duration of contest')), (SCOREBOARD_AFTER_PARTICIPATION, _('Hidden for duration of participation')), + (SCOREBOARD_HIDDEN, _('Hidden permanently')), ) key = models.CharField(max_length=20, verbose_name=_('contest id'), unique=True, validators=[RegexValidator('^[a-z0-9]+$', _('Contest id must be ^[a-z0-9]+$'))]) @@ -234,7 +236,7 @@ def show_scoreboard(self): if (self.scoreboard_visibility in (self.SCOREBOARD_AFTER_CONTEST, self.SCOREBOARD_AFTER_PARTICIPATION) and not self.ended): return False - return True + return self.scoreboard_visibility != self.SCOREBOARD_HIDDEN @property def contest_window_length(self): diff --git a/judge/models/tests/test_contest.py b/judge/models/tests/test_contest.py index 97884a7af8..3c571a28ce 100644 --- a/judge/models/tests/test_contest.py +++ b/judge/models/tests/test_contest.py @@ -108,6 +108,18 @@ def setUpTestData(self): testers=('non_staff_tester',), ) + self.full_hidden_scoreboard_contest = create_contest( + key='full_hidden_board', + start_time=_now - timezone.timedelta(days=100), + end_time=_now - timezone.timedelta(days=1), + time_limit=timezone.timedelta(days=1), + is_visible=True, + scoreboard_visibility=Contest.SCOREBOARD_HIDDEN, + authors=('non_staff_author',), + curators=('staff_contest_edit_own',), + testers=('non_staff_tester',), + ) + for contest_key in ('contest_scoreboard', 'particip_scoreboard', 'visible_scoreboard'): create_contest_participation( contest=contest_key, @@ -137,6 +149,13 @@ def setUpTestData(self): virtual=ContestParticipation.SPECTATE, ) + create_contest_participation( + contest='full_hidden_board', + user='normal_after_window', + real_start=_now - timezone.timedelta(days=5), + virtual=ContestParticipation.LIVE, + ) + self.users['normal'].profile.current_contest = create_contest_participation( contest='hidden_scoreboard', user='normal', @@ -439,6 +458,32 @@ def test_visible_scoreboard_contest_methods(self): } self._test_object_methods_with_users(self.visible_scoreboard_contest, data) + def test_full_hidden_scoreboard_contest_methods(self): + data = { + 'superuser': { + 'can_see_full_scoreboard': self.assertTrue, + }, + 'non_staff_tester': { + 'can_see_full_scoreboard': self.assertFalse, + }, + 'non_staff_author': { + 'can_see_full_scoreboard': self.assertTrue, + }, + 'staff_contest_edit_own': { + 'can_see_full_scoreboard': self.assertTrue, + }, + 'staff_contest_edit_all': { + 'can_see_full_scoreboard': self.assertTrue, + }, + 'normal': { + 'can_see_full_scoreboard': self.assertFalse, + }, + 'normal_after_window': { + 'can_see_full_scoreboard': self.assertFalse, + }, + } + self._test_object_methods_with_users(self.full_hidden_scoreboard_contest, data) + def test_private_contest_methods(self): with self.assertRaises(Contest.PrivateContest): self.private_contest.access_check(self.users['normal']) diff --git a/judge/views/api/api_v2.py b/judge/views/api/api_v2.py index b2591b16cd..f7ad327920 100644 --- a/judge/views/api/api_v2.py +++ b/judge/views/api/api_v2.py @@ -285,7 +285,8 @@ def get_object_data(self, contest): 'rating_floor': contest.rating_floor, 'rating_ceiling': contest.rating_ceiling, 'hidden_scoreboard': contest.scoreboard_visibility in (contest.SCOREBOARD_AFTER_CONTEST, - contest.SCOREBOARD_AFTER_PARTICIPATION), + contest.SCOREBOARD_AFTER_PARTICIPATION, + contest.SCOREBOARD_HIDDEN), 'scoreboard_visibility': contest.scoreboard_visibility, 'is_organization_private': contest.is_organization_private, 'organizations': list( diff --git a/judge/views/problem.py b/judge/views/problem.py index 50ad232cd8..b1c5bf18b0 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -419,8 +419,11 @@ def get_context_data(self, **kwargs): else: context['hot_problems'] = None context['point_start'], context['point_end'], context['point_values'] = 0, 0, {} - context['hide_contest_scoreboard'] = self.contest.scoreboard_visibility in \ - (self.contest.SCOREBOARD_AFTER_CONTEST, self.contest.SCOREBOARD_AFTER_PARTICIPATION) + context['hide_contest_scoreboard'] = self.contest.scoreboard_visibility in ( + self.contest.SCOREBOARD_AFTER_CONTEST, + self.contest.SCOREBOARD_AFTER_PARTICIPATION, + self.contest.SCOREBOARD_HIDDEN, + ) return context def get_noui_slider_points(self): diff --git a/judge/views/submission.py b/judge/views/submission.py index ce73770dee..03baa9df35 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -251,7 +251,9 @@ def _get_queryset(self): contest_queryset = Contest.objects.filter(Q(authors=self.request.profile) | Q(curators=self.request.profile) | Q(scoreboard_visibility=Contest.SCOREBOARD_VISIBLE) | - Q(end_time__lt=timezone.now())).distinct() + Q(end_time__lt=timezone.now())) \ + .exclude(scoreboard_visibility=Contest.SCOREBOARD_HIDDEN) \ + .distinct() queryset = queryset.filter(Q(user=self.request.profile) | Q(contest_object__in=contest_queryset) | Q(contest_object__isnull=True)) diff --git a/templates/contest/contest.html b/templates/contest/contest.html index 0d37c83a4c..b0dd1b14cd 100644 --- a/templates/contest/contest.html +++ b/templates/contest/contest.html @@ -164,6 +164,8 @@ {{ _('The scoreboard will be **hidden** until your window is over.')|markdown('default') }} {% elif contest.scoreboard_visibility == contest.SCOREBOARD_AFTER_CONTEST %} {{ _('The scoreboard will be **hidden** for the entire duration of the contest.')|markdown('default') }} + {% elif contest.scoreboard_visibility == contest.SCOREBOARD_HIDDEN %} + {{ _('The scoreboard will be **hidden**, even after the contest is over.')|markdown('default') }} {% endif %} {% if contest.access_code %}