Skip to content
Merged
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>')


Comment thread
m4teuk marked this conversation as resolved.
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]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we need this array? i believe it creates a sql query

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

Copy link
Copy Markdown
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As for me this looks safe, the column is clearly marked as "the last submission" and the link is pointing to a a submission that have been used

Comment thread
m4teuk marked this conversation as resolved.
Outdated
Comment thread
m4teuk marked this conversation as resolved.
Outdated

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