From 0fbcc4d03a67718d095b63ea559c1c61aad6413a Mon Sep 17 00:00:00 2001 From: Minh Ton <37860569+Minh-Ton@users.noreply.github.com> Date: Sun, 21 Apr 2024 10:51:06 +0700 Subject: [PATCH] feat: add 'Reject Submission' feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Phan Bình Nguyên Lâm <76941117+LLaammTTeerr@users.noreply.github.com> --- dmoj/settings.py | 1 + dmoj/urls.py | 1 + judge/models/problem.py | 3 +++ judge/models/submission.py | 20 ++++++++++++++++++++ judge/views/widgets.py | 21 +++++++++++++++++++++ resources/submission.scss | 5 +++++ templates/submission/status.html | 21 +++++++++++++++++++++ 7 files changed, 72 insertions(+) diff --git a/dmoj/settings.py b/dmoj/settings.py index 50052f85a..4a4ab9187 100755 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -231,6 +231,7 @@ 'WA': '#ed4420', 'CE': '#42586d', 'ERR': '#ffa71c', + 'RJ': '#3e6291', } DMOJ_API_PAGE_SIZE = 1000 diff --git a/dmoj/urls.py b/dmoj/urls.py index 4767387e4..bbfae0c8f 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -334,6 +334,7 @@ def paged_list_view(view, name): path('widgets/', include([ path('rejudge', widgets.rejudge_submission, name='submission_rejudge'), + path('reject', widgets.reject_submission, name='submission_reject'), path('single_submission', submission.single_submission, name='submission_single_query'), path('submission_testcases', submission.SubmissionTestCaseQuery.as_view(), name='submission_testcases_query'), path('status-table', status.status_table, name='status_table'), diff --git a/judge/models/problem.py b/judge/models/problem.py index 7a90824ae..521dad7bc 100644 --- a/judge/models/problem.py +++ b/judge/models/problem.py @@ -322,6 +322,9 @@ def is_accessible_by(self, user, skip_contest_problem_check=False): def is_rejudgeable_by(self, user): return user.has_perm('judge.rejudge_submission') and self.is_editable_by(user) + def is_rejectable_by(self, user): + return user.has_perm('judge.reject_submission') and self.is_editable_by(user) + def is_subs_manageable_by(self, user): return user.is_staff and self.is_rejudgeable_by(user) diff --git a/judge/models/submission.py b/judge/models/submission.py index 957e405c4..e7872c25e 100644 --- a/judge/models/submission.py +++ b/judge/models/submission.py @@ -30,6 +30,7 @@ ('IE', _('Internal Error')), ('SC', _('Short Circuited')), ('AB', _('Aborted')), + ('RJ', _('Rejected')), ) SUBMISSION_STATUS = ( @@ -68,6 +69,7 @@ class Submission(models.Model): 'G': _('Grading'), 'D': _('Completed'), 'AB': _('Aborted'), + 'RJ': _('Rejected'), } user = models.ForeignKey(Profile, on_delete=models.CASCADE, db_index=False) @@ -126,6 +128,23 @@ def long_status(self): def is_locked(self): return self.locked_after is not None and self.locked_after < timezone.now() + def reject(self): + self.points = 0 + self.result = 'RJ' + self.case_points = 0 + if self.problem.is_public and not self.problem.is_organization_private: + self.user._updating_stats_only = True + self.user.calculate_points() + self.problem._updating_stats_only = True + self.problem.update_stats() + self.save() + queryset = SubmissionTestCase.objects.filter(submission=self) + for tc in queryset: + tc.points = 0 + tc.status = 'RJ' + tc.save() + self.update_contest() + def judge(self, *args, rejudge=False, force_judge=False, rejudge_user=None, **kwargs): if force_judge or not self.is_locked: if rejudge: @@ -229,6 +248,7 @@ class Meta: permissions = ( ('abort_any_submission', _('Abort any submission')), ('rejudge_submission', _('Rejudge the submission')), + ('reject_submission', _('Reject the submisison')), ('rejudge_submission_lot', _('Rejudge a lot of submissions')), ('spam_submission', _('Submit without limit')), ('view_all_submission', _('View all submission')), diff --git a/judge/views/widgets.py b/judge/views/widgets.py index bcaa2f75e..5e6c262ac 100755 --- a/judge/views/widgets.py +++ b/judge/views/widgets.py @@ -37,6 +37,27 @@ def rejudge_submission(request): return HttpResponseRedirect(redirect) if redirect else HttpResponse('success', content_type='text/plain') +@login_required +@require_POST +def reject_submission(request): + if 'id' not in request.POST or not request.POST['id'].isdigit(): + return HttpResponseBadRequest() + + try: + submission = Submission.objects.get(id=request.POST['id']) + except Submission.DoesNotExist: + return HttpResponseBadRequest() + + if not submission.problem.is_rejectable_by(request.user): + return HttpResponseForbidden() + + submission.reject() + + redirect = request.POST.get('path', None) + + return HttpResponseRedirect(redirect) if redirect else HttpResponse('success', content_type='text/plain') + + def django_uploader(image): ext = os.path.splitext(image.name)[1] if ext not in settings.MARTOR_UPLOAD_SAFE_EXTS: diff --git a/resources/submission.scss b/resources/submission.scss index 18a59abe4..db93d8d62 100644 --- a/resources/submission.scss +++ b/resources/submission.scss @@ -318,6 +318,11 @@ label[for="language"], label[for="status"], label[for="organization"] { font-weight: bold; } +.case-RJ { + color : #3E6291; + font-weight: bold; +} + .case-bad { text-decoration: underline; } diff --git a/templates/submission/status.html b/templates/submission/status.html index 1c20f0d00..236958fd8 100644 --- a/templates/submission/status.html +++ b/templates/submission/status.html @@ -96,6 +96,27 @@ {% endif %} + + {% if perms.judge.reject_submission and can_edit and not submission.is_locked %} + {% compress js %} + + {% endcompress %} +