Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes in MPContestController #153

Merged
merged 17 commits into from
Dec 12, 2023
Merged
3 changes: 3 additions & 0 deletions oioioi/base/templatetags/simple_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.utils.safestring import mark_safe

from oioioi.contests.scores import IntegerScore
from oioioi.mp.score import FloatScore
from oioioi.pa.score import PAScore

register = template.Library()
Expand Down Expand Up @@ -274,6 +275,8 @@ def result_color_class(raw_score):
score_max_value = 100
elif isinstance(raw_score, PAScore):
score_max_value = 10
elif isinstance(raw_score, FloatScore):
score_max_value = 100
else:
# There should be a method to get maximum points for
# contest, for now, support just above cases.
Expand Down
2 changes: 1 addition & 1 deletion oioioi/mp/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def get_inlines(self, request, obj):
if hasattr(obj, 'controller') and isinstance(
obj.controller, MPContestController
):
return inlines + [SubmissionScoreMultiplierInline]
return inlines + (SubmissionScoreMultiplierInline,)
return inlines


Expand Down
60 changes: 38 additions & 22 deletions oioioi/mp/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from oioioi.base.utils.query_helpers import Q_always_true
from oioioi.base.utils.redirect import safe_redirect
from oioioi.contests.models import Submission
from oioioi.contests.models import Submission, SubmissionReport
from oioioi.mp.models import MPRegistration, SubmissionScoreMultiplier
from oioioi.mp.score import FloatScore
from oioioi.participants.controllers import ParticipantsController
Expand Down Expand Up @@ -107,6 +107,17 @@ def registration_controller(self):
def ranking_controller(self):
return MPRankingController(self.contest)

def _get_score_for_submission(self, submission, ssm):
score = FloatScore(submission.score.value)
rtimes = self.get_round_times(None, submission.problem_instance.round)
# Round was active when the submission was sent
if rtimes.is_active(submission.date):
return score
# Round was over when the submission was sent but multiplier was ahead
if ssm and ssm.end_date >= submission.date:
return score * ssm.multiplier
return None

def update_user_result_for_problem(self, result):
"""Submissions sent during the round are scored as normal.
Submissions sent while the round was over but SubmissionScoreMultiplier was active
Expand All @@ -119,28 +130,31 @@ def update_user_result_for_problem(self, result):
score__isnull=False,
)

if submissions:
best_submission = None
for submission in submissions:
ssm = SubmissionScoreMultiplier.objects.filter(
contest=submission.problem_instance.contest,
)

score = FloatScore(submission.score.value)
rtimes = self.get_round_times(None, submission.problem_instance.round)
if rtimes.is_active(submission.date):
pass
elif ssm.exists() and ssm[0].end_date >= submission.date:
score = score * ssm[0].multiplier
else:
score = None
if not best_submission or (
score is not None and best_submission[1] < score
):
best_submission = [submission, score]
best_submission = None
best_submission_score = None
try:
ssm = SubmissionScoreMultiplier.objects.get(
contest=result.problem_instance.contest
)
except SubmissionScoreMultiplier.DoesNotExist:
ssm = None

for submission in submissions:
score = self._get_score_for_submission(submission, ssm)
if not best_submission or (score and best_submission_score < score):
best_submission = submission
best_submission_score = score

try:
report = SubmissionReport.objects.get(
submission=best_submission, status='ACTIVE', kind='NORMAL'
)
except SubmissionReport.DoesNotExist:
report = None

result.score = best_submission[1]
result.status = best_submission[0].status
result.score = best_submission_score
result.status = best_submission.status if best_submission else None
result.submission_report = report

def can_submit(self, request, problem_instance, check_round_times=True):
"""Contest admin can always submit.
Expand All @@ -155,6 +169,8 @@ def can_submit(self, request, problem_instance, check_round_times=True):
return True
if not is_participant(request):
return False
if problem_instance.round is None:
return False

rtimes = self.get_round_times(None, problem_instance.round)
round_over_contest_running = rtimes.is_past(
Expand Down
22 changes: 22 additions & 0 deletions oioioi/mp/fixtures/test_mp_contest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
"enable_editor": false
}
},
{
"model": "participants.participant",
"pk": 1,
"fields": {
"contest": "contest1",
"user": 2,
"status": "ACTIVE",
"anonymous": false
}
},
{
"model": "problems.problem",
"pk": 1,
Expand Down Expand Up @@ -105,6 +115,18 @@
"needs_rejudge": false
}
},
{
"model": "contests.probleminstance",
"pk": 5,
"fields": {
"contest": "contest1",
"round": null,
"problem": 1,
"short_name": "squ2",
"submissions_limit": 10,
"needs_rejudge": false
}
},
{
"model": "contests.submission",
"pk": 1,
Expand Down
6 changes: 3 additions & 3 deletions oioioi/mp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from oioioi.participants.models import RegistrationModel
from oioioi.contests.models import Contest

check_django_app_dependencies(__name__, ['oioioi.participants'])
check_django_app_dependencies(__name__, ['oioioi.contests'])


Expand All @@ -20,12 +19,13 @@ def erase_data(self):


class SubmissionScoreMultiplier(models.Model):
""" If SubmissionScoreMultiplier exists, users can submit problems
"""If SubmissionScoreMultiplier exists, users can submit problems
even after round ends, until end_date

Result score for submission after round's end is multiplied by
given multiplier value
"""

contest = models.OneToOneField(
Contest, verbose_name=_("contest"), on_delete=models.CASCADE
)
Expand Down
36 changes: 28 additions & 8 deletions oioioi/mp/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime, timezone
import re
from datetime import datetime, timezone

from django.urls import reverse

Expand All @@ -23,7 +23,7 @@ class TestMPRanking(TestCase):
def _ranking_url(self, key='c'):
contest = Contest.objects.get(name='contest1')
return reverse('ranking', kwargs={'contest_id': contest.id, 'key': key})

def _check_order(self, response, expected):
prev_pos = 0
for round_name in expected:
Expand All @@ -48,12 +48,15 @@ def test_columns_order(self):
self.assertTrue(self.client.login(username='test_user1'))
with fake_time(datetime(2023, 1, 6, 0, 0, tzinfo=timezone.utc)):
response = self.client.get(self._ranking_url())
self._check_order(response, [
b'<th>User</th>',
b'<th[^>]*>Sum</th>',
b'<th[^>]*>\s*(<a[^>]*>)*\s*squ1\s*(</a>)*\s*</th>',
b'<th[^>]*>\s*(<a[^>]*>)*\s*squ\s*(</a>)*\s*</th>'
])
self._check_order(
response,
[
b'<th>User</th>',
b'<th[^>]*>Sum</th>',
b'<th[^>]*>\s*(<a[^>]*>)*\s*squ1\s*(</a>)*\s*</th>',
b'<th[^>]*>\s*(<a[^>]*>)*\s*squ\s*(</a>)*\s*</th>',
],
)

def test_no_zero_scores_in_ranking(self):
self.assertTrue(self.client.login(username='test_user1'))
Expand All @@ -65,6 +68,23 @@ def test_no_zero_scores_in_ranking(self):
self.assertFalse(re.search(b'<td[^>]*>Test User4', response.content))


class TestNoRoundProblem(TestCase):
fixtures = ['test_mp_users', 'test_mp_contest']

def test_no_round_problem(self):
self.assertTrue(self.client.login(username='test_user1'))
contest = Contest.objects.get()
url = reverse('submit', kwargs={'contest_id': contest.id})
with fake_time(datetime(2023, 1, 5, 12, 10, tzinfo=timezone.utc)):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn('form', response.context)
form = response.context['form']
# there are 3 problems, one of them doesn't have round
# +1 because of blank field
self.assertEqual(len(form.fields['problem_instance_id'].choices), 3)


class TestSubmissionScoreMultiplier(TestCase):
def _create_result(user, pi):
res = UserResultForProblem()
Expand Down
Loading