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 %}
+
+
+ {% with authors=contest.authors.all() %}
+ {% if authors %}
+ -
+ {% trans trimmed count=authors|length, link_authors=link_users(authors) %}
+ The author of this contest is {{ link_authors }}.
+ {% pluralize count %}
+ The authors of this contest are {{ link_authors }}.
+ {% endtrans %}
+
+ {% endif %}
+ {% endwith %}
+ {% with testers=contest.testers.all() %}
+ {% if testers %}
+ -
+ {% trans trimmed count=testers|length, link_testers=link_users(testers) %}
+ Special thanks to {{ link_testers }} for testing and feedback on the problems.
+ {% pluralize count %}
+ Special thanks to {{ link_testers }} for testing and feedback on the problems.
+ {% endtrans %}
+
+ {% endif %}
+ {% endwith %}
+ -
+ {% if contest.is_rated %}
+ {% if contest.rate_all %}
+ {% if contest.rating_floor is not none and contest.rating_ceiling is not none %}
+ {{ _('This contest will be **rated** for **all** participants who have a rating between **%(rating_floor)d** and **%(rating_ceiling)d**, inclusive.', rating_floor=contest.rating_floor, rating_ceiling=contest.rating_ceiling)|markdown('default') }}
+ {% elif contest.rating_floor is not none and contest.rating_ceiling is none %}
+ {{ _('This contest will be **rated** for **all** participants who have a rating of at least **%(rating_floor)d**.', rating_floor=contest.rating_floor)|markdown('default') }}
+ {% elif contest.rating_floor is none and contest.rating_ceiling is not none %}
+ {{ _('This contest will be **rated** for **all** participants who have a rating of at most **%(rating_ceiling)d**.', rating_ceiling=contest.rating_ceiling)|markdown('default') }}
+ {% else %}
+ {{ _('This contest will be **rated** for **all** participants.')|markdown('default') }}
+ {% endif %}
+ {% else %}
+ {% if contest.rating_floor is not none and contest.rating_ceiling is not none %}
+ {{ _('This contest will be **rated** for participants who submit at least once and have a rating between **%(rating_floor)d** and **%(rating_ceiling)d**, inclusive.', rating_floor=contest.rating_floor, rating_ceiling=contest.rating_ceiling)|markdown('default') }}
+ {% elif contest.rating_floor is not none and contest.rating_ceiling is none %}
+ {{ _('This contest will be **rated** for participants who submit at least once and have a rating of at least **%(rating_floor)d**.', rating_floor=contest.rating_floor)|markdown('default') }}
+ {% elif contest.rating_floor is none and contest.rating_ceiling is not none %}
+ {{ _('This contest will be **rated** for participants who submit at least once and have a rating of at most **%(rating_ceiling)d**.', rating_ceiling=contest.rating_ceiling)|markdown('default') }}
+ {% else %}
+ {{ _('This contest will be **rated** for participants who submit at least once.')|markdown('default') }}
+ {% endif %}
+ {% endif %}
+ {% else %}
+ {{ _('This contest will **not** be rated.')|markdown('default') }}
+ {% endif %}
+
+ -
+ {% filter markdown('default') %}
+ {% trans trimmed count=metadata.problem_count %}
+ There is **{{ count }}** problem in this contest.
+ {% pluralize count %}
+ There are **{{ count }}** problems in this contest.
+ {% endtrans %}
+ {% endfilter %}
+
+ -
+ {% if metadata.has_partials %}
+ {{ _('**Partial scoring is enabled** for some or all of these problems.')|markdown('default') }}
+ {% else %}
+ {{ _('This contest **will not use partial scoring**.')|markdown('default') }}
+ {% endif %}
+
+ -
+ {% if metadata.has_pretests %}
+ {{ _('The pretest system **will be used** for some or all of these problems.')|markdown('default') }}
+ {% else %}
+ {{ _('The pretest system **will not be used** for this contest.')|markdown('default') }}
+ {% endif %}
+
+ -
+ {% if metadata.has_submission_cap %}
+ {{ _('Some or all of these problems **have a submission limit**.')|markdown('default') }}
+ {% else %}
+ {{ _('There is **no submission limit** for any of these problems.')|markdown('default') }}
+ {% endif %}
+
+
+ -
+
-
+ {{ _('The contest format is **%(format)s**.', format=contest.format.name)|markdown('default') }}
+
+ {% for line in contest.format.get_short_form_display() %}
+ - {{ line|markdown('default') }}
+ {% endfor %}
+
+
+ -
+ {% if contest.scoreboard_visibility == contest.SCOREBOARD_VISIBLE %}
+ {{ _('The scoreboard will be **visible** for the duration of the contest.')|markdown('default') }}
+ {% elif contest.scoreboard_visibility == contest.SCOREBOARD_AFTER_PARTICIPATION %}
+ {{ _('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') }}
+ {% endif %}
+
+ {% if contest.access_code %}
+ -
+ {{ _('An **access code is required** to join the contest.')|markdown('default') }}
+
+ {% endif %}
+
+
+ {% 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') }}
|