Skip to content

Commit

Permalink
Implement ability to choose judge to submit on for admins; #79 (#1325)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ninjaclasher authored Apr 20, 2020
1 parent ff96113 commit 2f64355
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 32 deletions.
3 changes: 2 additions & 1 deletion judge/bridge/django_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions judge/bridge/judge_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
22 changes: 15 additions & 7 deletions judge/bridge/judge_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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'))
Expand All @@ -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)
11 changes: 9 additions & 2 deletions judge/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 _

Expand Down Expand Up @@ -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']
Expand Down
3 changes: 2 additions & 1 deletion judge/judgeapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions judge/models/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 9 additions & 3 deletions judge/views/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,14 @@ def problem_submit(request, problem, submission=None):
return HttpResponseForbidden('<h1>Do you want me to ban you?</h1>')
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)
Expand Down Expand Up @@ -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:
Expand All @@ -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')
Expand Down
6 changes: 5 additions & 1 deletion resources/problem.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
36 changes: 23 additions & 13 deletions templates/problem/submit.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

{% block js_media %}
<script type="text/javascript" src="{{ ACE_URL }}/ace.js"></script>
{{ form.media.js }}
{% compress js %}
{{ form.media.js }}
<script type="text/javascript">
$(function () {
function format(state) {
Expand Down Expand Up @@ -81,6 +81,13 @@
var dropdown = $('.select2-dropdown');
if (!$('#result-version-info').length)
dropdown.append($("<span id=\"result-version-info\">"));
dropdown.attr('id', 'language-select2');
});

$('#id_judge').on('select2:open', function (evt) {
var dropdown = $('.select2-dropdown');
$('#result-version-info').remove();
dropdown.attr('id', 'judge-select2');
});

$('#id_language').change(function () {
Expand Down Expand Up @@ -121,8 +128,8 @@
{% endblock %}

{% block media %}
{{ form.media.css }}
{% compress css %}
{{ form.media.css }}
<style media="screen">
#submit-wrapper {
margin-top: 0.7em;
Expand All @@ -145,42 +152,42 @@
text-align: right;
}

.select2-results__message {
#language-select2 .select2-results__message {
white-space: nowrap
}

.select2-dropdown--above {
#language-select2 .select2-dropdown--above {
display: flex;
flex-direction: column-reverse;
}

.select2-results__option {
#language-select2 .select2-results__option {
color: #757575 !important;
background: white !important;
}

.select2-results__option--highlighted {
#language-select2 .select2-results__option--highlighted {
text-decoration: underline;
}

.select2-results__option[aria-selected=true] {
#language-select2 .select2-results__option[aria-selected=true] {
font-weight: bold;
color: black !important;
}

.select2-results__option {
#language-select2 .select2-results__option {
padding: 4px 0px;
}

.select2-results__options {
#language-select2 .select2-results__options {
overflow-y: visible !important;
}

.select2-results__option {
#language-select2 .select2-results__option {
break-inside: avoid-column;
}

.select2-results {
#language-select2 .select2-results {
-webkit-columns: 10 7em;
-moz-columns: 10 7em;
columns: 10 7em;
Expand Down Expand Up @@ -253,8 +260,11 @@
{% if no_judges %}
<span style="color: red">{{ _('No judge is available for this problem.') }}</span>
{% else %}
<input type="submit" value="{{ _('Submit!') }}" class="button"
{% if request.in_contest and submission_limit and not submissions_left %}disabled{% endif %}>
<div class="submit-bar">
{{ form.judge }}
<input type="submit" value="{{ _('Submit!') }}" class="button"
{% if request.in_contest and submission_limit and not submissions_left %}disabled{% endif %}>
</div>
{% endif %}
</form>
{% endblock %}

0 comments on commit 2f64355

Please sign in to comment.