From 435839e6b3ec564fe228acb55066c99eca7c1203 Mon Sep 17 00:00:00 2001 From: Keenan Gugeler Date: Fri, 11 Mar 2022 13:50:19 -0500 Subject: [PATCH] Add batch dependencies to site data interface --- .../0145_site_data_batch_prerequisites.py | 18 +++++ judge/models/problem_data.py | 4 +- judge/utils/problem_data.py | 18 +++++ judge/views/problem_data.py | 5 +- templates/problem/data.html | 69 ++++++++++++------- 5 files changed, 85 insertions(+), 29 deletions(-) create mode 100644 judge/migrations/0145_site_data_batch_prerequisites.py diff --git a/judge/migrations/0145_site_data_batch_prerequisites.py b/judge/migrations/0145_site_data_batch_prerequisites.py new file mode 100644 index 0000000000..fadcb26fd1 --- /dev/null +++ b/judge/migrations/0145_site_data_batch_prerequisites.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.21 on 2024-01-06 04:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0144_submission_index_cleanup'), + ] + + operations = [ + migrations.AddField( + model_name='problemtestcase', + name='batch_dependencies', + field=models.TextField(blank=True, help_text='batch dependencies as a comma-separated list of integers', verbose_name='batch dependencies'), + ), + ] diff --git a/judge/models/problem_data.py b/judge/models/problem_data.py index a00016a510..5b182e523c 100644 --- a/judge/models/problem_data.py +++ b/judge/models/problem_data.py @@ -93,4 +93,6 @@ class ProblemTestCase(models.Model): output_limit = models.IntegerField(verbose_name=_('output limit length'), blank=True, null=True) checker = models.CharField(max_length=10, verbose_name=_('checker'), choices=CHECKERS, blank=True) checker_args = models.TextField(verbose_name=_('checker arguments'), blank=True, - help_text=_('Checker arguments as a JSON object.')) + help_text=_('checker arguments as a JSON object')) + batch_dependencies = models.TextField(verbose_name=_('batch dependencies'), blank=True, + help_text=_('batch dependencies as a comma-separated list of integers')) diff --git a/judge/utils/problem_data.py b/judge/utils/problem_data.py index ce59d29adb..b2fe1e2600 100644 --- a/judge/utils/problem_data.py +++ b/judge/utils/problem_data.py @@ -57,6 +57,7 @@ def __init__(self, problem, data, cases, files): def make_init(self): cases = [] batch = None + batch_count = 0 def end_batch(): if not batch['batched']: @@ -109,14 +110,31 @@ def make_checker(case): case.save(update_fields=('checker_args', 'is_pretest')) (batch['batched'] if batch else cases).append(data) elif case.type == 'S': + batch_count += 1 if batch: end_batch() if case.points is None: raise ProblemDataError(_('Batch start case #%d requires points.') % i) + dependencies = [] + if case.batch_dependencies.strip(): + try: + dependencies = list(map(int, case.batch_dependencies.split(','))) + except ValueError: + raise ProblemDataError( + _('Dependencies must be a comma-separated list of integers for batch start case #%d.') % i, + ) + for batch_number in dependencies: + if batch_number >= batch_count: + raise ProblemDataError( + _('Dependencies must depend on previous batches for batch start case #%d.') % i, + ) + elif batch_number < 1: + raise ProblemDataError(_('Dependencies must be positive for batch start case #%d.') % i) batch = { 'points': case.points, 'batched': [], 'is_pretest': case.is_pretest, + 'dependencies': dependencies, } if case.generator_args: batch['generator_args'] = case.generator_args.splitlines() diff --git a/judge/views/problem_data.py b/judge/views/problem_data.py index f484ce12f2..e29048b01c 100644 --- a/judge/views/problem_data.py +++ b/judge/views/problem_data.py @@ -75,10 +75,11 @@ class ProblemCaseForm(ModelForm): class Meta: model = ProblemTestCase - fields = ('order', 'type', 'input_file', 'output_file', 'points', - 'is_pretest', 'output_limit', 'output_prefix', 'checker', 'checker_args', 'generator_args') + fields = ('order', 'type', 'input_file', 'output_file', 'points', 'is_pretest', 'output_limit', + 'output_prefix', 'checker', 'checker_args', 'generator_args', 'batch_dependencies') widgets = { 'generator_args': HiddenInput, + 'batch_dependencies': HiddenInput, 'type': Select(attrs={'style': 'width: 100%'}), 'points': NumberInput(attrs={'style': 'width: 4em'}), 'output_prefix': NumberInput(attrs={'style': 'width: 4.5em'}), diff --git a/templates/problem/data.html b/templates/problem/data.html index d58067338b..8f2acdc9a8 100644 --- a/templates/problem/data.html +++ b/templates/problem/data.html @@ -125,37 +125,42 @@ .find('input[id$=points], input[id$=pretest]').toggle(!disabled); }).change(); - var tooltip_classes = 'tooltipped tooltipped-s'; - $tr.find('a.edit-generator-args').mouseover(function () { - switch ($tr.attr('data-type')) { - case 'C': - case 'S': - var $this = $(this).addClass(tooltip_classes); - $this.attr('aria-label', $this.prev().val() || '(none)'); - } - }).mouseout(function () { - $(this).removeClass(tooltip_classes).removeAttr('aria-label'); - }).featherlight($('.generator-args-editor'), { - beforeOpen: function () { + function addToolTip(linkClass, editorClass) { + var tooltip_classes = 'tooltipped tooltipped-s'; + $tr.find(linkClass).mouseover(function () { switch ($tr.attr('data-type')) { case 'C': case 'S': + var $this = $(this).addClass(tooltip_classes); + $this.attr('aria-label', $this.prev().val() || '(none)'); + } + }).mouseout(function () { + $(this).removeClass(tooltip_classes).removeAttr('aria-label'); + }).featherlight($(editorClass), { + beforeOpen: function () { + switch ($tr.attr('data-type')) { + case 'C': + case 'S': return true; - default: + default: return false; - } - }, - afterOpen: function () { - var $input = this.$currentTarget.prev(); - this.$instance.find('.generator-args-editor') + } + }, + afterOpen: function () { + var $input = this.$currentTarget.prev(); + this.$instance.find(editorClass) .find('textarea').val($input.val()).end() .find('.button').click(function () { - $input.val($(this).prev().val()); - $.featherlight.current().close(); - }).end() + $input.val($(this).prev().val()); + $.featherlight.current().close(); + }).end() .show(); - } - }); + } + }); + } + + addToolTip('a.edit-generator-args', '.generator-args-editor'); + addToolTip('a.edit-batch-dependencies', '.batch-dependencies-editor'); checker_precision($tr.find('select[id$=checker]')); }).find('tbody:first').find('tr').each(function () { @@ -300,18 +305,18 @@ cursor: move; } - .edit-generator-args { + .edit-generator-args, .edit-batch-dependencies { position: relative; } - .generator-args-editor textarea { + .generator-args-editor textarea, .batch-dependencies-editor textarea { display: block; width: 100%; margin-bottom: 0.5em; height: 8em; } - .generator-args-editor .button { + .generator-args-editor .button, .batch-dependencies-editor .button { display: block; float: right; } @@ -370,6 +375,10 @@ {{ _('Generator args') }} + @@ -384,6 +393,7 @@ + {% if cases_formset.can_delete %} {% endif %} @@ -424,6 +434,12 @@ {{ _('Edit') }} + {% if cases_formset.can_delete %} {% endif %} @@ -435,4 +451,5 @@ {{ _('Add new case') }} + {% endblock %}
{{ _('Output limit') }} {{ _('Checker') }} {{ _('Generator args') }}{{ _('Batch Dependencies') }}{{ _('Delete?') }} {{ form.batch_dependencies.errors }}{{ form.batch_dependencies }} + + + {{ _('Edit') }} + + {{ form.DELETE }}