From 50d0d703cf9ac295b82247f46090184b271ee9d7 Mon Sep 17 00:00:00 2001 From: FherStk Date: Sun, 18 Aug 2024 08:29:49 +0000 Subject: [PATCH] New "explanation" field has been added to the problem test-cases settings, the data included in the 'xpl' file will be displayed at the problem statement after its related output. The test-case information on the problem statement has been arranged by test-cases in order to make it more readable. --- .../migrations/0148_test_case_explanation.py | 18 +++++++ judge/models/problem_data.py | 53 +++++++++++++------ judge/utils/problem_data.py | 7 +++ judge/views/problem_data.py | 5 +- templates/problem/data.html | 5 ++ 5 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 judge/migrations/0148_test_case_explanation.py diff --git a/judge/migrations/0148_test_case_explanation.py b/judge/migrations/0148_test_case_explanation.py new file mode 100644 index 0000000000..d6865aadfa --- /dev/null +++ b/judge/migrations/0148_test_case_explanation.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.25 on 2024-08-18 07:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0147_infer_test_cases_from_zip'), + ] + + operations = [ + migrations.AddField( + model_name='problemtestcase', + name='explanation_file', + field=models.CharField(blank=True, null=True, default='', max_length=100, verbose_name='explanation file name'), + ), + ] diff --git a/judge/models/problem_data.py b/judge/models/problem_data.py index a30d559648..898c889a93 100644 --- a/judge/models/problem_data.py +++ b/judge/models/problem_data.py @@ -104,10 +104,7 @@ def setup_test_cases_content(self): for i, tc in enumerate([x for x in test_cases if x.is_pretest]): self.append_tescase_to_statement(zip, content, tc, i) - last = i - - if last > 0: - last += 1 + last = i + 1 for i, tc in enumerate([x for x in test_cases if not x.is_pretest]): self.append_tescase_to_statement(zip, content, tc, i + last) @@ -115,29 +112,32 @@ def setup_test_cases_content(self): self.test_cases_content = '\n'.join(content) def append_tescase_to_statement(self, zip, content, tc, i): - content.append(f'## Sample Input {i+1}') - content.append('') + content.append(f'## Test Case {i+1}') if tc.is_private: content.append('*Hidden: this is a private test case!* ') else: + content.append('### Input') content.append('```') - content.append(zip.read(tc.input_file).decode('utf-8')) + if tc.input_file != '': + content.append(zip.read(tc.input_file).decode('utf-8')) content.append('```') - content.append('') - content.append(f'## Sample Output {i+1}') - content.append('') - - if tc.is_private: - content.append('*Hidden: this is a private test case!* ') - - else: + content.append('') + content.append('### Output') content.append('```') - content.append(zip.read(tc.output_file).decode('utf-8')) + if tc.output_file != '': + content.append(zip.read(tc.output_file).decode('utf-8')) content.append('```') + if tc.explanation_file != '': + content.append('') + content.append('### Explanation') + + if tc.explanation_file != '': + content.append(zip.read(tc.explanation_file).decode('utf-8')) + content.append('') def infer_test_cases_from_zip(self): @@ -157,6 +157,22 @@ def infer_test_cases_from_zip(self): input = [x for x in files if '.in' in x or ('input' in x and '.' in x)] output = [x for x in files if '.out' in x or ('output' in x and '.' in x)] + # Not all explanations are mandatory, so there can be gaps! + input.sort() + output.sort() + explanation = [] + for i in range(len(input)): + in_file = input[i] + + xpl_file = in_file.replace('input', 'explanation') if 'input' in in_file else in_file.replace('in', 'xpl') + found = [x for x in files if xpl_file in x] + found.sort() + + if len(found) > 0: + explanation.append(found[0]) + else: + explanation.append('') + cases = [] for i in range(len(input)): list = ProblemTestCase.objects.filter(dataset_id=self.problem.pk, input_file=input[i], @@ -173,6 +189,7 @@ def infer_test_cases_from_zip(self): ptc.order = i ptc.input_file = input[i] ptc.output_file = output[i] + ptc.explanation_file = explanation[i] ptc.points = 0 cases.append(ptc) @@ -242,6 +259,9 @@ def _load_test_case_from_doc(self, doc, field, is_pretest): if test.get('out'): ptc.output_file = test['out'] + if test.get('xpl'): + ptc.explanation_file = test['xpl'] + if test.get('points'): ptc.points = test['points'] else: @@ -287,6 +307,7 @@ class ProblemTestCase(models.Model): default='C') input_file = models.CharField(max_length=100, verbose_name=_('input file name'), blank=True) output_file = models.CharField(max_length=100, verbose_name=_('output file name'), blank=True) + explanation_file = models.CharField(max_length=100, verbose_name=_('explanation file name'), blank=True) generator_args = models.TextField(verbose_name=_('generator arguments'), blank=True) points = models.IntegerField(verbose_name=_('point value'), blank=True, null=True) is_pretest = models.BooleanField(verbose_name=_('case is pretest?'), default=False) diff --git a/judge/utils/problem_data.py b/judge/utils/problem_data.py index a0660afd9d..f2e014b974 100644 --- a/judge/utils/problem_data.py +++ b/judge/utils/problem_data.py @@ -92,11 +92,16 @@ def make_checker(case): if case.output_file not in self.files: raise ProblemDataError(_('Output file for case %d does not exist: %s') % (i, case.output_file)) + if case.explanation_file != '' and case.explanation_file not in self.files: + raise ProblemDataError(_('Explanation file for case %d does not exist: %s') % + (i, case.explanation_file)) if case.input_file: data['in'] = case.input_file if case.output_file: data['out'] = case.output_file + if case.explanation_file: + data['xpl'] = case.explanation_file if case.points is not None: data['points'] = case.points if case.generator_args: @@ -151,6 +156,7 @@ def make_checker(case): case.checker_args = '' case.input_file = '' case.output_file = '' + case.explanation_file = '' case.save(update_fields=('checker_args', 'input_file', 'output_file')) elif case.type == 'E': if not batch: @@ -159,6 +165,7 @@ def make_checker(case): case.is_private = batch['is_private'] case.input_file = '' case.output_file = '' + case.explanation_file = '' case.generator_args = '' case.checker = '' case.checker_args = '' diff --git a/judge/views/problem_data.py b/judge/views/problem_data.py index 4aebc7a195..3086efa9e9 100644 --- a/judge/views/problem_data.py +++ b/judge/views/problem_data.py @@ -75,8 +75,9 @@ class ProblemCaseForm(ModelForm): class Meta: model = ProblemTestCase - fields = ('order', 'type', 'input_file', 'output_file', 'points', 'is_pretest', 'is_private', 'output_limit', - 'output_prefix', 'checker', 'checker_args', 'generator_args', 'batch_dependencies') + fields = ('order', 'type', 'input_file', 'output_file', 'explanation_file', 'points', 'is_pretest', + 'is_private', 'output_limit', 'output_prefix', 'checker', 'checker_args', 'generator_args', + 'batch_dependencies') widgets = { 'generator_args': HiddenInput, 'batch_dependencies': HiddenInput, diff --git a/templates/problem/data.html b/templates/problem/data.html index 3f2179fb4c..24ac226e43 100644 --- a/templates/problem/data.html +++ b/templates/problem/data.html @@ -404,6 +404,7 @@ {{ _('Type') }} {{ _('Input file') }} {{ _('Output file') }} + {{ _('Explanation file') }} {{ _('Points') }} {{ _('Pretest?') }} {{ _('Private?') }} @@ -439,6 +440,10 @@ form['output_file'].value() in valid_files) %} class="bad-file"{% endif %}> {{ form.output_file.errors }}{{ form.output_file }} + + {{ form.explanation_file.errors }}{{ form.explanation_file }} + {{ form.points.errors }}{{ form.points }} {{ form.is_pretest.errors }}{{ form.is_pretest }} {{ form.is_private.errors }}{{ form.is_private }}