diff --git a/judge/admin/contest.py b/judge/admin/contest.py index d3f603c770..50a0cd1b80 100644 --- a/judge/admin/contest.py +++ b/judge/admin/contest.py @@ -115,7 +115,7 @@ class ContestAdmin(NoBatchDeleteMixin, VersionAdmin): fieldsets = ( (None, {'fields': ('key', 'name', 'authors', 'curators', 'testers')}), (_('Settings'), {'fields': ('is_visible', 'use_clarifications', 'hide_problem_tags', 'hide_problem_authors', - 'run_pretests_only', 'locked_after', 'scoreboard_visibility', + 'show_short_display', 'run_pretests_only', 'locked_after', 'scoreboard_visibility', 'points_precision')}), (_('Scheduling'), {'fields': ('start_time', 'end_time', 'time_limit')}), (_('Details'), {'fields': ('description', 'og_image', 'logo_override_image', 'tags', 'summary')}), diff --git a/judge/contest_format/atcoder.py b/judge/contest_format/atcoder.py index 05e1fb2bdb..f93644534e 100644 --- a/judge/contest_format/atcoder.py +++ b/judge/contest_format/atcoder.py @@ -6,7 +6,7 @@ from django.urls import reverse from django.utils.html import format_html from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy +from django.utils.translation import gettext as _, gettext_lazy, ungettext from judge.contest_format.default import DefaultContestFormat from judge.contest_format.registry import register_contest_format @@ -112,3 +112,16 @@ def display_user_problem(self, participation, contest_problem): ) else: return mark_safe('') + + def get_short_form_display(self): + yield _('The maximum score submission for each problem will be used.') + + penalty = self.config['penalty'] + if penalty: + yield ungettext( + 'Each submission before the first maximum score submission will incur a **penalty of %d minute**.', + 'Each submission before the first maximum score submission will incur a **penalty of %d minutes**.', + penalty, + ) % penalty + + yield _('Ties will be broken by the last score altering submission time.') diff --git a/judge/contest_format/base.py b/judge/contest_format/base.py index 0b70fb2c23..4de7430e3a 100644 --- a/judge/contest_format/base.py +++ b/judge/contest_format/base.py @@ -92,6 +92,15 @@ def get_label_for_problem(self, index): """ raise NotImplementedError() + @abstractmethod + def get_short_form_display(self): + """ + Returns a generator of Markdown strings to display the contest format's settings in short form. + + :return: A generator, where each item is an individual line. + """ + raise NotImplementedError() + @classmethod def best_solution_state(cls, points, total): if not points: diff --git a/judge/contest_format/default.py b/judge/contest_format/default.py index e1e65c3891..0aeb724e53 100644 --- a/judge/contest_format/default.py +++ b/judge/contest_format/default.py @@ -6,7 +6,7 @@ from django.urls import reverse from django.utils.html import format_html from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy +from django.utils.translation import gettext as _, gettext_lazy from judge.contest_format.base import BaseContestFormat from judge.contest_format.registry import register_contest_format @@ -74,3 +74,7 @@ def get_problem_breakdown(self, participation, contest_problems): def get_label_for_problem(self, index): return str(index + 1) + + def get_short_form_display(self): + yield _('The maximum score submission for each problem will be used.') + yield _('Ties will be broken by the sum of the last submission time on problems with a non-zero score.') diff --git a/judge/contest_format/ecoo.py b/judge/contest_format/ecoo.py index fe5bd86559..086e6d550a 100644 --- a/judge/contest_format/ecoo.py +++ b/judge/contest_format/ecoo.py @@ -6,7 +6,7 @@ from django.urls import reverse from django.utils.html import format_html from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy +from django.utils.translation import gettext as _, gettext_lazy, ungettext from judge.contest_format.default import DefaultContestFormat from judge.contest_format.registry import register_contest_format @@ -125,3 +125,25 @@ def display_participation_result(self, participation): points=floatformat(participation.score, -self.contest.points_precision), cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday') if self.config['cumtime'] else '', ) + + def get_short_form_display(self): + yield _('The score on your **last** non-CE submission for each problem will be used.') + + first_ac_bonus = self.config['first_ac_bonus'] + if first_ac_bonus: + yield _( + 'There is a **%d bonus** for fully solving on your first non-CE submission.', + ) % first_ac_bonus + + time_bonus = self.config['time_bonus'] + if time_bonus: + yield ungettext( + 'For every **%d minute** you submit before the end of your window, there will be a **1** point bonus.', + 'For every **%d minutes** you submit before the end of your window, there will be a **1** point bonus.', + time_bonus, + ) % time_bonus + + if self.config['cumtime']: + yield _('Ties will be broken by the sum of the last submission time on **all** problems.') + else: + yield _('Ties by score will **not** be broken.') diff --git a/judge/contest_format/icpc.py b/judge/contest_format/icpc.py index 35eefe16a3..25a8c64675 100644 --- a/judge/contest_format/icpc.py +++ b/judge/contest_format/icpc.py @@ -6,7 +6,7 @@ from django.urls import reverse from django.utils.html import format_html from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy +from django.utils.translation import gettext as _, gettext_lazy, ungettext from judge.contest_format.default import DefaultContestFormat from judge.contest_format.registry import register_contest_format @@ -122,3 +122,17 @@ def get_label_for_problem(self, index): ret += chr((index - 1) % 26 + 65) index = (index - 1) // 26 return ret[::-1] + + def get_short_form_display(self): + yield _('The maximum score submission for each problem will be used.') + + penalty = self.config['penalty'] + if penalty: + yield ungettext( + 'Each submission before the first maximum score submission will incur a **penalty of %d minute**.', + 'Each submission before the first maximum score submission will incur a **penalty of %d minutes**.', + penalty, + ) % penalty + + yield _('Ties will be broken by the sum of the last score altering submission time on problems with a non-zero ' + 'score, followed by the time of the last score altering submission.') diff --git a/judge/contest_format/ioi.py b/judge/contest_format/ioi.py index f9b9f4c1be..55997914c0 100644 --- a/judge/contest_format/ioi.py +++ b/judge/contest_format/ioi.py @@ -1,5 +1,5 @@ from django.db import connection -from django.utils.translation import gettext_lazy +from django.utils.translation import gettext as _, gettext_lazy from judge.contest_format.legacy_ioi import LegacyIOIContestFormat from judge.contest_format.registry import register_contest_format @@ -79,7 +79,7 @@ def update_participation(self, participation): format_data[problem_id]['points'] += subtask_points format_data[problem_id]['time'] = max(dt, format_data[problem_id]['time']) - for _, problem_data in format_data.items(): + for problem_data in format_data.values(): penalty = problem_data['time'] points = problem_data['points'] if self.config['cumtime'] and points: @@ -91,3 +91,12 @@ def update_participation(self, participation): participation.tiebreaker = 0 participation.format_data = format_data participation.save() + + def get_short_form_display(self): + yield _('The maximum score for each problem batch will be used.') + + if self.config['cumtime']: + yield _('Ties will be broken by the sum of the last score altering submission time on problems with a ' + 'non-zero score.') + else: + yield _('Ties by score will **not** be broken.') diff --git a/judge/contest_format/legacy_ioi.py b/judge/contest_format/legacy_ioi.py index 45ac148b61..e3d552cf48 100644 --- a/judge/contest_format/legacy_ioi.py +++ b/judge/contest_format/legacy_ioi.py @@ -6,7 +6,7 @@ from django.urls import reverse from django.utils.html import format_html from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy +from django.utils.translation import gettext as _, gettext_lazy from judge.contest_format.default import DefaultContestFormat from judge.contest_format.registry import register_contest_format @@ -92,3 +92,12 @@ def display_participation_result(self, participation): points=floatformat(participation.score, -self.contest.points_precision), cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday') if self.config['cumtime'] else '', ) + + def get_short_form_display(self): + yield _('The maximum score submission for each problem will be used.') + + if self.config['cumtime']: + yield _('Ties will be broken by the sum of the last score altering submission time on problems with a ' + 'non-zero score.') + else: + yield _('Ties by score will **not** be broken.') diff --git a/judge/migrations/0124_contest_show_short_display.py b/judge/migrations/0124_contest_show_short_display.py new file mode 100644 index 0000000000..ac805fee38 --- /dev/null +++ b/judge/migrations/0124_contest_show_short_display.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2021-06-19 01:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0123_contest_rating_elo_mmr'), + ] + + operations = [ + migrations.AddField( + model_name='contest', + name='show_short_display', + field=models.BooleanField(default=False, help_text='Whether to show a section containing contest settings on the contest page or not.', verbose_name='show short form settings display'), + ), + ] diff --git a/judge/models/contest.py b/judge/models/contest.py index bed0d00efa..7b0f13041b 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -115,6 +115,10 @@ class Contest(models.Model): 'testcases. Commonly set during a contest, then unset ' 'prior to rejudging user submissions when the contest ends.'), default=False) + show_short_display = models.BooleanField(verbose_name=_('show short form settings display'), + help_text=_('Whether to show a section containing contest settings ' + 'on the contest page or not.'), + default=False) is_organization_private = models.BooleanField(verbose_name=_('private to organizations'), default=False) organizations = models.ManyToManyField(Organization, blank=True, verbose_name=_('organizations'), help_text=_('If private, only these organizations may see the contest')) diff --git a/judge/views/contests.py b/judge/views/contests.py index 1cbec1d9ad..d133d4981c 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -234,8 +234,23 @@ def get_context_data(self, **kwargs): .annotate(has_public_editorial=Sum(Case(When(solution__is_public=True, then=1), default=0, output_field=IntegerField()))) \ .add_i18n_name(self.request.LANGUAGE_CODE) - context['contest_has_public_editorials'] = any( - problem.is_public and problem.has_public_editorial for problem in context['contest_problems'] + context['metadata'] = { + 'has_public_editorials': any( + problem.is_public and problem.has_public_editorial for problem in context['contest_problems'] + ), + } + context['metadata'].update( + **self.object.contest_problems + .annotate( + partials_enabled=F('partial').bitand(F('problem__partial')), + pretests_enabled=F('is_pretested').bitand(F('contest__run_pretests_only')), + ) + .aggregate( + has_partials=Sum('partials_enabled'), + has_pretests=Sum('pretests_enabled'), + has_submission_cap=Sum('max_submissions'), + problem_count=Count('id'), + ), ) return context diff --git a/resources/contest.scss b/resources/contest.scss index edde5d1d24..777ced9860 100644 --- a/resources/contest.scss +++ b/resources/contest.scss @@ -93,6 +93,8 @@ #banner { border-bottom: 1px solid rgba(0, 0, 0, 0.2); padding-bottom: 1em; + color: rgb(85, 85, 85); + font-size: $base_font_size; a.date { display: block; @@ -114,8 +116,14 @@ #time { text-align: center; display: block; - color: rgb(85, 85, 85); - font-size: $base_font_size; + } + + div#details ul { + margin-bottom: 0; + list-style: none; + p { + margin: 0; + } } } diff --git a/templates/contest/contest.html b/templates/contest/contest.html index 4673ff62b1..6a4b7bfc77 100644 --- a/templates/contest/contest.html +++ b/templates/contest/contest.html @@ -65,6 +65,115 @@ {% endtrans %} {% endif %} + {% if contest.show_short_display %} +
+ +
+ {% endif %}
@@ -84,7 +193,7 @@

{{ _ {{ _('Points') }} {{ _('AC Rate') }} {{ _('Users') }} - {% if contest_has_public_editorials %} + {% if metadata.has_public_editorials %} {{ _('Editorials') }} {% endif %} @@ -108,7 +217,7 @@

{{ _ {{ problem.user_count }} {% endif %} - {% if contest_has_public_editorials %} + {% if metadata.has_public_editorials %} {% if problem.is_public and problem.has_public_editorial %} {{ _('Editorial') }}