NDOJ/judge/admin/submission.py

385 lines
12 KiB
Python
Raw Normal View History

2020-01-21 06:35:58 +00:00
from functools import partial
from operator import itemgetter
from django.conf import settings
from django.conf.urls import url
from django.contrib import admin, messages
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.html import format_html
from django.utils.translation import gettext, gettext_lazy as _, pgettext, ungettext
from django_ace import AceWidget
2022-05-14 17:57:27 +00:00
from judge.models import (
ContestParticipation,
ContestProblem,
ContestSubmission,
Profile,
Submission,
SubmissionSource,
SubmissionTestCase,
)
2020-01-21 06:35:58 +00:00
from judge.utils.raw_sql import use_straight_join
class SubmissionStatusFilter(admin.SimpleListFilter):
2022-05-14 17:57:27 +00:00
parameter_name = title = "status"
__lookups = (
("None", _("None")),
("NotDone", _("Not done")),
("EX", _("Exceptional")),
) + Submission.STATUS
2020-01-21 06:35:58 +00:00
__handles = set(map(itemgetter(0), Submission.STATUS))
def lookups(self, request, model_admin):
return self.__lookups
def queryset(self, request, queryset):
2022-05-14 17:57:27 +00:00
if self.value() == "None":
2020-01-21 06:35:58 +00:00
return queryset.filter(status=None)
2022-05-14 17:57:27 +00:00
elif self.value() == "NotDone":
return queryset.exclude(status__in=["D", "IE", "CE", "AB"])
elif self.value() == "EX":
return queryset.exclude(status__in=["D", "CE", "G", "AB"])
2020-01-21 06:35:58 +00:00
elif self.value() in self.__handles:
return queryset.filter(status=self.value())
class SubmissionResultFilter(admin.SimpleListFilter):
2022-05-14 17:57:27 +00:00
parameter_name = title = "result"
__lookups = (("None", _("None")), ("BAD", _("Unaccepted"))) + Submission.RESULT
2020-01-21 06:35:58 +00:00
__handles = set(map(itemgetter(0), Submission.RESULT))
def lookups(self, request, model_admin):
return self.__lookups
def queryset(self, request, queryset):
2022-05-14 17:57:27 +00:00
if self.value() == "None":
2020-01-21 06:35:58 +00:00
return queryset.filter(result=None)
2022-05-14 17:57:27 +00:00
elif self.value() == "BAD":
return queryset.exclude(result="AC")
2020-01-21 06:35:58 +00:00
elif self.value() in self.__handles:
return queryset.filter(result=self.value())
class SubmissionTestCaseInline(admin.TabularInline):
2022-05-14 17:57:27 +00:00
fields = ("case", "batch", "status", "time", "memory", "points", "total")
readonly_fields = ("case", "batch", "total")
2020-01-21 06:35:58 +00:00
model = SubmissionTestCase
can_delete = False
max_num = 0
class ContestSubmissionInline(admin.StackedInline):
2022-05-14 17:57:27 +00:00
fields = ("problem", "participation", "points")
2020-01-21 06:35:58 +00:00
model = ContestSubmission
def get_formset(self, request, obj=None, **kwargs):
2022-05-14 17:57:27 +00:00
kwargs["formfield_callback"] = partial(
self.formfield_for_dbfield, request=request, obj=obj
)
2020-01-21 06:35:58 +00:00
return super(ContestSubmissionInline, self).get_formset(request, obj, **kwargs)
def formfield_for_dbfield(self, db_field, **kwargs):
2022-05-14 17:57:27 +00:00
submission = kwargs.pop("obj", None)
2020-01-21 06:35:58 +00:00
label = None
if submission:
2022-05-14 17:57:27 +00:00
if db_field.name == "participation":
kwargs["queryset"] = ContestParticipation.objects.filter(
user=submission.user, contest__problems=submission.problem
).only("id", "contest__name")
2020-01-21 06:35:58 +00:00
def label(obj):
return obj.contest.name
2022-05-14 17:57:27 +00:00
elif db_field.name == "problem":
kwargs["queryset"] = ContestProblem.objects.filter(
problem=submission.problem
).only("id", "problem__name", "contest__name")
2020-01-21 06:35:58 +00:00
def label(obj):
2022-05-14 17:57:27 +00:00
return pgettext("contest problem", "%(problem)s in %(contest)s") % {
"problem": obj.problem.name,
"contest": obj.contest.name,
2020-01-21 06:35:58 +00:00
}
2022-05-14 17:57:27 +00:00
field = super(ContestSubmissionInline, self).formfield_for_dbfield(
db_field, **kwargs
)
2020-01-21 06:35:58 +00:00
if label is not None:
field.label_from_instance = label
return field
class SubmissionSourceInline(admin.StackedInline):
2022-05-14 17:57:27 +00:00
fields = ("source",)
2020-01-21 06:35:58 +00:00
model = SubmissionSource
can_delete = False
extra = 0
def get_formset(self, request, obj=None, **kwargs):
2022-05-14 17:57:27 +00:00
kwargs.setdefault("widgets", {})["source"] = AceWidget(
mode=obj and obj.language.ace, theme=request.profile.ace_theme
)
2020-01-21 06:35:58 +00:00
return super().get_formset(request, obj, **kwargs)
class SubmissionAdmin(admin.ModelAdmin):
2022-05-14 17:57:27 +00:00
readonly_fields = ("user", "problem", "date", "judged_date")
fields = (
"user",
"problem",
"date",
"judged_date",
"time",
"memory",
"points",
"language",
"status",
"result",
"case_points",
"case_total",
"judged_on",
"error",
)
actions = ("judge", "recalculate_score")
list_display = (
"id",
"problem_code",
"problem_name",
"user_column",
"execution_time",
"pretty_memory",
"points",
"language_column",
"status",
"result",
"judge_column",
)
list_filter = ("language", SubmissionStatusFilter, SubmissionResultFilter)
search_fields = ("problem__code", "problem__name", "user__user__username")
2020-01-21 06:35:58 +00:00
actions_on_top = True
actions_on_bottom = True
2022-05-14 17:57:27 +00:00
inlines = [
SubmissionSourceInline,
SubmissionTestCaseInline,
ContestSubmissionInline,
]
2020-01-21 06:35:58 +00:00
def get_queryset(self, request):
2022-05-14 17:57:27 +00:00
queryset = Submission.objects.select_related(
"problem", "user__user", "language"
).only(
"problem__code",
"problem__name",
"user__user__username",
"language__name",
"time",
"memory",
"points",
"status",
"result",
2020-01-21 06:35:58 +00:00
)
use_straight_join(queryset)
2022-05-14 17:57:27 +00:00
if not request.user.has_perm("judge.edit_all_problem"):
2020-01-21 06:35:58 +00:00
id = request.profile.id
2022-05-14 17:57:27 +00:00
queryset = queryset.filter(
Q(problem__authors__id=id) | Q(problem__curators__id=id)
).distinct()
2020-01-21 06:35:58 +00:00
return queryset
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
2022-05-14 17:57:27 +00:00
if not request.user.has_perm("judge.edit_own_problem"):
2020-01-21 06:35:58 +00:00
return False
2022-05-14 17:57:27 +00:00
if request.user.has_perm("judge.edit_all_problem") or obj is None:
2020-01-21 06:35:58 +00:00
return True
return obj.problem.is_editor(request.profile)
def lookup_allowed(self, key, value):
2022-05-14 17:57:27 +00:00
return super(SubmissionAdmin, self).lookup_allowed(key, value) or key in (
"problem__code",
)
2020-01-21 06:35:58 +00:00
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
if "case_points" in form.changed_data or "case_total" in form.changed_data:
obj.update_contest()
2020-01-21 06:35:58 +00:00
def judge(self, request, queryset):
2022-05-14 17:57:27 +00:00
if not request.user.has_perm(
"judge.rejudge_submission"
) or not request.user.has_perm("judge.edit_own_problem"):
self.message_user(
request,
gettext("You do not have the permission to rejudge submissions."),
level=messages.ERROR,
)
2020-01-21 06:35:58 +00:00
return
2022-05-14 17:57:27 +00:00
queryset = queryset.order_by("id")
if (
not request.user.has_perm("judge.rejudge_submission_lot")
and queryset.count() > settings.DMOJ_SUBMISSIONS_REJUDGE_LIMIT
):
self.message_user(
request,
gettext(
"You do not have the permission to rejudge THAT many submissions."
),
level=messages.ERROR,
)
2020-01-21 06:35:58 +00:00
return
2022-05-14 17:57:27 +00:00
if not request.user.has_perm("judge.edit_all_problem"):
2020-01-21 06:35:58 +00:00
id = request.profile.id
2022-05-14 17:57:27 +00:00
queryset = queryset.filter(
Q(problem__authors__id=id) | Q(problem__curators__id=id)
)
2020-01-21 06:35:58 +00:00
judged = len(queryset)
for model in queryset:
model.judge(rejudge=True, batch_rejudge=True)
2022-05-14 17:57:27 +00:00
self.message_user(
request,
ungettext(
"%d submission was successfully scheduled for rejudging.",
"%d submissions were successfully scheduled for rejudging.",
judged,
)
% judged,
)
judge.short_description = _("Rejudge the selected submissions")
2020-01-21 06:35:58 +00:00
def recalculate_score(self, request, queryset):
2022-05-14 17:57:27 +00:00
if not request.user.has_perm("judge.rejudge_submission"):
self.message_user(
request,
gettext("You do not have the permission to rejudge submissions."),
level=messages.ERROR,
)
2020-01-21 06:35:58 +00:00
return
2022-05-14 17:57:27 +00:00
submissions = list(
queryset.defer(None)
.select_related(None)
.select_related("problem")
.only(
"points",
"case_points",
"case_total",
"problem__partial",
"problem__points",
)
)
2020-01-21 06:35:58 +00:00
for submission in submissions:
2022-05-14 17:57:27 +00:00
submission.points = round(
submission.case_points
/ submission.case_total
* submission.problem.points
if submission.case_total
else 0,
1,
)
if (
not submission.problem.partial
and submission.points < submission.problem.points
):
2020-01-21 06:35:58 +00:00
submission.points = 0
submission.save()
submission.update_contest()
2022-05-14 17:57:27 +00:00
for profile in Profile.objects.filter(
id__in=queryset.values_list("user_id", flat=True).distinct()
):
2020-01-21 06:35:58 +00:00
profile.calculate_points()
2022-05-14 17:57:27 +00:00
cache.delete("user_complete:%d" % profile.id)
cache.delete("user_attempted:%d" % profile.id)
2020-01-21 06:35:58 +00:00
for participation in ContestParticipation.objects.filter(
2022-05-14 17:57:27 +00:00
id__in=queryset.values_list("contest__participation_id")
).prefetch_related("contest"):
2020-01-21 06:35:58 +00:00
participation.recompute_results()
2022-05-14 17:57:27 +00:00
self.message_user(
request,
ungettext(
"%d submission were successfully rescored.",
"%d submissions were successfully rescored.",
len(submissions),
)
% len(submissions),
)
recalculate_score.short_description = _("Rescore the selected submissions")
2020-01-21 06:35:58 +00:00
def problem_code(self, obj):
return obj.problem.code
2022-05-14 17:57:27 +00:00
problem_code.short_description = _("Problem code")
problem_code.admin_order_field = "problem__code"
2020-01-21 06:35:58 +00:00
def problem_name(self, obj):
return obj.problem.name
2022-05-14 17:57:27 +00:00
problem_name.short_description = _("Problem name")
problem_name.admin_order_field = "problem__name"
2020-01-21 06:35:58 +00:00
def user_column(self, obj):
return obj.user.user.username
2022-05-14 17:57:27 +00:00
user_column.admin_order_field = "user__user__username"
user_column.short_description = _("User")
2020-01-21 06:35:58 +00:00
def execution_time(self, obj):
2022-05-14 17:57:27 +00:00
return round(obj.time, 2) if obj.time is not None else "None"
execution_time.short_description = _("Time")
execution_time.admin_order_field = "time"
2020-01-21 06:35:58 +00:00
def pretty_memory(self, obj):
memory = obj.memory
if memory is None:
2022-05-14 17:57:27 +00:00
return gettext("None")
2020-01-21 06:35:58 +00:00
if memory < 1000:
2022-05-14 17:57:27 +00:00
return gettext("%d KB") % memory
2020-01-21 06:35:58 +00:00
else:
2022-05-14 17:57:27 +00:00
return gettext("%.2f MB") % (memory / 1024)
pretty_memory.admin_order_field = "memory"
pretty_memory.short_description = _("Memory")
2020-01-21 06:35:58 +00:00
def language_column(self, obj):
return obj.language.name
2022-05-14 17:57:27 +00:00
language_column.admin_order_field = "language__name"
language_column.short_description = _("Language")
2020-01-21 06:35:58 +00:00
def judge_column(self, obj):
2022-05-14 17:57:27 +00:00
return format_html(
'<input type="button" value="Rejudge" onclick="location.href=\'{}/judge/\'" />',
obj.id,
)
judge_column.short_description = ""
2020-01-21 06:35:58 +00:00
def get_urls(self):
return [
2022-05-14 17:57:27 +00:00
url(r"^(\d+)/judge/$", self.judge_view, name="judge_submission_rejudge"),
2020-01-21 06:35:58 +00:00
] + super(SubmissionAdmin, self).get_urls()
def judge_view(self, request, id):
2022-05-14 17:57:27 +00:00
if not request.user.has_perm(
"judge.rejudge_submission"
) or not request.user.has_perm("judge.edit_own_problem"):
2020-01-21 06:35:58 +00:00
raise PermissionDenied()
submission = get_object_or_404(Submission, id=id)
2022-05-14 17:57:27 +00:00
if not request.user.has_perm(
"judge.edit_all_problem"
) and not submission.problem.is_editor(request.profile):
2020-01-21 06:35:58 +00:00
raise PermissionDenied()
submission.judge(rejudge=True)
2022-05-14 17:57:27 +00:00
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))