NDOJ/judge/views/submission.py

1014 lines
37 KiB
Python
Raw Normal View History

2020-01-21 06:35:58 +00:00
import json
2020-01-22 01:18:43 +00:00
import os.path
2020-01-21 06:35:58 +00:00
from operator import attrgetter
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.cache import cache
2020-01-22 01:18:43 +00:00
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import PermissionDenied
from django.db.models import Prefetch
from django.db.models import Q
from django.http import Http404
from django.http import HttpResponse
from django.http import HttpResponseBadRequest
from django.http import HttpResponseRedirect
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import render
2022-06-01 05:28:56 +00:00
from django.template.defaultfilters import floatformat
2020-01-21 06:35:58 +00:00
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property
2020-01-22 01:18:43 +00:00
from django.utils.html import escape
from django.utils.html import format_html
2020-01-21 06:35:58 +00:00
from django.utils.safestring import mark_safe
2020-01-22 01:18:43 +00:00
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy
2020-01-21 06:35:58 +00:00
from django.views.decorators.http import require_POST
2020-01-22 01:18:43 +00:00
from django.views.generic import DetailView
from django.views.generic import ListView
2023-03-10 04:31:55 +00:00
from django.views import View
2020-01-21 06:35:58 +00:00
from judge import event_poster as event
from judge.highlight_code import highlight_code
2022-11-22 04:05:35 +00:00
from judge.models import Contest, ContestParticipation
2020-01-22 01:18:43 +00:00
from judge.models import Language
from judge.models import Problem
from judge.models import ProblemTestCase
from judge.models import ProblemTranslation
from judge.models import Profile
from judge.models import Submission
from judge.utils.problems import get_result_data
2021-06-01 20:34:42 +00:00
from judge.utils.problem_data import get_problem_case
2021-05-24 20:00:36 +00:00
from judge.utils.raw_sql import join_sql_subquery, use_straight_join
2020-01-22 01:18:43 +00:00
from judge.utils.views import DiggPaginatorMixin
2023-02-18 21:12:33 +00:00
from judge.utils.infinite_paginator import InfinitePaginationMixin
2020-01-22 01:18:43 +00:00
from judge.utils.views import TitleMixin
2022-06-01 05:28:56 +00:00
from judge.utils.timedelta import nice_repr
2020-01-21 17:42:16 +00:00
2020-01-21 06:35:58 +00:00
def submission_related(queryset):
2024-04-13 22:02:54 +00:00
return queryset.select_related("user", "problem", "language").only(
2022-05-14 17:57:27 +00:00
"id",
2024-04-13 22:02:54 +00:00
"user_id",
2022-05-14 17:57:27 +00:00
"problem__name",
"problem__code",
"problem__is_public",
"language__short_name",
"language__key",
"date",
"time",
"memory",
"points",
"result",
"status",
"case_points",
"case_total",
"current_testcase",
"contest_object",
)
2020-01-21 06:35:58 +00:00
class SubmissionMixin(object):
model = Submission
2022-05-14 17:57:27 +00:00
context_object_name = "submission"
pk_url_kwarg = "submission"
2020-01-21 06:35:58 +00:00
class SubmissionDetailBase(LoginRequiredMixin, TitleMixin, SubmissionMixin, DetailView):
def get_object(self, queryset=None):
submission = super(SubmissionDetailBase, self).get_object(queryset)
2023-10-12 01:33:48 +00:00
if submission.is_accessible_by(self.request.profile):
2022-04-29 19:36:26 +00:00
return submission
2022-05-14 17:57:27 +00:00
2020-01-21 06:35:58 +00:00
raise PermissionDenied()
def get_title(self):
submission = self.object
2022-05-14 17:57:27 +00:00
return _("Submission of %(problem)s by %(user)s") % {
"problem": submission.problem.translated_name(self.request.LANGUAGE_CODE),
"user": submission.user.user.username,
2020-01-21 06:35:58 +00:00
}
def get_content_title(self):
submission = self.object
2022-05-14 17:57:27 +00:00
return mark_safe(
escape(_("Submission of %(problem)s by %(user)s"))
% {
"problem": format_html(
'<a href="{0}">{1}</a>',
reverse("problem_detail", args=[submission.problem.code]),
submission.problem.translated_name(self.request.LANGUAGE_CODE),
),
"user": format_html(
'<a href="{0}">{1}</a>',
reverse("user_page", args=[submission.user.user.username]),
submission.user.user.username,
),
}
)
2020-01-21 06:35:58 +00:00
def get_hidden_subtasks(request, submission):
contest = submission.contest_object
2023-01-04 21:57:10 +00:00
if contest and contest.is_editable_by(request.user):
2023-01-04 21:11:00 +00:00
return set()
if contest and contest.format.has_hidden_subtasks:
try:
return contest.format.get_hidden_subtasks().get(
str(submission.contest.problem.id), set()
)
except Exception:
pass
return set()
def make_batch(batch, cases, include_cases=True):
result = {"id": batch}
if include_cases:
result["cases"] = cases
2020-01-21 06:35:58 +00:00
if batch:
2022-11-20 03:41:43 +00:00
result["points"] = sum(map(attrgetter("points"), cases))
result["total"] = sum(map(attrgetter("total"), cases))
result["AC"] = abs(result["points"] - result["total"]) < 1e-5
2020-01-21 06:35:58 +00:00
return result
def group_test_cases(submission, hidden_subtasks, include_cases=True):
cases = submission.test_cases.exclude(batch__in=hidden_subtasks)
2020-01-21 06:35:58 +00:00
result = []
buf = []
last = None
for case in cases:
if case.batch != last and buf:
result.append(make_batch(last, buf, include_cases))
2020-01-21 06:35:58 +00:00
buf = []
buf.append(case)
last = case.batch
if buf:
result.append(make_batch(last, buf, include_cases))
2020-01-21 06:35:58 +00:00
return result
2021-05-25 04:22:56 +00:00
def get_cases_data(submission):
2022-05-14 17:57:27 +00:00
testcases = ProblemTestCase.objects.filter(dataset=submission.problem).order_by(
"order"
)
if submission.is_pretested:
2020-05-29 20:46:17 +00:00
testcases = testcases.filter(is_pretest=True)
2021-05-25 04:22:56 +00:00
files = []
for case in testcases:
2022-05-14 17:57:27 +00:00
if case.input_file:
files.append(case.input_file)
if case.output_file:
files.append(case.output_file)
2021-05-25 04:22:56 +00:00
case_data = get_problem_case(submission.problem, files)
2020-05-29 20:46:17 +00:00
problem_data = {}
2021-05-26 17:10:19 +00:00
count = 0
for case in testcases:
2022-05-14 17:57:27 +00:00
if case.type != "C":
continue
2021-05-26 17:10:19 +00:00
count += 1
problem_data[count] = {
2023-10-27 23:02:02 +00:00
"input": case_data.get(case.input_file, "") if case.input_file else "",
"answer": case_data.get(case.output_file, "") if case.output_file else "",
}
2020-01-21 17:42:16 +00:00
return problem_data
2020-01-22 01:18:43 +00:00
2020-01-21 06:35:58 +00:00
class SubmissionStatus(SubmissionDetailBase):
2022-05-14 17:57:27 +00:00
template_name = "submission/status.html"
2020-01-21 06:35:58 +00:00
def access_testcases_in_contest(self):
contest = self.object.contest_or_none
if contest is None:
return False
if contest.problem.problem.is_editable_by(self.request.user):
return True
if contest.problem.contest.is_in_contest(self.request.user):
return False
if contest.participation.ended:
return True
return False
2020-01-21 06:35:58 +00:00
def get_context_data(self, **kwargs):
context = super(SubmissionStatus, self).get_context_data(**kwargs)
submission = self.object
2022-12-29 04:50:26 +00:00
context["hidden_subtasks"] = get_hidden_subtasks(self.request, self.object)
2022-05-14 17:57:27 +00:00
context["last_msg"] = event.last()
context["batches"] = group_test_cases(
submission, context["hidden_subtasks"], True
)
2022-05-14 17:57:27 +00:00
context["time_limit"] = submission.problem.time_limit
context["can_see_testcases"] = False
2022-08-19 03:47:41 +00:00
context["highlighted_source"] = highlight_code(
2024-01-19 01:46:41 +00:00
submission.source.source,
submission.language.pygments,
linenos=True,
title=submission.language,
2022-08-19 03:47:41 +00:00
)
2022-05-14 17:57:27 +00:00
2020-08-05 13:31:43 +00:00
contest = submission.contest_or_none
show_testcases = False
can_see_testcases = self.access_testcases_in_contest()
2022-05-14 17:57:27 +00:00
if contest is not None:
show_testcases = contest.problem.show_testcases or False
2022-05-14 17:57:27 +00:00
if contest is None or show_testcases or can_see_testcases:
2022-05-14 17:57:27 +00:00
context["cases_data"] = get_cases_data(submission)
context["can_see_testcases"] = True
2020-01-21 06:35:58 +00:00
try:
2020-01-21 17:42:16 +00:00
lang_limit = submission.problem.language_limits.get(
2022-05-14 17:57:27 +00:00
language=submission.language
)
2020-01-21 06:35:58 +00:00
except ObjectDoesNotExist:
pass
else:
2022-05-14 17:57:27 +00:00
context["time_limit"] = lang_limit.time_limit
2020-01-21 06:35:58 +00:00
return context
class SubmissionTestCaseQuery(SubmissionStatus):
2022-05-14 17:57:27 +00:00
template_name = "submission/status-testcases.html"
2020-01-21 06:35:58 +00:00
def get(self, request, *args, **kwargs):
2022-05-14 17:57:27 +00:00
if "id" not in request.GET or not request.GET["id"].isdigit():
2020-01-21 06:35:58 +00:00
return HttpResponseBadRequest()
2020-01-21 17:42:16 +00:00
self.kwargs[self.pk_url_kwarg] = kwargs[self.pk_url_kwarg] = int(
2022-05-14 17:57:27 +00:00
request.GET["id"]
)
2020-01-21 06:35:58 +00:00
return super(SubmissionTestCaseQuery, self).get(request, *args, **kwargs)
2024-01-19 01:46:41 +00:00
class SubmissionSourceRaw(SubmissionDetailBase):
2020-01-21 06:35:58 +00:00
def get(self, request, *args, **kwargs):
submission = self.get_object()
2022-05-14 17:57:27 +00:00
return HttpResponse(submission.source.source, content_type="text/plain")
2020-01-21 06:35:58 +00:00
@require_POST
def abort_submission(request, submission):
submission = get_object_or_404(Submission, id=int(submission))
2020-12-01 23:18:17 +00:00
# if (not request.user.is_authenticated or (submission.was_rejudged or (request.profile != submission.user)) and
# not request.user.has_perm('abort_any_submission')):
# raise PermissionDenied()
2022-05-14 17:57:27 +00:00
if not request.user.is_authenticated or not request.user.has_perm(
"abort_any_submission"
):
2020-01-21 06:35:58 +00:00
raise PermissionDenied()
submission.abort()
2022-05-14 17:57:27 +00:00
return HttpResponseRedirect(reverse("submission_status", args=(submission.id,)))
2020-01-21 06:35:58 +00:00
class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
model = Submission
paginate_by = 50
show_problem = True
2022-05-14 17:57:27 +00:00
title = gettext_lazy("All submissions")
content_title = gettext_lazy("All submissions")
2022-06-06 16:36:35 +00:00
page_type = "all_submissions_list"
2022-05-14 17:57:27 +00:00
template_name = "submission/list.html"
context_object_name = "submissions"
2020-01-21 06:35:58 +00:00
first_page_href = None
2022-11-18 22:59:58 +00:00
include_frozen = False
2023-07-06 15:37:43 +00:00
organization = None
2020-01-21 06:35:58 +00:00
def get_result_data(self):
result = self._get_result_data()
2022-05-14 17:57:27 +00:00
for category in result["categories"]:
category["name"] = _(category["name"])
2020-01-21 06:35:58 +00:00
return result
def _get_result_data(self):
2023-02-18 21:12:33 +00:00
return get_result_data(self.get_queryset().order_by())
2020-01-21 06:35:58 +00:00
def access_check(self, request):
pass
@cached_property
def in_contest(self):
2022-05-14 17:57:27 +00:00
return (
self.request.user.is_authenticated
and self.request.profile.current_contest is not None
2022-01-10 11:13:46 +00:00
and self.request.in_contest_mode
2022-05-14 17:57:27 +00:00
)
2020-01-21 06:35:58 +00:00
@cached_property
def contest(self):
return self.request.profile.current_contest.contest
2022-10-29 03:34:12 +00:00
def _get_entire_queryset(self):
2023-07-06 15:37:43 +00:00
organization = self.organization or self.request.organization
if organization:
2023-07-06 15:39:16 +00:00
queryset = Submission.objects.filter(
contest_object__organizations=organization
)
2023-07-06 15:37:43 +00:00
else:
queryset = Submission.objects.all()
2020-01-21 06:35:58 +00:00
use_straight_join(queryset)
2022-05-14 17:57:27 +00:00
queryset = submission_related(queryset.order_by("-id"))
2020-01-21 06:35:58 +00:00
if self.show_problem:
2022-05-14 17:57:27 +00:00
queryset = queryset.prefetch_related(
Prefetch(
"problem__translations",
queryset=ProblemTranslation.objects.filter(
language=self.request.LANGUAGE_CODE
),
to_attr="_trans",
)
)
2020-01-21 06:35:58 +00:00
if self.in_contest:
2021-05-24 20:00:36 +00:00
queryset = queryset.filter(contest_object=self.contest)
if not self.contest.can_see_full_scoreboard(self.request.user):
queryset = queryset.filter(user=self.request.profile)
if (
2023-01-04 21:11:00 +00:00
self.contest.format.has_hidden_subtasks
and not self.contest.is_editable_by(self.request.user)
):
2022-12-28 18:29:02 +00:00
queryset = queryset.filter(user=self.request.profile)
2022-11-18 22:59:58 +00:00
if self.contest.freeze_after and not self.include_frozen:
queryset = queryset.exclude(
2022-12-28 18:29:02 +00:00
~Q(user=self.request.profile),
date__gte=self.contest.freeze_after + self.contest.start_time,
2022-11-18 22:59:58 +00:00
)
2020-01-21 06:35:58 +00:00
else:
2022-05-14 17:57:27 +00:00
queryset = queryset.select_related("contest_object").defer(
"contest_object__description"
)
2020-01-21 06:35:58 +00:00
# This is not technically correct since contest organizers *should* see these, but
# the join would be far too messy
2022-05-14 17:57:27 +00:00
if not self.request.user.has_perm("judge.see_private_contest"):
2021-05-24 20:00:36 +00:00
# Show submissions for any contest you can edit or visible scoreboard
2022-05-14 17:57:27 +00:00
contest_queryset = Contest.objects.filter(
Q(authors=self.request.profile)
| Q(curators=self.request.profile)
| Q(scoreboard_visibility=Contest.SCOREBOARD_VISIBLE)
| Q(end_time__lt=timezone.now())
).distinct()
queryset = queryset.filter(
Q(user=self.request.profile)
| Q(contest_object__in=contest_queryset)
| Q(contest_object__isnull=True)
)
2020-01-21 06:35:58 +00:00
if self.selected_languages:
2023-07-26 16:52:34 +00:00
# Note (DMOJ): MariaDB can't optimize this subquery for some insane, unknown reason,
# so we are forcing an eager evaluation to get the IDs right here.
# Otherwise, with multiple language filters, MariaDB refuses to use an index
# (or runs the subquery for every submission, which is even more horrifying to think about).
2020-01-21 17:42:16 +00:00
queryset = queryset.filter(
2023-07-26 16:52:34 +00:00
language__in=list(
Language.objects.filter(
key__in=self.selected_languages
).values_list("id", flat=True)
)
2022-05-14 17:57:27 +00:00
)
2020-01-21 06:35:58 +00:00
if self.selected_statuses:
2023-02-15 22:36:33 +00:00
submission_results = [i for i, _ in Submission.RESULT]
if self.selected_statuses[0] in submission_results:
queryset = queryset.filter(result__in=self.selected_statuses)
else:
queryset = queryset.filter(status__in=self.selected_statuses)
2020-01-21 06:35:58 +00:00
return queryset
2023-02-18 21:12:33 +00:00
def get_queryset(self):
2022-10-29 03:34:12 +00:00
queryset = self._get_entire_queryset()
2020-01-21 06:35:58 +00:00
if not self.in_contest:
2021-05-24 20:00:36 +00:00
join_sql_subquery(
queryset,
2022-05-14 17:57:27 +00:00
subquery=str(
Problem.get_visible_problems(self.request.user)
.distinct()
.only("id")
.query
),
2021-05-24 20:00:36 +00:00
params=[],
2022-05-14 17:57:27 +00:00
join_fields=[("problem_id", "id")],
alias="visible_problems",
2022-11-01 01:43:06 +00:00
related_model=Problem,
2021-05-24 20:00:36 +00:00
)
2020-01-21 06:35:58 +00:00
return queryset
def get_my_submissions_page(self):
return None
2023-02-13 03:35:48 +00:00
def get_friend_submissions_page(self):
return None
2020-01-21 06:35:58 +00:00
def get_all_submissions_page(self):
2022-05-14 17:57:27 +00:00
return reverse("all_submissions")
2020-01-21 06:35:58 +00:00
def get_searchable_status_codes(self):
2022-05-30 23:23:31 +00:00
all_statuses = list(Submission.RESULT)
all_statuses.extend([i for i in Submission.STATUS if i not in all_statuses])
hidden_codes = ["SC", "D", "G"]
2020-01-21 06:35:58 +00:00
if not self.request.user.is_superuser and not self.request.user.is_staff:
2022-05-14 17:57:27 +00:00
hidden_codes += ["IE"]
2022-06-01 05:28:56 +00:00
return [(key, value) for key, value in all_statuses if key not in hidden_codes]
2020-01-21 06:35:58 +00:00
def in_hidden_subtasks_contest(self):
return (
2023-01-02 23:22:45 +00:00
self.in_contest
and self.contest.format.has_hidden_subtasks
2023-01-04 21:11:00 +00:00
and not self.contest.is_editable_by(self.request.user)
2023-01-02 23:22:45 +00:00
)
def modify_attrs(self, submission):
# Used to modify submission's info in contest with hidden subtasks
batches = group_test_cases(
submission, get_hidden_subtasks(self.request, submission), False
)
setattr(submission, "case_points", sum([i.get("points", 0) for i in batches]))
setattr(submission, "batches", batches)
if submission.status in ("IE", "CE", "AB"):
setattr(submission, "_result_class", submission.result_class)
else:
setattr(submission, "_result_class", "TLE")
2020-01-21 06:35:58 +00:00
def get_context_data(self, **kwargs):
context = super(SubmissionsListBase, self).get_context_data(**kwargs)
authenticated = self.request.user.is_authenticated
2022-05-14 17:57:27 +00:00
context["dynamic_update"] = False
context["show_problem"] = self.show_problem
2023-10-12 01:33:48 +00:00
context["profile"] = self.request.profile
2022-05-14 17:57:27 +00:00
context["all_languages"] = Language.objects.all().values_list("key", "name")
context["selected_languages"] = self.selected_languages
context["all_statuses"] = self.get_searchable_status_codes()
context["selected_statuses"] = self.selected_statuses
if not self.in_hidden_subtasks_contest():
2023-01-02 23:22:45 +00:00
context["results_json"] = mark_safe(json.dumps(self.get_result_data()))
context["results_colors_json"] = mark_safe(
json.dumps(settings.DMOJ_STATS_SUBMISSION_RESULT_COLORS)
)
else:
context["results_json"] = None
2022-05-14 17:57:27 +00:00
context["page_suffix"] = suffix = (
("?" + self.request.GET.urlencode()) if self.request.GET else ""
)
context["first_page_href"] = (self.first_page_href or ".") + suffix
context["my_submissions_link"] = self.get_my_submissions_page()
2023-02-13 03:35:48 +00:00
context["friend_submissions_link"] = self.get_friend_submissions_page()
2022-05-14 17:57:27 +00:00
context["all_submissions_link"] = self.get_all_submissions_page()
2022-06-06 16:36:35 +00:00
context["page_type"] = self.page_type
2022-04-14 20:40:48 +00:00
context["in_hidden_subtasks_contest"] = self.in_hidden_subtasks_contest()
if context["in_hidden_subtasks_contest"]:
for submission in context["submissions"]:
self.modify_attrs(submission)
2024-02-23 23:07:34 +00:00
2020-01-21 06:35:58 +00:00
return context
def get(self, request, *args, **kwargs):
check = self.access_check(request)
if check is not None:
return check
2023-02-15 22:36:33 +00:00
self.selected_languages = request.GET.getlist("language")
self.selected_statuses = request.GET.getlist("status")
2020-01-21 06:35:58 +00:00
2023-01-04 21:21:03 +00:00
if self.in_contest and self.contest.is_editable_by(self.request.user):
2022-11-18 22:59:58 +00:00
self.include_frozen = True
2022-05-14 17:57:27 +00:00
if "results" in request.GET:
2020-01-21 06:35:58 +00:00
return JsonResponse(self.get_result_data())
return super(SubmissionsListBase, self).get(request, *args, **kwargs)
class UserMixin(object):
def get(self, request, *args, **kwargs):
2022-11-22 04:05:35 +00:00
if "user" not in kwargs and "participation" not in kwargs:
raise ImproperlyConfigured("Must pass a user or participation")
if "user" in kwargs:
self.profile = get_object_or_404(Profile, user__username=kwargs["user"])
self.username = kwargs["user"]
else:
self.participation = get_object_or_404(
ContestParticipation, id=kwargs["participation"]
)
self.profile = self.participation.user
self.username = self.profile.user.username
2022-11-18 22:59:58 +00:00
if self.profile == request.profile:
self.include_frozen = True
2020-01-21 06:35:58 +00:00
return super(UserMixin, self).get(request, *args, **kwargs)
class ConditionalUserTabMixin(object):
def get_context_data(self, **kwargs):
2022-05-14 17:57:27 +00:00
context = super(ConditionalUserTabMixin, self).get_context_data(**kwargs)
2020-01-21 06:35:58 +00:00
if self.request.user.is_authenticated and self.request.profile == self.profile:
2022-06-06 16:36:35 +00:00
context["page_type"] = "my_submissions_tab"
2020-01-21 06:35:58 +00:00
else:
2022-06-06 16:36:35 +00:00
context["page_type"] = "user_submissions_tab"
2022-05-14 17:57:27 +00:00
context["tab_username"] = self.profile.user.username
2020-01-21 06:35:58 +00:00
return context
2023-02-13 03:35:48 +00:00
class GeneralSubmissions(SubmissionsListBase):
def get_my_submissions_page(self):
if self.request.user.is_authenticated:
return reverse(
"all_user_submissions", kwargs={"user": self.request.user.username}
)
return None
def get_friend_submissions_page(self):
if self.request.user.is_authenticated:
return reverse("all_friend_submissions")
return None
class AllUserSubmissions(ConditionalUserTabMixin, UserMixin, GeneralSubmissions):
2023-02-18 21:12:33 +00:00
def get_queryset(self):
2022-05-14 17:57:27 +00:00
return (
super(AllUserSubmissions, self)
2023-02-18 21:12:33 +00:00
.get_queryset()
2022-05-14 17:57:27 +00:00
.filter(user_id=self.profile.id)
)
2020-01-21 06:35:58 +00:00
def get_title(self):
if self.request.user.is_authenticated and self.request.profile == self.profile:
2022-05-14 17:57:27 +00:00
return _("All my submissions")
return _("All submissions by %s") % self.username
2020-01-21 06:35:58 +00:00
def get_content_title(self):
if self.request.user.is_authenticated and self.request.profile == self.profile:
2023-02-13 03:35:48 +00:00
return format_html(_("All my submissions"))
2022-05-14 17:57:27 +00:00
return format_html(
2023-02-13 03:35:48 +00:00
_('All submissions by <a href="{1}">{0}</a>'),
2022-05-14 17:57:27 +00:00
self.username,
reverse("user_page", args=[self.username]),
)
2020-01-21 06:35:58 +00:00
def get_context_data(self, **kwargs):
context = super(AllUserSubmissions, self).get_context_data(**kwargs)
2022-05-14 17:57:27 +00:00
context["dynamic_update"] = context["page_obj"].number == 1
context["dynamic_user_id"] = self.profile.id
context["last_msg"] = event.last()
2020-01-21 06:35:58 +00:00
return context
2023-02-13 03:35:48 +00:00
class AllFriendSubmissions(LoginRequiredMixin, GeneralSubmissions):
2023-02-18 21:12:33 +00:00
def get_queryset(self):
2023-02-13 03:35:48 +00:00
friends = self.request.profile.get_friends()
return (
2023-02-18 21:12:33 +00:00
super(AllFriendSubmissions, self).get_queryset().filter(user_id__in=friends)
2023-02-13 03:35:48 +00:00
)
def get_title(self):
return _("All friend submissions")
def get_context_data(self, **kwargs):
context = super(AllFriendSubmissions, self).get_context_data(**kwargs)
context["dynamic_update"] = False
context["page_type"] = "friend_tab"
return context
2020-01-21 06:35:58 +00:00
class ProblemSubmissionsBase(SubmissionsListBase):
show_problem = False
dynamic_update = True
2022-06-01 19:31:20 +00:00
check_contest_in_access_check = False
2020-01-21 06:35:58 +00:00
2023-02-18 21:12:33 +00:00
def get_queryset(self):
2022-05-14 17:57:27 +00:00
if (
self.in_contest
and not self.contest.contest_problems.filter(
problem_id=self.problem.id
).exists()
):
2020-01-21 06:35:58 +00:00
raise Http404()
2022-05-14 17:57:27 +00:00
return (
super(ProblemSubmissionsBase, self)
2022-10-29 03:34:12 +00:00
._get_entire_queryset()
2022-05-14 17:57:27 +00:00
.filter(problem_id=self.problem.id)
)
2020-01-21 06:35:58 +00:00
def get_title(self):
2022-05-14 17:57:27 +00:00
return _("All submissions for %s") % self.problem_name
2020-01-21 06:35:58 +00:00
def get_content_title(self):
2022-05-14 17:57:27 +00:00
return format_html(
'All submissions for <a href="{1}">{0}</a>',
self.problem_name,
reverse("problem_detail", args=[self.problem.code]),
)
2020-01-21 06:35:58 +00:00
def access_check_contest(self, request):
2022-06-03 03:14:01 +00:00
if self.in_contest:
if not self.contest.can_see_own_scoreboard(request.user):
raise Http404()
if not self.contest.is_accessible_by(request.user):
raise Http404()
2020-01-21 06:35:58 +00:00
2022-06-01 19:31:20 +00:00
def access_check(self, request):
2020-01-21 06:35:58 +00:00
if self.check_contest_in_access_check:
self.access_check_contest(request)
2022-06-01 19:31:20 +00:00
else:
2022-06-02 05:20:45 +00:00
is_own = hasattr(self, "is_own") and self.is_own
if not is_own and not self.problem.is_accessible_by(
request.user, request.in_contest_mode
):
2022-06-01 19:31:20 +00:00
raise Http404()
2020-01-21 06:35:58 +00:00
def get(self, request, *args, **kwargs):
2022-05-14 17:57:27 +00:00
if "problem" not in kwargs:
raise ImproperlyConfigured(_("Must pass a problem"))
self.problem = get_object_or_404(Problem, code=kwargs["problem"])
self.problem_name = self.problem.translated_name(self.request.LANGUAGE_CODE)
2020-01-21 06:35:58 +00:00
return super(ProblemSubmissionsBase, self).get(request, *args, **kwargs)
def get_all_submissions_page(self):
2022-05-14 17:57:27 +00:00
return reverse(
"chronological_submissions", kwargs={"problem": self.problem.code}
)
2020-01-21 06:35:58 +00:00
def get_context_data(self, **kwargs):
2022-05-14 17:57:27 +00:00
context = super(ProblemSubmissionsBase, self).get_context_data(**kwargs)
2020-01-21 06:35:58 +00:00
if self.dynamic_update:
2022-05-14 17:57:27 +00:00
context["dynamic_update"] = context["page_obj"].number == 1
context["dynamic_problem_id"] = self.problem.id
context["last_msg"] = event.last()
context["best_submissions_link"] = reverse(
"ranked_submissions", kwargs={"problem": self.problem.code}
)
2020-01-21 06:35:58 +00:00
return context
class ProblemSubmissions(ProblemSubmissionsBase):
def get_my_submissions_page(self):
if self.request.user.is_authenticated:
2022-05-14 17:57:27 +00:00
return reverse(
"user_submissions",
kwargs={
"problem": self.problem.code,
"user": self.request.user.username,
},
)
2020-01-21 06:35:58 +00:00
class UserProblemSubmissions(ConditionalUserTabMixin, UserMixin, ProblemSubmissions):
check_contest_in_access_check = False
@cached_property
def is_own(self):
2022-05-14 17:57:27 +00:00
return (
self.request.user.is_authenticated and self.request.profile == self.profile
)
2020-01-21 06:35:58 +00:00
def access_check(self, request):
super(UserProblemSubmissions, self).access_check(request)
if not self.is_own:
self.access_check_contest(request)
2023-02-18 21:12:33 +00:00
def get_queryset(self):
2022-05-14 17:57:27 +00:00
return (
super(UserProblemSubmissions, self)
2023-02-18 21:12:33 +00:00
.get_queryset()
2022-05-14 17:57:27 +00:00
.filter(user_id=self.profile.id)
)
2020-01-21 06:35:58 +00:00
def get_title(self):
if self.is_own:
2022-05-14 17:57:27 +00:00
return _("My submissions for %(problem)s") % {"problem": self.problem_name}
return _("%(user)s's submissions for %(problem)s") % {
"user": self.username,
"problem": self.problem_name,
}
2020-01-21 06:35:58 +00:00
def get_content_title(self):
if self.request.user.is_authenticated and self.request.profile == self.profile:
2022-05-14 17:57:27 +00:00
return format_html(
"""My submissions for <a href="{3}">{2}</a>""",
self.username,
reverse("user_page", args=[self.username]),
self.problem_name,
reverse("problem_detail", args=[self.problem.code]),
)
return format_html(
"""<a href="{1}">{0}</a>'s submissions for <a href="{3}">{2}</a>""",
self.username,
reverse("user_page", args=[self.username]),
self.problem_name,
reverse("problem_detail", args=[self.problem.code]),
)
2020-01-21 06:35:58 +00:00
def get_context_data(self, **kwargs):
2022-05-14 17:57:27 +00:00
context = super(UserProblemSubmissions, self).get_context_data(**kwargs)
context["dynamic_user_id"] = self.profile.id
2020-01-21 06:35:58 +00:00
return context
def single_submission(request, submission_id, show_problem=True):
request.no_profile_update = True
authenticated = request.user.is_authenticated
2022-05-14 17:57:27 +00:00
submission = get_object_or_404(
submission_related(Submission.objects.all()), id=int(submission_id)
)
2020-01-21 06:35:58 +00:00
if not submission.problem.is_accessible_by(request.user):
raise Http404()
2022-05-14 17:57:27 +00:00
return render(
request,
"submission/row.html",
{
"submission": submission,
"show_problem": show_problem,
"problem_name": show_problem
and submission.problem.translated_name(request.LANGUAGE_CODE),
2023-10-12 01:33:48 +00:00
"profile": request.profile if authenticated else None,
2022-05-14 17:57:27 +00:00
},
)
2020-01-21 06:35:58 +00:00
def single_submission_query(request):
request.no_profile_update = True
2022-05-14 17:57:27 +00:00
if "id" not in request.GET or not request.GET["id"].isdigit():
2020-01-21 06:35:58 +00:00
return HttpResponseBadRequest()
try:
2022-05-14 17:57:27 +00:00
show_problem = int(request.GET.get("show_problem", "1"))
2020-01-21 06:35:58 +00:00
except ValueError:
return HttpResponseBadRequest()
2022-05-14 17:57:27 +00:00
return single_submission(request, int(request.GET["id"]), bool(show_problem))
2020-01-21 06:35:58 +00:00
2023-02-18 21:12:33 +00:00
class AllSubmissions(InfinitePaginationMixin, GeneralSubmissions):
2020-01-21 06:35:58 +00:00
stats_update_interval = 3600
2023-02-18 21:12:33 +00:00
@property
def use_infinite_pagination(self):
return not self.in_contest
2020-01-21 06:35:58 +00:00
def get_context_data(self, **kwargs):
context = super(AllSubmissions, self).get_context_data(**kwargs)
2023-01-27 22:52:35 +00:00
context["dynamic_update"] = (
context["page_obj"].number == 1
) and not self.request.organization
2022-05-14 17:57:27 +00:00
context["last_msg"] = event.last()
context["stats_update_interval"] = self.stats_update_interval
2020-01-21 06:35:58 +00:00
return context
def _get_result_data(self):
2023-02-15 22:36:33 +00:00
if self.request.organization or self.in_contest:
2020-01-21 06:35:58 +00:00
return super(AllSubmissions, self)._get_result_data()
2022-05-14 17:57:27 +00:00
key = "global_submission_result_data"
2023-02-15 22:36:33 +00:00
if self.selected_statuses:
key += ":" + ",".join(self.selected_statuses)
if self.selected_languages:
key += ":" + ",".join(self.selected_languages)
2020-01-21 06:35:58 +00:00
result = cache.get(key)
if result:
return result
2023-02-15 23:22:37 +00:00
queryset = Submission.objects
if self.selected_languages:
2023-02-16 05:19:55 +00:00
queryset = queryset.filter(
language__in=Language.objects.filter(key__in=self.selected_languages)
)
2023-02-15 23:22:37 +00:00
if self.selected_statuses:
submission_results = [i for i, _ in Submission.RESULT]
if self.selected_statuses[0] in submission_results:
queryset = queryset.filter(result__in=self.selected_statuses)
else:
queryset = queryset.filter(status__in=self.selected_statuses)
result = get_result_data(queryset)
2020-01-21 06:35:58 +00:00
cache.set(key, result, self.stats_update_interval)
return result
class ForceContestMixin(object):
@property
def in_contest(self):
return True
@property
def contest(self):
return self._contest
def access_check(self, request):
super(ForceContestMixin, self).access_check(request)
2022-05-14 17:57:27 +00:00
if not request.user.has_perm("judge.see_private_contest"):
2020-01-21 06:35:58 +00:00
if not self.contest.is_visible:
raise Http404()
2022-05-14 17:57:27 +00:00
if (
self.contest.start_time is not None
and self.contest.start_time > timezone.now()
):
2020-01-21 06:35:58 +00:00
raise Http404()
def get_problem_number(self, problem):
2022-05-14 17:57:27 +00:00
return (
self.contest.contest_problems.select_related("problem")
.get(problem=problem)
.order
)
2020-01-21 06:35:58 +00:00
def get(self, request, *args, **kwargs):
2022-05-14 17:57:27 +00:00
if "contest" not in kwargs:
raise ImproperlyConfigured(_("Must pass a contest"))
self._contest = get_object_or_404(Contest, key=kwargs["contest"])
2020-01-21 06:35:58 +00:00
return super(ForceContestMixin, self).get(request, *args, **kwargs)
class UserContestSubmissions(ForceContestMixin, UserProblemSubmissions):
2022-06-01 19:31:20 +00:00
check_contest_in_access_check = True
2020-01-21 06:35:58 +00:00
def get_title(self):
if self.problem.is_accessible_by(self.request.user):
2022-05-14 17:57:27 +00:00
return "%s's submissions for %s in %s" % (
self.username,
self.problem_name,
self.contest.name,
)
2020-01-21 06:35:58 +00:00
return "%s's submissions for problem %s in %s" % (
2022-05-14 17:57:27 +00:00
self.username,
self.get_problem_number(self.problem),
self.contest.name,
)
2020-01-21 06:35:58 +00:00
2022-06-01 16:59:58 +00:00
def access_check(self, request):
super(UserContestSubmissions, self).access_check(request)
if not self.contest.users.filter(user_id=self.profile.id).exists():
raise Http404()
2020-01-21 06:35:58 +00:00
def get_content_title(self):
if self.problem.is_accessible_by(self.request.user):
2022-05-14 17:57:27 +00:00
return format_html(
_(
'<a href="{1}">{0}</a>\'s submissions for '
'<a href="{3}">{2}</a> in <a href="{5}">{4}</a>'
),
self.username,
reverse("user_page", args=[self.username]),
self.problem_name,
reverse("problem_detail", args=[self.problem.code]),
self.contest.name,
reverse("contest_view", args=[self.contest.key]),
)
return format_html(
_(
'<a href="{1}">{0}</a>\'s submissions for '
'problem {2} in <a href="{4}">{3}</a>'
),
self.username,
reverse("user_page", args=[self.username]),
self.get_problem_number(self.problem),
self.contest.name,
reverse("contest_view", args=[self.contest.key]),
)
2022-06-01 05:28:56 +00:00
class UserContestSubmissionsAjax(UserContestSubmissions):
template_name = "submission/user-ajax.html"
def contest_time(self, s):
2022-11-01 18:52:14 +00:00
if s.contest.participation.live:
2022-11-01 18:45:58 +00:00
if self.contest.time_limit:
return s.date - s.contest.participation.real_start
return s.date - self.contest.start_time
2022-06-01 05:28:56 +00:00
return None
2022-11-22 04:05:35 +00:00
def get_best_subtask_points(self):
2023-01-02 23:22:45 +00:00
if self.contest.format.has_hidden_subtasks:
2022-11-22 04:05:35 +00:00
contest_problem = self.contest.contest_problems.get(problem=self.problem)
best_subtasks = {}
total_points = 0
problem_points = 0
achieved_points = 0
2023-01-02 23:22:45 +00:00
hidden_subtasks = self.contest.format.get_hidden_subtasks()
2022-11-22 04:05:35 +00:00
for (
problem_id,
pp,
time,
subtask_points,
total_subtask_points,
subtask,
sub_id,
) in self.contest.format.get_results_by_subtask(
self.participation, self.include_frozen
):
if contest_problem.id != problem_id or total_subtask_points == 0:
continue
if not subtask:
subtask = 0
problem_points = pp
submission = Submission.objects.get(id=sub_id)
2023-01-04 21:11:00 +00:00
if subtask in hidden_subtasks.get(
str(problem_id), set()
) and not self.contest.is_editable_by(self.request.user):
2022-12-20 08:24:24 +00:00
best_subtasks[subtask] = {
2022-12-29 04:50:26 +00:00
"submission": None,
2022-12-29 05:05:44 +00:00
"contest_time": None,
2022-12-20 08:24:24 +00:00
"points": "???",
"total": total_subtask_points,
}
else:
best_subtasks[subtask] = {
"submission": submission,
"contest_time": nice_repr(
self.contest_time(submission), "noday"
),
"points": subtask_points,
"total": total_subtask_points,
}
achieved_points += subtask_points
2022-11-22 04:05:35 +00:00
total_points += total_subtask_points
for subtask in best_subtasks.values():
2022-12-20 08:24:24 +00:00
if subtask["points"] != "???":
subtask["points"] = floatformat(
subtask["points"] / total_points * problem_points,
-self.contest.points_precision,
)
2022-11-22 04:05:35 +00:00
subtask["total"] = floatformat(
subtask["total"] / total_points * problem_points,
-self.contest.points_precision,
)
2022-12-20 08:24:24 +00:00
if total_points > 0 and best_subtasks:
achieved_points = achieved_points / total_points * problem_points
2022-11-22 04:05:35 +00:00
return best_subtasks, achieved_points, problem_points
return None
2022-06-01 05:28:56 +00:00
def get_context_data(self, **kwargs):
context = super(UserContestSubmissionsAjax, self).get_context_data(**kwargs)
context["contest"] = self.contest
context["problem"] = self.problem
context["profile"] = self.profile
contest_problem = self.contest.contest_problems.get(problem=self.problem)
2022-11-01 18:52:14 +00:00
filtered_submissions = []
2022-12-20 21:24:34 +00:00
# Only show this for some users when using ioi16
2023-01-04 21:11:00 +00:00
if not self.contest.format.has_hidden_subtasks or self.contest.is_editable_by(
self.request.user
):
2022-12-20 21:24:34 +00:00
for s in context["submissions"]:
if not hasattr(s, "contest"):
continue
contest_time = self.contest_time(s)
if contest_time:
s.contest_time = nice_repr(contest_time, "noday")
else:
s.contest_time = None
total = floatformat(
contest_problem.points, -self.contest.points_precision
)
points = floatformat(s.contest.points, -self.contest.points_precision)
s.display_point = f"{points} / {total}"
filtered_submissions.append(s)
context["submissions"] = filtered_submissions
else:
context["submissions"] = None
2022-11-22 04:05:35 +00:00
best_subtasks = self.get_best_subtask_points()
if best_subtasks:
(
context["best_subtasks"],
context["points"],
context["total"],
) = best_subtasks
2022-12-20 08:24:24 +00:00
if context["points"] != "???":
context["points"] = floatformat(
context["points"], -self.contest.points_precision
)
2022-11-22 04:05:35 +00:00
context["total"] = floatformat(
context["total"], -self.contest.points_precision
)
context["subtasks"] = sorted(context["best_subtasks"].keys())
2022-06-01 05:28:56 +00:00
return context
2022-06-01 19:31:20 +00:00
def get(self, request, *args, **kwargs):
try:
return super(UserContestSubmissionsAjax, self).get(request, *args, **kwargs)
except Http404:
2022-06-02 05:20:45 +00:00
return HttpResponse(_("You don't have permission to access."))
2023-03-10 04:31:55 +00:00
class SubmissionSourceFileView(View):
def get(self, request, filename):
filepath = os.path.join(settings.DMOJ_SUBMISSION_ROOT, filename)
if not os.path.exists(filepath):
raise Http404("File not found")
response = HttpResponse()
with open(filepath, "rb") as f:
response.content = f.read()
response["Content-Type"] = "application/octet-stream"
response["Content-Disposition"] = "attachment; filename=%s" % (filename,)
return response