Skip to content

Commit bc9e872

Browse files
authored
Show problem limits (#499)
* Add limits visibility model and include it in admin panel * Add fetching the tests data in controller * Update the query to include language overrides * Adjust the query, add fixutures and test * Add stringification of limits * Adjust the template * Minor update * Improve limits computing and rendering * Adjust style to display columns in proper manner, update tests * Bugfix * Further bugfix * Add more tests, small refactor * Fix migrations * Update migrations
1 parent 91f5ec2 commit bc9e872

File tree

13 files changed

+679
-11
lines changed

13 files changed

+679
-11
lines changed

oioioi/contestexcl/tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ def _modify_contestexcl(
180180
('problemstatementconfig', 0, 0, 0, 1),
181181
('rankingvisibilityconfig', 0, 0, 0, 1),
182182
('registrationavailabilityconfig', 0, 0, 0, 1),
183+
('limitsvisibilityconfig', 0, 0, 0, 1),
183184
('balloonsdeliveryaccessdata', 0, 0, 0, 1),
184185
('statistics_config', 0, 0, 0, 1),
185186
('exclusivenessconfig_set', len(excl_start_date_forms), 0, 0, 1000),

oioioi/contests/controllers.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from django.utils.safestring import mark_safe
1313
from django.utils.translation import gettext_lazy as _
1414
from django.utils.translation import gettext_noop
15+
from django.db.models import Min, Max, Q
1516

1617
from oioioi.base.utils import (
1718
ObjectWithMixins,
@@ -21,8 +22,10 @@
2122
from oioioi.base.utils.query_helpers import Q_always_true
2223
from oioioi.contests.models import (
2324
Contest,
25+
ProblemInstance,
2426
ProblemStatementConfig,
2527
RankingVisibilityConfig,
28+
LimitsVisibilityConfig,
2629
Round,
2730
RoundTimeExtension,
2831
ScoreReport,
@@ -41,6 +44,7 @@
4144
last_break_between_rounds,
4245
rounds_times,
4346
visible_problem_instances,
47+
process_instances_to_limits,
4448
)
4549
from oioioi.problems.controllers import ProblemController
4650

@@ -586,6 +590,19 @@ def can_see_statement(self, request_or_context, problem_instance):
586590
def default_can_see_statement(self, request_or_context, problem_instance):
587591
return True
588592

593+
def can_see_problems_limits(self, request):
594+
context = self.make_context(request)
595+
lvc = LimitsVisibilityConfig.objects.filter(contest=context.contest)
596+
if lvc.exists() and lvc[0].visible == 'YES':
597+
return True
598+
elif lvc.exists() and lvc[0].visible == 'NO':
599+
return False
600+
else:
601+
return self.default_can_see_problems_limits(request)
602+
603+
def default_can_see_problems_limits(self, request):
604+
return False
605+
589606
def can_submit(self, request, problem_instance, check_round_times=True):
590607
"""Determines if the current user is allowed to submit a solution for
591608
the given problem.
@@ -641,6 +658,105 @@ def is_submissions_limit_exceeded(self, request, problem_instance, kind=None):
641658
request, problem_instance, kind
642659
)
643660

661+
def get_problems_limits(self, request):
662+
"""Returns a dictionary containing data about time and memory limits for a given ProblemInstance:
663+
ProblemInstanceID -> {
664+
'default': (min_time, max_time, min_memory, max_memory),
665+
'cpp': (min_time, max_time, min_memory, max_memory),
666+
'py': (min_time, max_time, min_memory, max_memory)
667+
}
668+
Corresponding dictionary is None if no limits exist
669+
"""
670+
671+
instances = ProblemInstance.objects.filter(contest=request.contest).annotate(
672+
# default limits
673+
min_time=Min('test__time_limit', filter=Q(test__is_active=True)),
674+
max_time=Max('test__time_limit', filter=Q(test__is_active=True)),
675+
min_memory=Min('test__memory_limit', filter=Q(test__is_active=True)),
676+
max_memory=Max('test__memory_limit', filter=Q(test__is_active=True)),
677+
678+
# cpp overridden limits
679+
cpp_min_time=Min(
680+
'test__languageoverridefortest__time_limit',
681+
filter=Q(test__languageoverridefortest__language='cpp') & Q(test__is_active=True)
682+
),
683+
cpp_max_time=Max(
684+
'test__languageoverridefortest__time_limit',
685+
filter=Q(test__languageoverridefortest__language='cpp') & Q(test__is_active=True)
686+
),
687+
cpp_min_memory=Min(
688+
'test__languageoverridefortest__memory_limit',
689+
filter=Q(test__languageoverridefortest__language='cpp') & Q(test__is_active=True)
690+
),
691+
cpp_max_memory=Max(
692+
'test__languageoverridefortest__memory_limit',
693+
filter=Q(test__languageoverridefortest__language='cpp') & Q(test__is_active=True)
694+
),
695+
696+
# python overridden limits
697+
py_min_time=Min(
698+
'test__languageoverridefortest__time_limit',
699+
filter=Q(test__languageoverridefortest__language='py') & Q(test__is_active=True)
700+
),
701+
py_max_time=Max(
702+
'test__languageoverridefortest__time_limit',
703+
filter=Q(test__languageoverridefortest__language='py') & Q(test__is_active=True)
704+
),
705+
py_min_memory=Min(
706+
'test__languageoverridefortest__memory_limit',
707+
filter=Q(test__languageoverridefortest__language='py') & Q(test__is_active=True)
708+
),
709+
py_max_memory=Max(
710+
'test__languageoverridefortest__memory_limit',
711+
filter=Q(test__languageoverridefortest__language='py') & Q(test__is_active=True)
712+
),
713+
714+
# non-overridden test limits in cpp
715+
cpp_min_time_non_overridden=Min(
716+
'test__time_limit',
717+
filter=~Q(test__languageoverridefortest__language='cpp') & Q(test__is_active=True)
718+
),
719+
cpp_max_time_non_overridden=Max(
720+
'test__time_limit',
721+
filter=~Q(test__languageoverridefortest__language='cpp') & Q(test__is_active=True)
722+
),
723+
cpp_min_memory_non_overridden=Min(
724+
'test__memory_limit',
725+
filter=~Q(test__languageoverridefortest__language='cpp') & Q(test__is_active=True)
726+
),
727+
cpp_max_memory_non_overridden=Max(
728+
'test__memory_limit',
729+
filter=~Q(test__languageoverridefortest__language='cpp') & Q(test__is_active=True)
730+
),
731+
732+
# non-overridden test limits in python
733+
py_min_time_non_overridden=Min(
734+
'test__time_limit',
735+
filter=~Q(test__languageoverridefortest__language='py') & Q(test__is_active=True)
736+
),
737+
py_max_time_non_overridden=Max(
738+
'test__time_limit',
739+
filter=~Q(test__languageoverridefortest__language='py') & Q(test__is_active=True)
740+
),
741+
py_min_memory_non_overridden=Min(
742+
'test__memory_limit',
743+
filter=~Q(test__languageoverridefortest__language='py') & Q(test__is_active=True)
744+
),
745+
py_max_memory_non_overridden=Max(
746+
'test__memory_limit',
747+
filter=~Q(test__languageoverridefortest__language='py') & Q(test__is_active=True)
748+
),
749+
).values(
750+
'id',
751+
'min_time', 'max_time', 'min_memory', 'max_memory',
752+
'cpp_min_time', 'cpp_max_time', 'cpp_min_memory', 'cpp_max_memory',
753+
'cpp_min_time_non_overridden', 'cpp_max_time_non_overridden', 'cpp_min_memory_non_overridden', 'cpp_max_memory_non_overridden',
754+
'py_min_time', 'py_max_time', 'py_min_memory', 'py_max_memory',
755+
'py_min_time_non_overridden', 'py_max_time_non_overridden', 'py_min_memory_non_overridden', 'py_max_memory_non_overridden'
756+
)
757+
758+
return process_instances_to_limits(instances)
759+
644760
def adjust_submission_form(self, request, form, problem_instance):
645761
# by default delegate to ProblemController
646762
problem_instance.problem.controller.adjust_submission_form(
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Generated by Django 4.2.20 on 2025-06-08 15:53
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
import oioioi.base.fields
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('contests', '0021_sync_indexes_state'),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='LimitsVisibilityConfig',
17+
fields=[
18+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19+
('visible', oioioi.base.fields.EnumField(default='NO', help_text="Determines whether participants can see problems' time and memory limits", max_length=64, verbose_name='limits visibility')),
20+
('contest', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='contests.contest')),
21+
],
22+
options={
23+
'verbose_name': 'limits visibility config',
24+
'verbose_name_plural': 'limits visibility configs',
25+
},
26+
),
27+
]

oioioi/contests/models.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,27 @@ class Meta(object):
356356
verbose_name_plural = _("ranking visibility configs")
357357

358358

359+
limits_visibility_options = EnumRegistry()
360+
limits_visibility_options.register('YES', _("Visible"))
361+
limits_visibility_options.register('NO', _("Not visible"))
362+
363+
364+
class LimitsVisibilityConfig(models.Model):
365+
contest = models.OneToOneField('contests.Contest', on_delete=models.CASCADE)
366+
visible = EnumField(
367+
limits_visibility_options,
368+
default='NO',
369+
verbose_name=_("limits visibility"),
370+
help_text=_(
371+
"Determines whether participants can see problems' time and memory limits"
372+
),
373+
)
374+
375+
class Meta(object):
376+
verbose_name = _("limits visibility config")
377+
verbose_name_plural = _("limits visibility configs")
378+
379+
359380
registration_availability_options = EnumRegistry()
360381
registration_availability_options.register('YES', _("Open"))
361382
registration_availability_options.register('NO', _("Closed"))

oioioi/contests/static/common/contests.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,18 @@ table.table-striped > tbody > tr.problemlist-subheader {
4545
margin: 3rem;
4646
margin-bottom: 4rem;
4747
}
48+
49+
.problem-name-column {
50+
width: 100%;
51+
}
52+
53+
.problem-limits-row {
54+
background-color: transparent !important;
55+
border-color: transparent !important;
56+
white-space: nowrap;
57+
}
58+
59+
.problem-limits-row td {
60+
border: none;
61+
white-space: nowrap;
62+
}

oioioi/contests/templates/contests/problems_list.html

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ <h1>{% trans "Problems" %}</h1>
5858
<thead>
5959
<tr>
6060
<th></th>
61-
<th>{% trans "Name" %}</th>
61+
<th class="problem-name-column">{% trans "Name" %}</th>
62+
{% if show_problems_limits %}
63+
<th>
64+
{% trans "Limits" %}
65+
</th>
66+
{% endif %}
6267
{% if show_submissions_limit %}
6368
<th class="text-right">
6469
{% if user.is_authenticated %}
@@ -75,8 +80,7 @@ <h1>{% trans "Problems" %}</h1>
7580
</tr>
7681
</thead>
7782
<tbody>
78-
{% for pi, statement_visible, round_time, result, submissions_left, submissions_limit, can_submit in problem_instances %}
79-
83+
{% for pi, statement_visible, round_time, problem_limits, result, submissions_left, submissions_limit, can_submit in problem_instances %}
8084
{% if show_rounds %}
8185
{% ifchanged pi.round %}
8286
<tr class="problemlist-subheader">
@@ -97,6 +101,23 @@ <h1>{% trans "Problems" %}</h1>
97101
{{ pi.problem.name }}
98102
{% endif %}
99103
</td>
104+
{% if show_problems_limits %}
105+
<td class="text-right">
106+
{% if problem_limits %}
107+
<table>
108+
{% for row in problem_limits %}
109+
<tr class="problem-limits-row">
110+
{% for entry in row %}
111+
<td>
112+
{{ entry }}
113+
</td>
114+
{% endfor %}
115+
</tr>
116+
{% endfor %}
117+
</table>
118+
{% endif %}
119+
</td>
120+
{% endif %}
100121
{% if show_submissions_limit %}
101122
<td class="text-right">
102123
{% if submissions_left == None %}

oioioi/contests/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def make_empty_contest_formset():
6969
('problemstatementconfig', 0, 0, 0, 1),
7070
('rankingvisibilityconfig', 0, 0, 0, 1),
7171
('registrationavailabilityconfig', 0, 0, 0, 1),
72+
('limitsvisibilityconfig', 0, 0, 0, 1),
7273
('balloonsdeliveryaccessdata', 1, 0, 0, 1),
7374
('statistics_config', 1, 0, 0, 1),
7475
('exclusivenessconfig_set', 0, 0, 0, 1000),

0 commit comments

Comments
 (0)