Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions oioioi/acm/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ def _fill_user_result_for_problem(self, result, pi_submissions):
result.status = None
return None

def get_last_scored_submission(self, user, problem_instance, before=None, include_current=False):
"""This function is not implemented for ACM contests because score difference
isn't shown for ACM contests.
"""
raise NotImplementedError

def update_user_result_for_problem(self, result):
submissions = (
Submission.objects.filter(
Expand Down Expand Up @@ -201,6 +207,9 @@ def can_see_round(self, request_or_context, round, no_admin=False):
def get_default_safe_exec_mode(self):
return 'cpu'

def display_score_change(self):
return False


class ACMOpenContestController(ACMContestController):
description = _("ACM style contest (open)")
Expand Down
30 changes: 30 additions & 0 deletions oioioi/contests/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,8 @@ def get_list_display(self, request):
]
if request.contest:
list_display.remove('contest_display')
if request.contest.controller.display_score_change():
list_display.append('score_diff_display')
return list_display

def get_list_display_links(self, request, list_display):
Expand Down Expand Up @@ -848,6 +850,34 @@ def score_display(self, instance):
score_display.short_description = _("Score")
score_display.admin_order_field = 'score_with_nulls_smallest'

def score_diff_display(self, instance):
contest_controller = instance.problem_instance.contest.controller
pi_controller = instance.problem_instance.controller
if not contest_controller.display_score_change() or instance.kind != 'NORMAL':
return format_html('<span class="text-secondary">-</span>')

try:
previous_submission = pi_controller.get_last_scored_submission(
instance.user,
instance.problem_instance,
before=instance.date,
)
except Submission.DoesNotExist:
previous_submission = None
try:
curr_submission = pi_controller.get_last_scored_submission(
instance.user,
instance.problem_instance,
before=instance.date,
include_current=True,
)
except Submission.DoesNotExist:
curr_submission = None
return contest_controller.render_score_change(previous_submission, curr_submission)

score_diff_display.short_description = _("Score change")
score_diff_display.admin_order_field = 'score'

def contest_display(self, instance):
return instance.problem_instance.contest

Expand Down
35 changes: 35 additions & 0 deletions oioioi/contests/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.db.models import Subquery
from django.template.loader import render_to_string
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 as _
from django.utils.translation import gettext_noop
Expand Down Expand Up @@ -707,6 +708,10 @@ def update_submission_score(self, submission):
problem = submission.problem_instance.problem
problem.controller.update_submission_score(submission)

def get_last_scored_submission(self, user, problem_instance, before=None, include_current=False):
problem = problem_instance.problem
return problem.controller.get_last_scored_submission(user, problem_instance, before, include_current)

def update_user_result_for_problem(self, result):
problem = result.problem_instance.problem
problem.controller.update_user_result_for_problem(result)
Expand Down Expand Up @@ -974,6 +979,36 @@ def _is_partial_score(self, test_report):
def show_default_fields(self, problem_instance):
return problem_instance.problem.controller.show_default_fields(problem_instance)

def display_score_change(self):
"""
Whether to display score change for a submission in submissions admin.
"""
return True

def _calculate_score_change(self, before, after):
"""
Calculate score difference between two scores.
"""
if before is None or after is None:
return after
cls = type(before)
return cls(after.value - before.value)

def render_score_change(self, previous_submission, current_submission):
"""
Calculates and renders score change between two submissions.
"""
prev_score = previous_submission.score if previous_submission else None
curr_score = current_submission.score if current_submission else None
diff = self._calculate_score_change(prev_score, curr_score)
if diff is None:
return format_html('<span class="text-secondary">-</span>')
if diff.value == 0:
return format_html('<span class="text-secondary">0</span>')
if diff.value > 0:
return format_html('<span class="text-success">+{}</span>', diff.value)
return format_html('<span class="text-danger">{}</span>', diff.value)


class PastRoundsHiddenContestControllerMixin(object):
"""ContestController mixin that hides past rounds
Expand Down
64 changes: 63 additions & 1 deletion oioioi/contests/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
ProblemStatement,
)
from oioioi.programs.controllers import ProgrammingContestController
from oioioi.programs.models import ModelProgramSubmission, Test
from oioioi.programs.models import ModelProgramSubmission, Test, ProgramSubmission
from oioioi.programs.tests import SubmitFileMixin
from oioioi.simpleui.views import (
contest_dashboard_redirect as simpleui_contest_dashboard,
Expand Down Expand Up @@ -4200,3 +4200,65 @@ def test_score_badge(self):
self.assertIn('badge-warning', self._get_badge_for_problem(response.content, 'zad2'))
self.assertIn('badge-danger', self._get_badge_for_problem(response.content, 'zad3'))


class TestScoreDiffDisplay(TestCase):
fixtures = [
'test_users',
'test_contest',
'test_full_package',
'test_problem_instance',
]

contest_controller = 'oioioi.contests.controllers.ContestController'

def _change_contest_controller(self, controller_name):
contest = Contest.objects.get()
contest.controller_name = controller_name
contest.save()

def _get_score_diff(self, submission):
contest = Contest.objects.get()
url = reverse('oioioiadmin:contests_submission_changelist', kwargs={'contest_id': contest.id})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)

soup = bs4.BeautifulSoup(response.content, 'html.parser')
submissions_table = soup.find('table', {'id': 'result_list'})
tbody = submissions_table.find('tbody')
for tr in tbody.find_all('tr'):
s_id = tr.find('th', {'class': 'field-id'}).text
if s_id == str(submission.id):
return tr.find('td', {'class': 'field-score_diff_display'}).text

def _create_submission(self, score, expected_diff):
problem_instance = ProblemInstance.objects.get(pk=1)
user = User.objects.get(username='test_admin')
ps = ProgramSubmission.objects.create(
source_file=ContentFile(b'int main() {}', name='main.cpp'),
problem_instance=problem_instance,
user=user,
score=score,
status='OK',
)
ps.save()
self.assertEqual(expected_diff, self._get_score_diff(ps))
return ps

def setUp(self):
self.client.force_login(User.objects.get(username='test_admin'))
self._change_contest_controller(self.contest_controller)
Submission.objects.all().delete()


class TestScoreDiffSimpleContest(TestScoreDiffDisplay):
contest_controller = 'oioioi.programs.controllers.ProgrammingContestController'

def test(self):
self._create_submission(IntegerScore(25), '+25')
s1 = self._create_submission(IntegerScore(50), '+25')
s2 = self._create_submission(IntegerScore(100), '+50')
self._create_submission(IntegerScore(0), '-100')
s1.kind = 'IGNORED'
s1.save()
self.assertEqual(self._get_score_diff(s2), '+75')
self.assertEqual(self._get_score_diff(s1), '-')
55 changes: 38 additions & 17 deletions oioioi/mp/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,23 +122,24 @@ def _get_score_for_submission(self, submission, ssm):
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
are scored with given multiplier.
"""
def get_last_scored_submission(self, user, problem_instance, before=None, include_current=False):
submissions = Submission.objects.filter(
problem_instance=result.problem_instance,
user=result.user,
problem_instance=problem_instance,
user=user,
kind='NORMAL',
score__isnull=False,
)
if before:
if include_current:
submissions = submissions.filter(date__lte=before)
else:
submissions = submissions.filter(date__lt=before)

best_submission = None
best_submission_score = None
try:
ssm = SubmissionScoreMultiplier.objects.get(
contest=result.problem_instance.contest
contest=problem_instance.contest
)
except SubmissionScoreMultiplier.DoesNotExist:
ssm = None
Expand All @@ -148,17 +149,37 @@ def update_user_result_for_problem(self, result):
if not best_submission or (score and best_submission_score < score):
best_submission = submission
best_submission_score = score
return best_submission

try:
report = SubmissionReport.objects.get(
submission=best_submission, status='ACTIVE', kind='NORMAL'
)
except SubmissionReport.DoesNotExist:
report = 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
are scored with given multiplier.
"""
best_submission = self.get_last_scored_submission(result.user, result.problem_instance)
if best_submission:
try:
ssm = SubmissionScoreMultiplier.objects.get(
contest=result.problem_instance.contest
)
except SubmissionScoreMultiplier.DoesNotExist:
ssm = None
best_submission_score = self._get_score_for_submission(best_submission, ssm)

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

result.score = best_submission_score
result.status = best_submission.status if best_submission else None
result.submission_report = report
result.score = best_submission_score
result.status = best_submission.status if best_submission else None
result.submission_report = report
else:
result.score = None
result.status = None
result.submission_report = None

def can_submit(self, request, problem_instance, check_round_times=True):
"""Contest admin can always submit.
Expand Down
15 changes: 15 additions & 0 deletions oioioi/mp/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from oioioi.base.tests import TestCase, fake_time
from oioioi.contests.models import Contest, UserResultForProblem
from oioioi.contests.tests.tests import TestScoreDiffDisplay
from oioioi.mp.score import FloatScore


Expand Down Expand Up @@ -96,3 +97,17 @@ def test_results_scores(self):
for urfp in UserResultForProblem.objects.all():
res = self._create_result(urfp.user, urfp.problem_instance)
self.assertEqual(res.score, urfp.score)


class TestScoreDiffMPContest(TestScoreDiffDisplay):
contest_controller = 'oioioi.mp.controllers.MPContestController'

def test(self):
self._create_submission(FloatScore(25.5), '+25.5')
s1 = self._create_submission(FloatScore(50), '+24.5')
s2 = self._create_submission(FloatScore(100), '+50.0')
self._create_submission(FloatScore(0), '0')
s1.kind = 'IGNORED'
s1.save()
self.assertEqual(self._get_score_diff(s1), '-')
self.assertEqual(self._get_score_diff(s2), '+74.5')
Loading
Loading