From 2f643551bf370536c5651d42a5faae3ed24bf5a6 Mon Sep 17 00:00:00 2001 From: Evan Zhang Date: Sun, 19 Apr 2020 20:10:57 -0400 Subject: [PATCH] Implement ability to choose judge to submit on for admins; #79 (#1325) --- judge/bridge/django_handler.py | 3 ++- judge/bridge/judge_handler.py | 4 ++-- judge/bridge/judge_list.py | 22 ++++++++++++++------- judge/forms.py | 11 +++++++++-- judge/judgeapi.py | 3 ++- judge/models/submission.py | 4 ++-- judge/views/problem.py | 12 +++++++++--- resources/problem.scss | 6 +++++- templates/problem/submit.html | 36 ++++++++++++++++++++++------------ 9 files changed, 69 insertions(+), 32 deletions(-) diff --git a/judge/bridge/django_handler.py b/judge/bridge/django_handler.py index c8a7e9717c..b284b68f78 100644 --- a/judge/bridge/django_handler.py +++ b/judge/bridge/django_handler.py @@ -37,10 +37,11 @@ def on_submission(self, data): problem = data['problem-id'] language = data['language'] source = data['source'] + judge_id = data['judge-id'] priority = data['priority'] if not self.judges.check_priority(priority): return {'name': 'bad-request'} - self.judges.judge(id, problem, language, source, priority) + self.judges.judge(id, problem, language, source, judge_id, priority) return {'name': 'submission-received', 'submission-id': id} def on_termination(self, data): diff --git a/judge/bridge/judge_handler.py b/judge/bridge/judge_handler.py index d088d4320e..63299792cd 100644 --- a/judge/bridge/judge_handler.py +++ b/judge/bridge/judge_handler.py @@ -166,8 +166,8 @@ def on_handshake(self, packet): threading.Thread(target=self._ping_thread).start() self._connected() - def can_judge(self, problem, executor): - return problem in self.problems and executor in self.executors + def can_judge(self, problem, executor, judge_id=None): + return problem in self.problems and executor in self.executors and (not judge_id or self.name == judge_id) @property def working(self): diff --git a/judge/bridge/judge_list.py b/judge/bridge/judge_list.py index 151b946a8c..1954ced8f8 100644 --- a/judge/bridge/judge_list.py +++ b/judge/bridge/judge_list.py @@ -29,8 +29,8 @@ def _handle_free_judge(self, judge): node = self.queue.first while node: if not isinstance(node.value, PriorityMarker): - id, problem, language, source = node.value - if judge.can_judge(problem, language): + id, problem, language, source, judge_id = node.value + if judge.can_judge(problem, language, judge_id): self.submission_map[id] = judge logger.info('Dispatched queued submission %d: %s', id, judge.name) try: @@ -99,15 +99,20 @@ def abort(self, submission): def check_priority(self, priority): return 0 <= priority < self.priorities - def judge(self, id, problem, language, source, priority): + def judge(self, id, problem, language, source, judge_id, priority): with self.lock: if id in self.submission_map or id in self.node_map: # Already judging, don't queue again. This can happen during batch rejudges, rejudges should be # idempotent. return - candidates = [judge for judge in self.judges if not judge.working and judge.can_judge(problem, language)] - logger.info('Free judges: %d', len(candidates)) + candidates = [ + judge for judge in self.judges if not judge.working and judge.can_judge(problem, language, judge_id) + ] + if judge_id: + logger.info('Specified judge %s is%savailable', judge_id, ' ' if candidates else ' not ') + else: + logger.info('Free judges: %d', len(candidates)) if candidates: # Schedule the submission on the judge reporting least load. judge = min(candidates, key=attrgetter('load')) @@ -118,7 +123,10 @@ def judge(self, id, problem, language, source, priority): except Exception: logger.exception('Failed to dispatch %d (%s, %s) to %s', id, problem, language, judge.name) self.judges.discard(judge) - return self.judge(id, problem, language, source, priority) + return self.judge(id, problem, language, source, judge_id, priority) else: - self.node_map[id] = self.queue.insert((id, problem, language, source), self.priority[priority]) + self.node_map[id] = self.queue.insert( + (id, problem, language, source, judge_id), + self.priority[priority], + ) logger.info('Queued submission: %d', id) diff --git a/judge/forms.py b/judge/forms.py index 87135ccd28..ad7a761c81 100644 --- a/judge/forms.py +++ b/judge/forms.py @@ -7,7 +7,7 @@ from django.core.exceptions import ValidationError from django.core.validators import RegexValidator from django.db.models import Q -from django.forms import CharField, Form, ModelForm +from django.forms import CharField, ChoiceField, Form, ModelForm from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ @@ -69,13 +69,20 @@ def __init__(self, *args, **kwargs): class ProblemSubmitForm(ModelForm): source = CharField(max_length=65536, widget=AceWidget(theme='twilight', no_ace_media=True)) + judge = ChoiceField(choices=(), widget=forms.HiddenInput(), required=False) - def __init__(self, *args, **kwargs): + def __init__(self, *args, judge_choices=(), **kwargs): super(ProblemSubmitForm, self).__init__(*args, **kwargs) self.fields['language'].empty_label = None self.fields['language'].label_from_instance = attrgetter('display_name') self.fields['language'].queryset = Language.objects.filter(judges__online=True).distinct() + if judge_choices: + self.fields['judge'].widget = Select2Widget( + attrs={'style': 'width: 150px', 'data-placeholder': _('Any judge')}, + ) + self.fields['judge'].choices = judge_choices + class Meta: model = Submission fields = ['language'] diff --git a/judge/judgeapi.py b/judge/judgeapi.py index e34bdb2d4d..7148de9957 100644 --- a/judge/judgeapi.py +++ b/judge/judgeapi.py @@ -48,7 +48,7 @@ def judge_request(packet, reply=True): return result -def judge_submission(submission, rejudge, batch_rejudge=False): +def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=None): from .models import ContestSubmission, Submission, SubmissionTestCase CONTEST_SUBMISSION_PRIORITY = 0 @@ -88,6 +88,7 @@ def judge_submission(submission, rejudge, batch_rejudge=False): 'problem-id': submission.problem.code, 'language': submission.language.key, 'source': submission.source.source, + 'judge-id': judge_id, 'priority': BATCH_REJUDGE_PRIORITY if batch_rejudge else REJUDGE_PRIORITY if rejudge else priority, }) except BaseException: diff --git a/judge/models/submission.py b/judge/models/submission.py index 41338af730..8c40b35672 100644 --- a/judge/models/submission.py +++ b/judge/models/submission.py @@ -113,8 +113,8 @@ def short_status(self): def long_status(self): return Submission.USER_DISPLAY_CODES.get(self.short_status, '') - def judge(self, rejudge=False, batch_rejudge=False): - judge_submission(self, rejudge, batch_rejudge) + def judge(self, *args, **kwargs): + judge_submission(self, *args, **kwargs) judge.alters_data = True diff --git a/judge/views/problem.py b/judge/views/problem.py index 649819eaf6..1609d14ce6 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -536,8 +536,14 @@ def problem_submit(request, problem, submission=None): return HttpResponseForbidden('

Do you want me to ban you?

') raise Http404() + if problem.is_editable_by(request.user): + judge_choices = tuple(Judge.objects.filter(online=True, problems=problem).values_list('name', 'name')) + else: + judge_choices = () + if request.method == 'POST': - form = ProblemSubmitForm(request.POST, instance=Submission(user=profile, problem=problem)) + form = ProblemSubmitForm(request.POST, judge_choices=judge_choices, + instance=Submission(user=profile, problem=problem)) if form.is_valid(): if (not request.user.has_perm('judge.spam_submission') and Submission.objects.filter(user=profile, was_rejudged=False) @@ -579,7 +585,7 @@ def problem_submit(request, problem, submission=None): # Save a query model.source = source - model.judge(rejudge=False) + model.judge(rejudge=False, judge_id=form.cleaned_data['judge']) return HttpResponseRedirect(reverse('submission_status', args=[str(model.id)])) else: @@ -595,7 +601,7 @@ def problem_submit(request, problem, submission=None): initial['language'] = sub.language except ValueError: raise Http404() - form = ProblemSubmitForm(initial=initial) + form = ProblemSubmitForm(judge_choices=judge_choices, initial=initial) form_data = initial form.fields['language'].queryset = ( problem.usable_languages.order_by('name', 'key') diff --git a/resources/problem.scss b/resources/problem.scss index 158b58f4aa..256a9eaf87 100644 --- a/resources/problem.scss +++ b/resources/problem.scss @@ -222,9 +222,13 @@ ul.problem-list { box-sizing: border-box; .button { - float: right; + display: inline-block !important; padding: 6px 12px; } + + .submit-bar { + float: right; + } } @media (max-width: 550px) { diff --git a/templates/problem/submit.html b/templates/problem/submit.html index 1e59f46843..c3219c991e 100644 --- a/templates/problem/submit.html +++ b/templates/problem/submit.html @@ -2,8 +2,8 @@ {% block js_media %} + {{ form.media.js }} {% compress js %} - {{ form.media.js }}