Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
31 changes: 31 additions & 0 deletions oioioi/contests/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,34 @@ def export_entries(registry, values):
return result


STATUS_CLASSES = {
"OK100": "badge-success",
"OK": "badge-success",
"INI_OK": "badge-success",
"TESTRUN_OK": "badge-success",
"OK75": "badge-warning",
"OK50": "badge-warning",
"OK25": "badge-danger",
"OK0": "badge-danger",
"ERR": "badge-danger",
"WA": "badge-danger",
"TLE": "badge-danger",
"RE": "badge-danger",
"CE": "badge-danger",
"MLE": "badge-danger",
"OLE": "badge-danger",
"SE": "badge-danger",
"RV": "badge-danger",
"INI_ERR": "badge-danger",
"MSE": "badge-danger",
"MCE": "badge-danger",
}


def get_badge_class(display_type):
return STATUS_CLASSES.get(display_type, "badge-secondary")


def submission_template_context(request, submission):
pi = submission.problem_instance
controller = pi.controller
Expand Down Expand Up @@ -108,12 +136,15 @@ def submission_template_context(request, submission):
else:
display_type = submission.status

badge_class = get_badge_class(display_type)

return {
"submission": submission,
"can_see_status": can_see_status,
"can_see_score": can_see_score,
"can_see_comment": can_see_comment,
"display_type": display_type,
"badge_class": badge_class,
"message": message,
"link": link,
"valid_kinds_for_submission": valid_kinds_for_submission,
Expand Down
9 changes: 8 additions & 1 deletion oioioi/contests/templates/contests/problems_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ <h1>{% trans "Problems" %}</h1>
</tr>
</thead>
<tbody>
{% for pi, statement_visible, round_time, problem_limits, result, submissions_left, submissions_limit, can_submit in problem_instances %}
{% for pi, statement_visible, round_time, problem_limits, result, submissions_left, submissions_limit, can_submit, last_submission in problem_instances %}
{% if show_rounds %}
{% ifchanged pi.round %}
<tr class="problemlist-subheader">
Expand All @@ -100,6 +100,13 @@ <h1>{% trans "Problems" %}</h1>
{% else %}
{{ pi.problem.name }}
{% endif %}
{% if show_status %}
{% if last_submission and last_submission.can_see_status %}
<a href="{{ last_submission.link }}">
<span class="badge {{ last_submission.badge_class }}">{{ last_submission.message }}</span>
</a>
{% endif %}
{% endif %}
</td>
{% if show_problems_limits %}
<td class="text-right">
Expand Down
38 changes: 38 additions & 0 deletions oioioi/contests/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3858,6 +3858,44 @@ def see_link_for_submission_on_problem_list(self, username, should_see):
self.assertNotContains(response, expected_hyperlink)


class TestStatusOnProblemsList(TestCase):
fixtures = [
"test_users",
"test_contest",
"test_full_package",
"test_problem_instance",
"test_submission",
]

def test_status_hidden_for_anonymous(self):
see_status_on_problems_list(self, None, False)

def test_status_visible_for_user(self):
see_status_on_problems_list(self, "test_user", True, expected_status="OK")

def test_status_hidden_for_admin(self):
see_status_on_problems_list(self, "test_admin", False)


def see_status_on_problems_list(self, username, should_see, expected_status=None):
if username is None:
self.client.logout()
else:
self.assertTrue(self.client.login(username=username))

contest = Contest.objects.get(pk="c")
problems_url = reverse("problems_list", kwargs={"contest_id": contest.id})

response = self.client.get(problems_url, follow=True)
# badge presence
if should_see:
if expected_status:
self.assertContains(response, expected_status)
else:
# No status header for anonymous users
self.assertNotContains(response, '<th class="text-right">Status</th>')


class TestSubmissionsLinksOnListView(TestCase):
fixtures = [
"test_users",
Expand Down
49 changes: 37 additions & 12 deletions oioioi/contests/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,28 +154,51 @@ def problems_list_view(request):
# 6) submissions_limit
# 7) can_submit
# Sorted by (start_date, end_date, round name, problem name)
# Preload user-related data to avoid N+1 queries
pi_ids = [pi.id for pi in problem_instances]
results_map = {}
last_submission_map = {}
if request.user.is_authenticated:
# Bulk fetch UserResultForProblem objects. We only keep those for which
# the user can see the submission score.
user_results_qs = (
UserResultForProblem.objects.filter(user__id=request.user.id, problem_instance_id__in=pi_ids)
.select_related("submission_report__submission")
)
for r in user_results_qs:
# Some controllers may hide score even if UserResultForProblem exists
if r and r.submission_report and controller.can_see_submission_score(request, r.submission_report.submission):
results_map[r.problem_instance_id] = r

# Bulk fetch user's submissions for the problem instances and build a map
# of latest submission per problem instance. Submissions are ordered by
# date descending, so the first occurrence for a given problem_instance
# is the latest one.
submissions_qs = (
Submission.objects.filter(
user__id=request.user.id,
problem_instance_id__in=pi_ids,
kind="NORMAL" # ignore ignored submissions
)
.order_by("-date")
)
for s in submissions_qs:
pid = s.problem_instance_id
if pid not in last_submission_map:
last_submission_map[pid] = s
Comment on lines +173 to +188
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it shouldn't always fetch last submission, for example in some contests max submission counts as the score (OI, OIJ).

In this PR #405 there are functions implemented that return scored submission effectively. Maybe you can copy them here.


problems_statements = sorted(
[
(
pi,
controller.can_see_statement(request, pi),
controller.get_round_times(request, pi.round),
problems_limits.get(pi.pk, None),
# Because this view can be accessed by an anynomous user we can't
# use `user=request.user` (it would cause TypeError). Surprisingly
# using request.user.id is ok since for AnynomousUser id is set
# to None.
next(
(
r
for r in UserResultForProblem.objects.filter(user__id=request.user.id, problem_instance=pi)
if r and r.submission_report and controller.can_see_submission_score(request, r.submission_report.submission)
),
None,
),
results_map.get(pi.id),
pi.controller.get_submissions_left(request, pi),
pi.controller.get_submissions_limit(request, pi),
controller.can_submit(request, pi) and not is_contest_archived(request),
submission_template_context(request, last_submission_map[pi.id]) if pi.id in last_submission_map else None,
)
for pi in problem_instances
],
Expand All @@ -185,6 +208,7 @@ def problems_list_view(request):
show_submissions_limit = any(p[6] for p in problems_statements)
show_submit_button = any(p[7] for p in problems_statements)
show_rounds = len(frozenset(pi.round_id for pi in problem_instances)) > 1
show_status = request.user.is_authenticated # Always show status for authenticated users
table_columns = 3 + int(show_problems_limits) + int(show_submissions_limit) + int(show_submit_button)

return TemplateResponse(
Expand All @@ -196,6 +220,7 @@ def problems_list_view(request):
"show_rounds": show_rounds,
"show_scores": request.user.is_authenticated,
"show_submissions_limit": show_submissions_limit,
"show_status": show_status,
"show_submit_button": show_submit_button,
"table_columns": table_columns,
"problems_on_page": getattr(settings, "PROBLEMS_ON_PAGE", 100),
Expand Down