Skip to content

Commit 89cdf39

Browse files
committed
Add support for archiving a submission
1 parent 4d1b682 commit 89cdf39

File tree

15 files changed

+155
-20
lines changed

15 files changed

+155
-20
lines changed

dmoj/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ def paged_list_view(view, name):
131131

132132
path('/manage/submission', include([
133133
path('', problem_manage.ManageProblemSubmissionView.as_view(), name='problem_manage_submissions'),
134+
path('/archive/locked', problem_manage.ArchiveLockedSubmissionsView.as_view(),
135+
name='problem_submissions_archive_locked'),
136+
path('/archive/success/<slug:task_id>', problem_manage.archive_success,
137+
name='problem_submissions_archive_success'),
134138
path('/rejudge', problem_manage.RejudgeSubmissionsView.as_view(), name='problem_submissions_rejudge'),
135139
path('/rejudge/preview', problem_manage.PreviewRejudgeSubmissionsView.as_view(),
136140
name='problem_submissions_rejudge_preview'),

judge/admin/submission.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ def get_formset(self, request, obj=None, **kwargs):
120120

121121
class SubmissionAdmin(VersionAdmin):
122122
readonly_fields = ('user', 'problem', 'date', 'judged_date')
123-
fields = ('user', 'problem', 'date', 'judged_date', 'locked_after', 'time', 'memory', 'points', 'language',
124-
'status', 'result', 'case_points', 'case_total', 'judged_on', 'error')
123+
fields = ('user', 'problem', 'date', 'judged_date', 'locked_after', 'is_archived', 'time', 'memory', 'points',
124+
'language', 'status', 'result', 'case_points', 'case_total', 'judged_on', 'error')
125125
actions = ('judge', 'recalculate_score')
126126
list_display = ('id', 'problem_code', 'problem_name', 'user_column', 'execution_time', 'pretty_memory',
127127
'points', 'language_column', 'status', 'result', 'judge_column')
@@ -135,6 +135,8 @@ def get_readonly_fields(self, request, obj=None):
135135
fields = self.readonly_fields
136136
if not request.user.has_perm('judge.lock_submission'):
137137
fields += ('locked_after',)
138+
if not request.user.has_perm('judge.archive_submission'):
139+
fields += ('is_archived',)
138140
return fields
139141

140142
def get_queryset(self, request):
@@ -244,7 +246,9 @@ def language_column(self, obj):
244246

245247
@admin.display(description='')
246248
def judge_column(self, obj):
247-
if obj.is_locked:
249+
if obj.is_archived:
250+
return format_html('<input type="button" disabled value="{0}"/>', _('Archived'))
251+
elif obj.is_locked:
248252
return format_html('<input type="button" disabled value="{0}"/>', _('Locked'))
249253
else:
250254
return format_html('<a class="button action-link" href="{1}">{0}</a>', _('Rejudge'),
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Generated by Django 4.2.17 on 2025-03-02 10:16
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('judge', '0149_add_organization_private_problems_permission'),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name='submission',
15+
options={
16+
'permissions': (
17+
('abort_any_submission', 'Abort any submission'),
18+
('rejudge_submission', 'Rejudge the submission'),
19+
('rejudge_submission_lot', 'Rejudge a lot of submissions'),
20+
('spam_submission', 'Submit without limit'),
21+
('view_all_submission', 'View all submission'),
22+
('resubmit_other', "Resubmit others' submission"),
23+
('lock_submission', 'Change lock status of submission'),
24+
('archive_submission', 'Archive any submission'),
25+
),
26+
'verbose_name': 'submission',
27+
'verbose_name_plural': 'submissions',
28+
},
29+
),
30+
migrations.AddField(
31+
model_name='submission',
32+
name='is_archived',
33+
field=models.BooleanField(default=False, verbose_name='is archived'),
34+
),
35+
]

judge/models/profile.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,16 @@ def calculate_points(self, table=_pp_table):
243243
from judge.models import Problem
244244
public_problems = Problem.get_public_problems()
245245
data = (
246-
public_problems.filter(submission__user=self, submission__points__isnull=False)
246+
public_problems.filter(submission__user=self, submission__is_archived=False,
247+
submission__points__isnull=False)
247248
.annotate(max_points=Max('submission__points')).order_by('-max_points')
248249
.values_list('max_points', flat=True).filter(max_points__gt=0)
249250
)
250251
bonus_function = settings.DMOJ_PP_BONUS_FUNCTION
251252
points = sum(data)
252253
entries = min(len(data), len(table))
253254
problems = (
254-
public_problems.filter(submission__user=self, submission__result='AC',
255+
public_problems.filter(submission__user=self, submission__is_archived=False, submission__result='AC',
255256
submission__case_points__gte=F('submission__case_total'))
256257
.values('id').distinct().count()
257258
)

judge/models/submission.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class Submission(models.Model):
8888
contest_object = models.ForeignKey('Contest', verbose_name=_('contest'), null=True, blank=True,
8989
on_delete=models.SET_NULL, related_name='+', db_index=False)
9090
locked_after = models.DateTimeField(verbose_name=_('submission lock'), null=True, blank=True)
91+
is_archived = models.BooleanField(verbose_name=_('is archived'), default=False)
9192

9293
@classmethod
9394
def result_class_from_code(cls, result, case_points, case_total):
@@ -121,7 +122,7 @@ def is_locked(self):
121122
return self.locked_after is not None and self.locked_after < timezone.now()
122123

123124
def judge(self, *args, rejudge=False, force_judge=False, rejudge_user=None, **kwargs):
124-
if force_judge or not self.is_locked:
125+
if force_judge or not (self.is_archived or self.is_locked):
125126
if rejudge:
126127
with revisions.create_revision(manage_manually=True):
127128
if rejudge_user:
@@ -227,6 +228,7 @@ class Meta:
227228
('view_all_submission', _('View all submission')),
228229
('resubmit_other', _("Resubmit others' submission")),
229230
('lock_submission', _('Change lock status of submission')),
231+
('archive_submission', _('Archive any submission')),
230232
)
231233
verbose_name = _('submission')
232234
verbose_name_plural = _('submissions')

judge/performance_points.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ def get_pp_breakdown(user, start=0, end=settings.DMOJ_PP_ENTRIES):
3737
INNER JOIN judge_submission ON (judge_problem.id = judge_submission.problem_id)
3838
WHERE (judge_problem.is_public AND
3939
NOT judge_problem.is_organization_private AND
40+
NOT judge_submission.is_archived AND
4041
judge_submission.points IS NOT NULL AND
4142
judge_submission.user_id = %s)
4243
GROUP BY judge_problem.id
4344
HAVING MAX(judge_submission.points) > 0.0
4445
) AS max_points_table
4546
{join_type} judge_submission ON (
4647
judge_submission.problem_id = max_points_table.problem_id AND
48+
NOT judge_submission.is_archived AND
4749
judge_submission.points = max_points_table.max_points AND
4850
judge_submission.user_id = %s
4951
)

judge/tasks/submission.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from judge.models import Problem, Profile, Submission
88
from judge.utils.celery import Progress
99

10-
__all__ = ('apply_submission_filter', 'rejudge_problem_filter', 'rescore_problem')
10+
__all__ = ('apply_submission_filter', 'archive_locked_submissions', 'rejudge_problem_filter', 'rescore_problem')
1111

1212

1313
def apply_submission_filter(queryset, id_range, languages, results):
@@ -18,11 +18,40 @@ def apply_submission_filter(queryset, id_range, languages, results):
1818
queryset = queryset.filter(language_id__in=languages)
1919
if results:
2020
queryset = queryset.filter(result__in=results)
21-
queryset = queryset.exclude(locked_after__lt=timezone.now()) \
21+
queryset = queryset.exclude(is_archived=True) \
22+
.exclude(locked_after__lt=timezone.now()) \
2223
.exclude(status__in=Submission.IN_PROGRESS_GRADING_STATUS)
2324
return queryset
2425

2526

27+
@shared_task(bind=True)
28+
def archive_locked_submissions(self, problem_id):
29+
submissions = \
30+
Submission.objects.filter(problem_id=problem_id, is_archived=False, locked_after__lt=timezone.now())
31+
32+
with Progress(self, submissions.count(), stage=_('Modifying submissions')) as p:
33+
archived = 0
34+
for submission in submissions.iterator():
35+
submission.is_archived = True
36+
submission.save(update_fields=['is_archived'])
37+
archived += 1
38+
if archived % 10 == 0:
39+
p.done = archived
40+
41+
with Progress(self, submissions.values('user_id').distinct().count(), stage=_('Recalculating user points')) as p:
42+
users = 0
43+
profiles = Profile.objects.filter(id__in=submissions.values_list('user_id', flat=True).distinct())
44+
for profile in profiles.iterator():
45+
profile._updating_stats_only = True
46+
profile.calculate_points()
47+
cache.delete('user_complete:%d' % profile.id)
48+
cache.delete('user_attempted:%d' % profile.id)
49+
users += 1
50+
if users % 10 == 0:
51+
p.done = users
52+
return archived
53+
54+
2655
@shared_task(bind=True)
2756
def rejudge_problem_filter(self, problem_id, id_range=None, languages=None, results=None, user_id=None):
2857
queryset = Submission.objects.filter(problem_id=problem_id)

judge/views/problem_manage.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
from django.contrib.auth.mixins import PermissionRequiredMixin
66
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
77
from django.urls import reverse
8+
from django.utils import timezone
89
from django.utils.html import escape, format_html
910
from django.utils.safestring import mark_safe
1011
from django.utils.translation import gettext as _, ngettext
1112
from django.views.generic import DetailView
1213
from django.views.generic.detail import BaseDetailView
1314

1415
from judge.models import Language, Submission
15-
from judge.tasks import apply_submission_filter, rejudge_problem_filter, rescore_problem
16+
from judge.tasks import apply_submission_filter, archive_locked_submissions, rejudge_problem_filter, rescore_problem
1617
from judge.utils.celery import redirect_to_task_status
1718
from judge.utils.views import TitleMixin
1819
from judge.views.problem import ProblemMixin
@@ -56,13 +57,26 @@ def get_content_title(self):
5657

5758
def get_context_data(self, **kwargs):
5859
context = super().get_context_data(**kwargs)
60+
context['locked_count'] = \
61+
self.object.submission_set.filter(is_archived=False, locked_after__lt=timezone.now()).count()
5962
context['submission_count'] = self.object.submission_set.count()
6063
context['languages'] = [(lang_id, short_name or key) for lang_id, key, short_name in
6164
Language.objects.values_list('id', 'key', 'short_name')]
6265
context['results'] = sorted(map(itemgetter(0), Submission.RESULT))
6366
return context
6467

6568

69+
class ArchiveLockedSubmissionsView(PermissionRequiredMixin, ManageProblemSubmissionActionMixin, BaseDetailView):
70+
permission_required = 'judge.archive_submission'
71+
72+
def perform_action(self):
73+
status = archive_locked_submissions.delay(self.object.id)
74+
return redirect_to_task_status(
75+
status, message=_('Archiving all locked submissions for %s...') % (self.object.name,),
76+
redirect=reverse('problem_submissions_archive_success', args=[self.object.code, status.id]),
77+
)
78+
79+
6680
class BaseRejudgeSubmissionsView(PermissionRequiredMixin, ManageProblemSubmissionActionMixin, BaseDetailView):
6781
permission_required = 'judge.rejudge_submission_lot'
6882

@@ -113,6 +127,15 @@ def perform_action(self):
113127
)
114128

115129

130+
def archive_success(request, problem, task_id):
131+
count = AsyncResult(task_id).result
132+
if not isinstance(count, int):
133+
raise Http404()
134+
messages.success(request, ngettext('%d submission was successfully archived.',
135+
'%d submissions were successfully archived.', count) % (count,))
136+
return HttpResponseRedirect(reverse('problem_manage_submissions', args=[problem]))
137+
138+
116139
def rejudge_success(request, problem, task_id):
117140
count = AsyncResult(task_id).result
118141
if not isinstance(count, int):

judge/views/ranked_submission.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ def get_queryset(self):
4242
FROM (
4343
SELECT sub.user_id AS uid, MAX(sub.points) AS points
4444
FROM judge_submission AS sub {contest_join}
45-
WHERE sub.problem_id = %s AND {points} > 0 {constraint}
45+
WHERE sub.problem_id = %s AND NOT sub.is_archived AND {points} > 0 {constraint}
4646
GROUP BY sub.user_id
4747
) AS highscore STRAIGHT_JOIN (
4848
SELECT sub.user_id AS uid, sub.points, MIN(sub.time) as time
4949
FROM judge_submission AS sub {contest_join}
50-
WHERE sub.problem_id = %s AND {points} > 0 {constraint}
50+
WHERE sub.problem_id = %s AND NOT sub.is_archived AND {points} > 0 {constraint}
5151
GROUP BY sub.user_id, {points}
5252
) AS fastest ON (highscore.uid = fastest.uid AND highscore.points = fastest.points)
5353
STRAIGHT_JOIN judge_submission AS sub

judge/views/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ class UserProblemsPage(UserPage):
203203
def get_context_data(self, **kwargs):
204204
context = super(UserProblemsPage, self).get_context_data(**kwargs)
205205

206-
result = Submission.objects.filter(user=self.object, points__gt=0, problem__is_public=True,
206+
result = Submission.objects.filter(user=self.object, is_archived=False, points__gt=0, problem__is_public=True,
207207
problem__is_organization_private=False) \
208208
.exclude(problem__in=self.get_completed_problems() if self.hide_solved else []) \
209209
.values('problem__id', 'problem__code', 'problem__name', 'problem__points', 'problem__group__full_name') \

0 commit comments

Comments
 (0)