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

Display score change in submissions admin #405

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
MasloMaslane marked this conversation as resolved.
Show resolved Hide resolved
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), '-')
62 changes: 42 additions & 20 deletions oioioi/mp/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,43 +122,65 @@ 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, ssm=None):
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
)
except SubmissionScoreMultiplier.DoesNotExist:
ssm = None
if ssm is None:
try:
ssm = SubmissionScoreMultiplier.objects.get(
contest=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
return best_submission
MasloMaslane marked this conversation as resolved.
Show resolved Hide resolved

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

result.score = best_submission_score
result.status = best_submission.status if best_submission else None
result.submission_report = report
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
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