Skip to content

Commit

Permalink
Add support for archiving a submission
Browse files Browse the repository at this point in the history
  • Loading branch information
int-y1 committed Mar 4, 2025
1 parent 4d1b682 commit 5950d36
Show file tree
Hide file tree
Showing 16 changed files with 95 additions and 25 deletions.
6 changes: 4 additions & 2 deletions judge/admin/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ def get_formset(self, request, obj=None, **kwargs):

class SubmissionAdmin(VersionAdmin):
readonly_fields = ('user', 'problem', 'date', 'judged_date')
fields = ('user', 'problem', 'date', 'judged_date', 'locked_after', 'time', 'memory', 'points', 'language',
'status', 'result', 'case_points', 'case_total', 'judged_on', 'error')
fields = ('user', 'problem', 'date', 'judged_date', 'locked_after', 'is_archived', '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')
Expand All @@ -135,6 +135,8 @@ def get_readonly_fields(self, request, obj=None):
fields = self.readonly_fields
if not request.user.has_perm('judge.lock_submission'):
fields += ('locked_after',)
if not request.user.has_perm('judge.archive_submission'):
fields += ('is_archived',)
return fields

def get_queryset(self, request):
Expand Down
35 changes: 35 additions & 0 deletions judge/migrations/0150_submission_is_archived.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 4.2.17 on 2025-03-02 10:16

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('judge', '0149_add_organization_private_problems_permission'),
]

operations = [
migrations.AlterModelOptions(
name='submission',
options={
'permissions': (
('abort_any_submission', 'Abort any submission'),
('rejudge_submission', 'Rejudge the submission'),
('rejudge_submission_lot', 'Rejudge a lot of submissions'),
('spam_submission', 'Submit without limit'),
('view_all_submission', 'View all submission'),
('resubmit_other', "Resubmit others' submission"),
('lock_submission', 'Change lock status of submission'),
('archive_submission', 'Archive any submission'),
),
'verbose_name': 'submission',
'verbose_name_plural': 'submissions',
},
),
migrations.AddField(
model_name='submission',
name='is_archived',
field=models.BooleanField(default=False, verbose_name='is archived'),
),
]
7 changes: 4 additions & 3 deletions judge/models/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,8 @@ def submission_source_visibility(self):
return self.submission_source_visibility_mode

def update_stats(self):
all_queryset = self.submission_set.filter(user__is_unlisted=False)
ac_queryset = all_queryset.filter(points__gte=self.points, result='AC')
all_queryset = self.submission_set.filter(user__is_unlisted=False, is_archived=False)
ac_queryset = all_queryset.filter(result='AC', case_points__gte=F('case_total'))
self.user_count = ac_queryset.values('user').distinct().count()
submissions = all_queryset.count()
if submissions:
Expand Down Expand Up @@ -466,7 +466,8 @@ def save(self, *args, **kwargs):

def is_solved_by(self, user):
# Return true if a full AC submission to the problem from the user exists.
return self.submission_set.filter(user=user.profile, result='AC', points__gte=F('problem__points')).exists()
return self.submission_set.filter(
user=user.profile, is_archived=False, result='AC', case_points__gte=F('case_total')).exists()

def vote_permission_for_user(self, user):
if not user.is_authenticated:
Expand Down
7 changes: 4 additions & 3 deletions judge/models/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def display_name(self):

@cached_property
def has_any_solves(self):
return self.submission_set.filter(result='AC', case_points__gte=F('case_total')).exists()
return self.submission_set.filter(is_archived=False, result='AC', case_points__gte=F('case_total')).exists()

@cached_property
def resolved_ace_theme(self):
Expand All @@ -243,15 +243,16 @@ def calculate_points(self, table=_pp_table):
from judge.models import Problem
public_problems = Problem.get_public_problems()
data = (
public_problems.filter(submission__user=self, submission__points__isnull=False)
public_problems.filter(submission__user=self, submission__is_archived=False,
submission__points__isnull=False)
.annotate(max_points=Max('submission__points')).order_by('-max_points')
.values_list('max_points', flat=True).filter(max_points__gt=0)
)
bonus_function = settings.DMOJ_PP_BONUS_FUNCTION
points = sum(data)
entries = min(len(data), len(table))
problems = (
public_problems.filter(submission__user=self, submission__result='AC',
public_problems.filter(submission__user=self, submission__is_archived=False, submission__result='AC',
submission__case_points__gte=F('submission__case_total'))
.values('id').distinct().count()
)
Expand Down
5 changes: 5 additions & 0 deletions judge/models/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class Submission(models.Model):
contest_object = models.ForeignKey('Contest', verbose_name=_('contest'), null=True, blank=True,
on_delete=models.SET_NULL, related_name='+', db_index=False)
locked_after = models.DateTimeField(verbose_name=_('submission lock'), null=True, blank=True)
is_archived = models.BooleanField(verbose_name=_('is archived'), default=False)

@classmethod
def result_class_from_code(cls, result, case_points, case_total):
Expand Down Expand Up @@ -129,6 +130,9 @@ def judge(self, *args, rejudge=False, force_judge=False, rejudge_user=None, **kw
revisions.set_comment('Rejudged')
revisions.add_to_revision(self)
judge_submission(self, *args, rejudge=rejudge, **kwargs)
elif not self.is_archived:
self.is_archived = True
self.save(update_fields=['is_archived'])

judge.alters_data = True

Expand Down Expand Up @@ -227,6 +231,7 @@ class Meta:
('view_all_submission', _('View all submission')),
('resubmit_other', _("Resubmit others' submission")),
('lock_submission', _('Change lock status of submission')),
('archive_submission', _('Archive any submission')),
)
verbose_name = _('submission')
verbose_name_plural = _('submissions')
Expand Down
2 changes: 2 additions & 0 deletions judge/performance_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ def get_pp_breakdown(user, start=0, end=settings.DMOJ_PP_ENTRIES):
INNER JOIN judge_submission ON (judge_problem.id = judge_submission.problem_id)
WHERE (judge_problem.is_public AND
NOT judge_problem.is_organization_private AND
NOT judge_submission.is_archived AND
judge_submission.points IS NOT NULL AND
judge_submission.user_id = %s)
GROUP BY judge_problem.id
HAVING MAX(judge_submission.points) > 0.0
) AS max_points_table
{join_type} judge_submission ON (
judge_submission.problem_id = max_points_table.problem_id AND
NOT judge_submission.is_archived AND
judge_submission.points = max_points_table.max_points AND
judge_submission.user_id = %s
)
Expand Down
12 changes: 7 additions & 5 deletions judge/tasks/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,25 @@
__all__ = ('apply_submission_filter', 'rejudge_problem_filter', 'rescore_problem')


def apply_submission_filter(queryset, id_range, languages, results):
def apply_submission_filter(queryset, id_range, languages, results, archive_locked):
if id_range:
start, end = id_range
queryset = queryset.filter(id__gte=start, id__lte=end)
if languages:
queryset = queryset.filter(language_id__in=languages)
if results:
queryset = queryset.filter(result__in=results)
queryset = queryset.exclude(locked_after__lt=timezone.now()) \
.exclude(status__in=Submission.IN_PROGRESS_GRADING_STATUS)
if not archive_locked:
queryset = queryset.exclude(locked_after__lt=timezone.now())
queryset = queryset.exclude(status__in=Submission.IN_PROGRESS_GRADING_STATUS)
return queryset


@shared_task(bind=True)
def rejudge_problem_filter(self, problem_id, id_range=None, languages=None, results=None, user_id=None):
def rejudge_problem_filter(self, problem_id, id_range=None, languages=None, results=None, archive_locked=None,
user_id=None):
queryset = Submission.objects.filter(problem_id=problem_id)
queryset = apply_submission_filter(queryset, id_range, languages, results)
queryset = apply_submission_filter(queryset, id_range, languages, results, archive_locked)
user = User.objects.get(id=user_id)

rejudged = 0
Expand Down
3 changes: 2 additions & 1 deletion judge/utils/problems.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ def user_completed_ids(profile):
key = 'user_complete:%d' % profile.id
result = cache.get(key)
if result is None:
result = set(Submission.objects.filter(user=profile, result='AC', case_points__gte=F('case_total'))
result = set(Submission.objects
.filter(user=profile, is_archived=False, result='AC', case_points__gte=F('case_total'))
.values_list('problem_id', flat=True).distinct())
cache.set(key, result, 86400)
return result
Expand Down
3 changes: 2 additions & 1 deletion judge/views/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,8 @@ def get_normal_queryset(self):
queryset = Problem.objects.filter(filter).select_related('group').defer('description', 'summary')
if self.profile is not None and self.hide_solved:
queryset = queryset.exclude(id__in=Submission.objects
.filter(user=self.profile, result='AC', case_points__gte=F('case_total'))
.filter(user=self.profile, is_archived=False,
result='AC', case_points__gte=F('case_total'))
.values_list('problem_id', flat=True))
if self.show_types:
queryset = queryset.prefetch_related('types')
Expand Down
15 changes: 9 additions & 6 deletions judge/views/problem_manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,17 @@ def perform_action(self):
except ValueError:
return HttpResponseBadRequest()

return self.generate_response(id_range, languages, self.request.POST.getlist('result'))
archive_locked = self.request.POST.get('archive_locked', 'off') == 'on'

def generate_response(self, id_range, languages, results):
return self.generate_response(id_range, languages, self.request.POST.getlist('result'), archive_locked)

def generate_response(self, id_range, languages, results, archive_locked):
raise NotImplementedError()


class RejudgeSubmissionsView(BaseRejudgeSubmissionsView):
def generate_response(self, id_range, languages, results):
status = rejudge_problem_filter.delay(self.object.id, id_range, languages, results,
def generate_response(self, id_range, languages, results, archive_locked):
status = rejudge_problem_filter.delay(self.object.id, id_range, languages, results, archive_locked,
user_id=self.request.user.id)
return redirect_to_task_status(
status, message=_('Rejudging selected submissions for %s...') % (self.object.name,),
Expand All @@ -99,8 +101,9 @@ def generate_response(self, id_range, languages, results):


class PreviewRejudgeSubmissionsView(BaseRejudgeSubmissionsView):
def generate_response(self, id_range, languages, results):
queryset = apply_submission_filter(self.object.submission_set.all(), id_range, languages, results)
def generate_response(self, id_range, languages, results, archive_locked):
queryset = apply_submission_filter(self.object.submission_set.all(),
id_range, languages, results, archive_locked)
return HttpResponse(str(queryset.count()))


Expand Down
4 changes: 2 additions & 2 deletions judge/views/ranked_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ def get_queryset(self):
FROM (
SELECT sub.user_id AS uid, MAX(sub.points) AS points
FROM judge_submission AS sub {contest_join}
WHERE sub.problem_id = %s AND {points} > 0 {constraint}
WHERE sub.problem_id = %s AND NOT sub.is_archived AND {points} > 0 {constraint}
GROUP BY sub.user_id
) AS highscore STRAIGHT_JOIN (
SELECT sub.user_id AS uid, sub.points, MIN(sub.time) as time
FROM judge_submission AS sub {contest_join}
WHERE sub.problem_id = %s AND {points} > 0 {constraint}
WHERE sub.problem_id = %s AND NOT sub.is_archived AND {points} > 0 {constraint}
GROUP BY sub.user_id, {points}
) AS fastest ON (highscore.uid = fastest.uid AND highscore.points = fastest.points)
STRAIGHT_JOIN judge_submission AS sub
Expand Down
2 changes: 1 addition & 1 deletion judge/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ class UserProblemsPage(UserPage):
def get_context_data(self, **kwargs):
context = super(UserProblemsPage, self).get_context_data(**kwargs)

result = Submission.objects.filter(user=self.object, points__gt=0, problem__is_public=True,
result = Submission.objects.filter(user=self.object, is_archived=False, points__gt=0, problem__is_public=True,
problem__is_organization_private=False) \
.exclude(problem__in=self.get_completed_problems() if self.hide_solved else []) \
.values('problem__id', 'problem__code', 'problem__name', 'problem__points', 'problem__group__full_name') \
Expand Down
8 changes: 8 additions & 0 deletions resources/submission.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
border-top: none;
}

&.submission-archived {
opacity: 0.6;

&:hover {
opacity: 1;
}
}

> div {
padding: 7px 5px;
vertical-align: middle;
Expand Down
6 changes: 6 additions & 0 deletions templates/problem/manage_submission.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ <h3>{{ _('Rejudge Submissions') }}</h3>
{% endfor %}
</select>
</div>
<div class="control-group">
<label>
{{ _('Archive locked submissions:') }}
<input id="by-archive-locked-check" type="checkbox" name="archive_locked" checked>
</label>
</div>
<a id="rejudge-selected" class="unselectable button full" href="#">
{{ _('Rejudge selected submissions') }}
</a>
Expand Down
2 changes: 1 addition & 1 deletion templates/submission/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ <h3>{{ _('Statistics') }} <i class="fa fa-pie-chart"></i></h3>
<div id="submissions-table">
{% set profile_id = request.profile.id if request.user.is_authenticated else 0 %}
{% for submission in submissions %}
<div class="submission-row" id="{{ submission.id }}">
<div class="submission-row{% if submission.is_archived %} submission-archived{% endif %}" id="{{ submission.id }}">
{% with problem_name=show_problem and (submission.problem.i18n_name or submission.problem.name) %}
{% include "submission/row.html" %}
{% endwith %}
Expand Down
3 changes: 3 additions & 0 deletions templates/submission/row.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
<i title="{{ submission.contest_object.name }}" class="fa fa-dot-circle-o"></i>
</a>
{% endif %}
{% if submission.is_archived %}
<i class="fa fa-archive"></i>
{% endif %}
</div>
</div>

Expand Down

0 comments on commit 5950d36

Please sign in to comment.