2020-01-21 06:35:58 +00:00
|
|
|
import errno
|
|
|
|
import os
|
2021-06-01 20:34:42 +00:00
|
|
|
from zipfile import BadZipFile, ZipFile
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2020-01-24 02:32:46 +00:00
|
|
|
from django.core.validators import FileExtensionValidator
|
2021-06-01 20:34:42 +00:00
|
|
|
from django.core.cache import cache
|
2020-01-21 06:35:58 +00:00
|
|
|
from django.db import models
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
2021-06-01 20:34:42 +00:00
|
|
|
from judge.utils.problem_data import ProblemDataStorage, get_file_cachekey
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2022-05-14 17:57:27 +00:00
|
|
|
__all__ = [
|
|
|
|
"problem_data_storage",
|
|
|
|
"problem_directory_file",
|
|
|
|
"ProblemData",
|
|
|
|
"ProblemTestCase",
|
|
|
|
"CHECKERS",
|
|
|
|
]
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
problem_data_storage = ProblemDataStorage()
|
|
|
|
|
|
|
|
|
2022-08-31 03:50:08 +00:00
|
|
|
def problem_directory_file_helper(code, filename):
|
2020-01-21 06:35:58 +00:00
|
|
|
return os.path.join(code, os.path.basename(filename))
|
|
|
|
|
|
|
|
|
|
|
|
def problem_directory_file(data, filename):
|
2022-08-31 03:50:08 +00:00
|
|
|
return problem_directory_file_helper(data.problem.code, filename)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
CHECKERS = (
|
2022-05-14 17:57:27 +00:00
|
|
|
("standard", _("Standard")),
|
|
|
|
("floats", _("Floats")),
|
|
|
|
("floatsabs", _("Floats (absolute)")),
|
|
|
|
("floatsrel", _("Floats (relative)")),
|
|
|
|
("rstripped", _("Non-trailing spaces")),
|
|
|
|
("sorted", _("Unordered")),
|
|
|
|
("identical", _("Byte identical")),
|
|
|
|
("linecount", _("Line-by-line")),
|
|
|
|
("custom", _("Custom checker (PY)")),
|
|
|
|
("customval", _("Custom validator (CPP)")),
|
|
|
|
("interact", _("Interactive")),
|
2022-06-22 07:28:34 +00:00
|
|
|
("testlib", _("Testlib")),
|
2020-01-21 06:35:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class ProblemData(models.Model):
|
2022-05-14 17:57:27 +00:00
|
|
|
problem = models.OneToOneField(
|
|
|
|
"Problem",
|
|
|
|
verbose_name=_("problem"),
|
|
|
|
related_name="data_files",
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
)
|
|
|
|
zipfile = models.FileField(
|
|
|
|
verbose_name=_("data zip file"),
|
|
|
|
storage=problem_data_storage,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
upload_to=problem_directory_file,
|
|
|
|
)
|
|
|
|
generator = models.FileField(
|
|
|
|
verbose_name=_("generator file"),
|
|
|
|
storage=problem_data_storage,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
upload_to=problem_directory_file,
|
|
|
|
)
|
|
|
|
output_prefix = models.IntegerField(
|
|
|
|
verbose_name=_("output prefix length"), blank=True, null=True
|
|
|
|
)
|
|
|
|
output_limit = models.IntegerField(
|
|
|
|
verbose_name=_("output limit length"), blank=True, null=True
|
|
|
|
)
|
|
|
|
feedback = models.TextField(
|
|
|
|
verbose_name=_("init.yml generation feedback"), blank=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"),
|
|
|
|
)
|
|
|
|
custom_checker = models.FileField(
|
|
|
|
verbose_name=_("custom checker file"),
|
|
|
|
storage=problem_data_storage,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
upload_to=problem_directory_file,
|
|
|
|
validators=[FileExtensionValidator(allowed_extensions=["py"])],
|
|
|
|
)
|
|
|
|
custom_validator = models.FileField(
|
|
|
|
verbose_name=_("custom validator file"),
|
|
|
|
storage=problem_data_storage,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
upload_to=problem_directory_file,
|
|
|
|
validators=[FileExtensionValidator(allowed_extensions=["cpp"])],
|
|
|
|
)
|
|
|
|
interactive_judge = models.FileField(
|
|
|
|
verbose_name=_("interactive judge"),
|
|
|
|
storage=problem_data_storage,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
upload_to=problem_directory_file,
|
|
|
|
validators=[FileExtensionValidator(allowed_extensions=["cpp"])],
|
|
|
|
)
|
2022-06-02 04:59:46 +00:00
|
|
|
fileio_input = models.TextField(
|
2022-06-02 05:20:45 +00:00
|
|
|
verbose_name=_("input file name"),
|
|
|
|
blank=True,
|
|
|
|
null=True,
|
|
|
|
help_text=_("Leave empty for stdin"),
|
2022-06-02 04:59:46 +00:00
|
|
|
)
|
|
|
|
fileio_output = models.TextField(
|
2022-06-02 05:20:45 +00:00
|
|
|
verbose_name=_("output file name"),
|
|
|
|
blank=True,
|
|
|
|
null=True,
|
|
|
|
help_text=_("Leave empty for stdout"),
|
2022-06-02 04:59:46 +00:00
|
|
|
)
|
2022-05-14 17:57:27 +00:00
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
__original_zipfile = None
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super(ProblemData, self).__init__(*args, **kwargs)
|
|
|
|
self.__original_zipfile = self.zipfile
|
|
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
2022-05-08 01:06:39 +00:00
|
|
|
# Delete caches
|
|
|
|
if self.__original_zipfile:
|
2022-05-14 17:57:27 +00:00
|
|
|
try:
|
2021-06-01 20:34:42 +00:00
|
|
|
files = ZipFile(self.__original_zipfile.path).namelist()
|
|
|
|
for file in files:
|
2022-05-14 17:57:27 +00:00
|
|
|
cache_key = "problem_archive:%s:%s" % (
|
|
|
|
self.problem.code,
|
|
|
|
get_file_cachekey(file),
|
|
|
|
)
|
2021-06-01 20:34:42 +00:00
|
|
|
cache.delete(cache_key)
|
|
|
|
except BadZipFile:
|
|
|
|
pass
|
2022-05-08 01:06:39 +00:00
|
|
|
if self.zipfile != self.__original_zipfile and self.__original_zipfile:
|
2020-01-21 06:35:58 +00:00
|
|
|
self.__original_zipfile.delete(save=False)
|
|
|
|
return super(ProblemData, self).save(*args, **kwargs)
|
|
|
|
|
|
|
|
def has_yml(self):
|
2022-05-14 17:57:27 +00:00
|
|
|
return problem_data_storage.exists("%s/init.yml" % self.problem.code)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def _update_code(self, original, new):
|
|
|
|
try:
|
|
|
|
problem_data_storage.rename(original, new)
|
|
|
|
except OSError as e:
|
|
|
|
if e.errno != errno.ENOENT:
|
|
|
|
raise
|
|
|
|
if self.zipfile:
|
2022-10-07 17:17:07 +00:00
|
|
|
self.zipfile.name = problem_directory_file_helper(new, self.zipfile.name)
|
2020-01-21 06:35:58 +00:00
|
|
|
if self.generator:
|
2022-10-07 17:17:07 +00:00
|
|
|
self.generator.name = problem_directory_file_helper(
|
|
|
|
new, self.generator.name
|
|
|
|
)
|
2020-01-24 02:32:46 +00:00
|
|
|
if self.custom_checker:
|
2022-10-07 17:17:07 +00:00
|
|
|
self.custom_checker.name = problem_directory_file_helper(
|
2022-05-14 17:57:27 +00:00
|
|
|
new, self.custom_checker.name
|
|
|
|
)
|
2020-03-17 06:11:03 +00:00
|
|
|
if self.custom_checker:
|
2022-10-07 17:17:07 +00:00
|
|
|
self.custom_checker.name = problem_directory_file_helper(
|
2022-05-14 17:57:27 +00:00
|
|
|
new, self.custom_checker.name
|
|
|
|
)
|
2020-03-17 06:11:03 +00:00
|
|
|
if self.custom_validator:
|
2022-10-07 17:17:07 +00:00
|
|
|
self.custom_validator.name = problem_directory_file_helper(
|
2022-05-14 17:57:27 +00:00
|
|
|
new, self.custom_validator.name
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
self.save()
|
2022-05-14 17:57:27 +00:00
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
_update_code.alters_data = True
|
|
|
|
|
|
|
|
|
|
|
|
class ProblemTestCase(models.Model):
|
2022-05-14 17:57:27 +00:00
|
|
|
dataset = models.ForeignKey(
|
|
|
|
"Problem",
|
|
|
|
verbose_name=_("problem data set"),
|
|
|
|
related_name="cases",
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
)
|
|
|
|
order = models.IntegerField(verbose_name=_("case position"))
|
|
|
|
type = models.CharField(
|
|
|
|
max_length=1,
|
|
|
|
verbose_name=_("case type"),
|
|
|
|
choices=(
|
|
|
|
("C", _("Normal case")),
|
|
|
|
("S", _("Batch start")),
|
|
|
|
("E", _("Batch end")),
|
|
|
|
),
|
|
|
|
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
|
|
|
|
)
|
|
|
|
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?"))
|
|
|
|
output_prefix = models.IntegerField(
|
|
|
|
verbose_name=_("output prefix length"), blank=True, null=True
|
|
|
|
)
|
|
|
|
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"),
|
|
|
|
)
|