From eab6da84d025c8aaeac39781692d8240257eb8c0 Mon Sep 17 00:00:00 2001 From: Mateusz Masiarz Date: Thu, 5 Dec 2024 17:50:15 +0100 Subject: [PATCH 01/10] Some `save_db()` code --- src/sio3pack/django/sinolpack/handler.py | 54 ++++++++++++++++++++- src/sio3pack/django/sinolpack/models.py | 60 +++++++++++++++++++++++- src/sio3pack/django/sinolpack/utils.py | 12 +++++ src/sio3pack/packages/sinolpack/enums.py | 10 ++-- src/sio3pack/packages/sinolpack/model.py | 17 +++++-- 5 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 src/sio3pack/django/sinolpack/utils.py diff --git a/src/sio3pack/django/sinolpack/handler.py b/src/sio3pack/django/sinolpack/handler.py index 0ae67e7..22cac48 100644 --- a/src/sio3pack/django/sinolpack/handler.py +++ b/src/sio3pack/django/sinolpack/handler.py @@ -1,9 +1,22 @@ -from sio3pack.django.sinolpack.models import SinolpackPackage +from typing import Type + +import yaml +from django.core.files import File +from django.db import transaction + +from sio3pack.django.sinolpack.models import SinolpackPackage, SinolpackConfig, SinolpackNameTranslation, \ + SinolpackModelSolution from sio3pack.packages.exceptions import PackageAlreadyExists from sio3pack.packages.package.django.handler import DjangoHandler class SinolpackDjangoHandler(DjangoHandler): + + def __init__(self, package: Type["Package"], problem_id: int): + super().__init__(package, problem_id) + self.db_package = None + + @transaction.atomic def save_to_db(self): """ Save the package to the database. @@ -11,7 +24,44 @@ def save_to_db(self): if SinolpackPackage.objects.filter(problem_id=self.problem_id).exists(): raise PackageAlreadyExists(self.problem_id) - SinolpackPackage.objects.create( + self.db_package = SinolpackPackage.objects.create( problem_id=self.problem_id, short_name=self.package.short_name, + full_name=self.package.full_name, ) + + self._save_config() + self._save_translated_titles() + self._save_model_solutions() + + def _save_config(self): + """ + Save the ``config.yml`` to the database. + """ + config = self.package.get_config() + SinolpackConfig.objects.create( + package=self.db_package, + config=yaml.dump(config), + ) + + def _save_translated_titles(self): + """ + Save the translated titles to the database. + """ + titles = self.package.get_titles() + for lang, title in titles.items(): + SinolpackNameTranslation.objects.create( + package=self.db_package, + language=lang, + name=title, + ) + + def _save_model_solutions(self): + for order, (kind, solution) in enumerate(self.package.get_model_solutions()): + instance = SinolpackModelSolution( + package=self.db_package, + name=solution.filename, + kind=kind, + order_key=order, + ) + instance.source_file.save(solution.filename, File(open(solution.path, "rb"))) diff --git a/src/sio3pack/django/sinolpack/models.py b/src/sio3pack/django/sinolpack/models.py index 961494c..1e57699 100644 --- a/src/sio3pack/django/sinolpack/models.py +++ b/src/sio3pack/django/sinolpack/models.py @@ -1,4 +1,15 @@ +import yaml +from django.conf import settings from django.db import models +from django.utils.translation import gettext_lazy as _ + +from sio3pack.django.sinolpack.utils import make_problem_filename +from sio3pack.packages.sinolpack.enums import ModelSolutionKind + +try: + from oioioi.filetracker.fields import FileField +except ImportError: + FileField = models.FileField class SinolpackPackage(models.Model): @@ -7,4 +18,51 @@ class SinolpackPackage(models.Model): """ problem_id = models.IntegerField() - short_name = models.CharField(max_length=100) + short_name = models.CharField(max_length=30, verbose_name=_("short name")) + full_name = models.CharField(max_length=255, verbose_name=_("full name")) + + +class SinolpackConfig(models.Model): + """ + Model to store ``config.yml`` present in Sinolpack packages. + """ + + package = models.OneToOneField(SinolpackPackage, on_delete=models.CASCADE) + config = models.TextField(verbose_name=_("config")) + + @property + def parsed_config(self): + if not self.config: + return {} + return yaml.safe_load(self.config) + + class Meta: + verbose_name = _("sinolpack's configuration") + verbose_name_plural = _("sinolpack's configurations") + + +class SinolpackNameTranslation(models.Model): + """ + Model to store translations of sinolpack package names. + """ + + package = models.OneToOneField(SinolpackPackage, on_delete=models.CASCADE) + language = models.CharField(max_length=2, choices=settings.LANGUAGES, verbose_name=_("language code")) + name = models.CharField(max_length=255, verbose_name=_("name translation")) + + class Meta: + verbose_name = _("sinolpack's name translation") + verbose_name_plural = _("sinolpack's name translations") + unique_together = ("package", "language") + + +class SinolpackModelSolution(models.Model): + package = models.OneToOneField(SinolpackPackage, on_delete=models.CASCADE) + name = models.CharField(max_length=30, verbose_name=_("name")) + source_file = FileField(upload_to=make_problem_filename, verbose_name=_("source file")) + kind = models.CharField(max_length=1, choices=ModelSolutionKind.all(), verbose_name=_("kind")) + order_key = models.IntegerField(default=0) + + @property + def short_name(self): + return self.name.rsplit('.', 1)[0] diff --git a/src/sio3pack/django/sinolpack/utils.py b/src/sio3pack/django/sinolpack/utils.py new file mode 100644 index 0000000..0e044ae --- /dev/null +++ b/src/sio3pack/django/sinolpack/utils.py @@ -0,0 +1,12 @@ +from sio3pack.django.sinolpack.models import SinolpackPackage +from django.utils.text import get_valid_filename + + +def make_problem_filename(instance, filename): + if not isinstance(instance, SinolpackPackage): + try: + instance = instance.package + except AttributeError: + raise ValueError(f'make_problem_filename used on an object {type(instance)} which does not have ' + f'a package attribute') + return f'sio3pack/sinolpack/{instance.problem_id}/{get_valid_filename(filename)}' diff --git a/src/sio3pack/packages/sinolpack/enums.py b/src/sio3pack/packages/sinolpack/enums.py index 0f44533..e6da638 100644 --- a/src/sio3pack/packages/sinolpack/enums.py +++ b/src/sio3pack/packages/sinolpack/enums.py @@ -2,9 +2,9 @@ class ModelSolutionKind(Enum): - NORMAL = 0 - SLOW = 1 - INCORRECT = 2 + NORMAL = '' + SLOW = 's' + INCORRECT = 'b' @classmethod def from_regex(cls, group): @@ -15,3 +15,7 @@ def from_regex(cls, group): if group == "b": return cls.INCORRECT raise ValueError(f"Invalid model solution kind: {group}") + + @classmethod + def all(cls): + return [cls.NORMAL, cls.SLOW, cls.INCORRECT] diff --git a/src/sio3pack/packages/sinolpack/model.py b/src/sio3pack/packages/sinolpack/model.py index 9d31864..941fddf 100644 --- a/src/sio3pack/packages/sinolpack/model.py +++ b/src/sio3pack/packages/sinolpack/model.py @@ -226,7 +226,7 @@ def get_model_solution_regex(self): extensions = self.get_submittable_extensions() return rf"^{self.short_name}[0-9]*([bs]?)[0-9]*(_.*)?\.(" + "|".join(extensions) + ")" - def _get_model_solutions(self) -> list[tuple[ModelSolutionKind, str]]: + def _get_model_solutions(self) -> list[tuple[ModelSolutionKind, LocalFile]]: """ Returns a list of model solutions, where each element is a tuple of model solution kind and filename. """ @@ -238,23 +238,30 @@ def _get_model_solutions(self) -> list[tuple[ModelSolutionKind, str]]: for file in os.listdir(self.get_prog_dir()): match = re.match(regex, file) if match and os.path.isfile(os.path.join(self.get_prog_dir(), file)): + file = LocalFile(os.path.join(self.get_prog_dir(), file)) model_solutions.append((ModelSolutionKind.from_regex(match.group(1)), file)) return model_solutions def sort_model_solutions( - self, model_solutions: list[tuple[ModelSolutionKind, str]] - ) -> list[tuple[ModelSolutionKind, str]]: + self, model_solutions: list[tuple[ModelSolutionKind, LocalFile]] + ) -> list[tuple[ModelSolutionKind, LocalFile]]: """ Sorts model solutions by kind. """ def sort_key(model_solution): - kind, name = model_solution - return kind.value, naturalsort_key(name[: name.index(".")]) + kind, file = model_solution + return kind.value, naturalsort_key(file.filename[: file.filename.index(".")]) return list(sorted(model_solutions, key=sort_key)) + def get_model_solutions(self) -> list[tuple[ModelSolutionKind, LocalFile]]: + """ + Returns a list of model solutions, where each element is a tuple of model solution kind and filename. + """ + return self.model_solutions + def _process_prog_files(self): """ Process all files in the problem's program directory that are used. From 34d4b05251cc526e028c29b0a32df9bc096b3d6d Mon Sep 17 00:00:00 2001 From: Mateusz Masiarz Date: Mon, 9 Dec 2024 20:20:36 +0100 Subject: [PATCH 02/10] Complete from_db --- .gitignore | 3 + src/sio3pack/django/sinolpack/handler.py | 40 +- ...002_sinolpackpackage_full_name_and_more.py | 360 ++++++++++++++++++ src/sio3pack/django/sinolpack/models.py | 96 ++++- src/sio3pack/django/sinolpack/utils.py | 12 - src/sio3pack/files/local_file.py | 2 +- src/sio3pack/packages/sinolpack/enums.py | 4 + src/sio3pack/packages/sinolpack/model.py | 56 ++- 8 files changed, 547 insertions(+), 26 deletions(-) create mode 100644 src/sio3pack/django/sinolpack/migrations/0002_sinolpackpackage_full_name_and_more.py delete mode 100644 src/sio3pack/django/sinolpack/utils.py diff --git a/.gitignore b/.gitignore index 471c6cc..188056b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ tests/test_django/db.sqlite3 # pytest-cov .coverage* coverage.xml + +# Files from Django db +/sio3pack diff --git a/src/sio3pack/django/sinolpack/handler.py b/src/sio3pack/django/sinolpack/handler.py index 22cac48..e0f73a4 100644 --- a/src/sio3pack/django/sinolpack/handler.py +++ b/src/sio3pack/django/sinolpack/handler.py @@ -4,8 +4,9 @@ from django.core.files import File from django.db import transaction +from sio3pack import LocalFile from sio3pack.django.sinolpack.models import SinolpackPackage, SinolpackConfig, SinolpackNameTranslation, \ - SinolpackModelSolution + SinolpackModelSolution, SinolpackAdditionalFile, SinolpackProblemStatement, SinolpackAttachment from sio3pack.packages.exceptions import PackageAlreadyExists from sio3pack.packages.package.django.handler import DjangoHandler @@ -33,6 +34,9 @@ def save_to_db(self): self._save_config() self._save_translated_titles() self._save_model_solutions() + self._save_additional_files() + self._save_problem_statements() + self._save_attachments() def _save_config(self): """ @@ -48,8 +52,7 @@ def _save_translated_titles(self): """ Save the translated titles to the database. """ - titles = self.package.get_titles() - for lang, title in titles.items(): + for lang, title in self.package.get_titles().items(): SinolpackNameTranslation.objects.create( package=self.db_package, language=lang, @@ -61,7 +64,36 @@ def _save_model_solutions(self): instance = SinolpackModelSolution( package=self.db_package, name=solution.filename, - kind=kind, + kind_name=kind.value, order_key=order, ) instance.source_file.save(solution.filename, File(open(solution.path, "rb"))) + + def _save_additional_files(self): + for file in self.package.get_additional_files(): + instance = SinolpackAdditionalFile( + package=self.db_package, + name=file.filename, + ) + instance.file.save(file.filename, File(open(file.path, "rb"))) + + def _save_problem_statements(self): + def _add_statement(language: str, statement: LocalFile): + instance = SinolpackProblemStatement( + package=self.db_package, + language=language, + ) + instance.content.save(statement.filename, File(open(statement.path, "rb"))) + + if self.package.get_statement(): + _add_statement("", self.package.get_statement()) + for lang, statement in self.package.get_statements().items(): + _add_statement(lang, statement) + + def _save_attachments(self): + for attachment in self.package.get_attachments(): + instance = SinolpackAttachment( + package=self.db_package, + description=attachment.filename, + ) + instance.content.save(attachment.filename, File(open(attachment.path, "rb"))) diff --git a/src/sio3pack/django/sinolpack/migrations/0002_sinolpackpackage_full_name_and_more.py b/src/sio3pack/django/sinolpack/migrations/0002_sinolpackpackage_full_name_and_more.py new file mode 100644 index 0000000..d19caf7 --- /dev/null +++ b/src/sio3pack/django/sinolpack/migrations/0002_sinolpackpackage_full_name_and_more.py @@ -0,0 +1,360 @@ +# Generated by Django 5.1.3 on 2024-12-09 19:17 + +import django.db.models.deletion +import sio3pack.django.sinolpack.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("sinolpack", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="sinolpackpackage", + name="full_name", + field=models.CharField(default="", max_length=255, verbose_name="full name"), + ), + migrations.AlterField( + model_name="sinolpackpackage", + name="short_name", + field=models.CharField(max_length=30, verbose_name="short name"), + ), + migrations.CreateModel( + name="SinolpackAdditionalFile", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "file", + models.FileField( + upload_to=sio3pack.django.sinolpack.models.make_problem_filename, verbose_name="file" + ), + ), + ( + "package", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), + ), + ], + options={ + "verbose_name": "additional file", + "verbose_name_plural": "additional files", + }, + ), + migrations.CreateModel( + name="SinolpackAttachment", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("description", models.CharField(max_length=255, verbose_name="description")), + ( + "content", + models.FileField( + upload_to=sio3pack.django.sinolpack.models.make_problem_filename, verbose_name="file" + ), + ), + ( + "package", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), + ), + ], + options={ + "verbose_name": "attachment", + "verbose_name_plural": "attachments", + }, + ), + migrations.CreateModel( + name="SinolpackConfig", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("config", models.TextField(verbose_name="config")), + ( + "package", + models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), + ), + ], + options={ + "verbose_name": "sinolpack's configuration", + "verbose_name_plural": "sinolpack's configurations", + }, + ), + migrations.CreateModel( + name="SinolpackModelSolution", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "source_file", + models.FileField( + upload_to=sio3pack.django.sinolpack.models.make_problem_filename, verbose_name="source file" + ), + ), + ( + "kind_name", + models.CharField( + choices=[("", "NORMAL"), ("s", "SLOW"), ("b", "INCORRECT")], max_length=1, verbose_name="kind" + ), + ), + ("order_key", models.IntegerField(default=0)), + ( + "package", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), + ), + ], + ), + migrations.CreateModel( + name="SinolpackProblemStatement", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "language", + models.CharField( + blank=True, + choices=[ + ("af", "Afrikaans"), + ("ar", "Arabic"), + ("ar-dz", "Algerian Arabic"), + ("ast", "Asturian"), + ("az", "Azerbaijani"), + ("bg", "Bulgarian"), + ("be", "Belarusian"), + ("bn", "Bengali"), + ("br", "Breton"), + ("bs", "Bosnian"), + ("ca", "Catalan"), + ("ckb", "Central Kurdish (Sorani)"), + ("cs", "Czech"), + ("cy", "Welsh"), + ("da", "Danish"), + ("de", "German"), + ("dsb", "Lower Sorbian"), + ("el", "Greek"), + ("en", "English"), + ("en-au", "Australian English"), + ("en-gb", "British English"), + ("eo", "Esperanto"), + ("es", "Spanish"), + ("es-ar", "Argentinian Spanish"), + ("es-co", "Colombian Spanish"), + ("es-mx", "Mexican Spanish"), + ("es-ni", "Nicaraguan Spanish"), + ("es-ve", "Venezuelan Spanish"), + ("et", "Estonian"), + ("eu", "Basque"), + ("fa", "Persian"), + ("fi", "Finnish"), + ("fr", "French"), + ("fy", "Frisian"), + ("ga", "Irish"), + ("gd", "Scottish Gaelic"), + ("gl", "Galician"), + ("he", "Hebrew"), + ("hi", "Hindi"), + ("hr", "Croatian"), + ("hsb", "Upper Sorbian"), + ("hu", "Hungarian"), + ("hy", "Armenian"), + ("ia", "Interlingua"), + ("id", "Indonesian"), + ("ig", "Igbo"), + ("io", "Ido"), + ("is", "Icelandic"), + ("it", "Italian"), + ("ja", "Japanese"), + ("ka", "Georgian"), + ("kab", "Kabyle"), + ("kk", "Kazakh"), + ("km", "Khmer"), + ("kn", "Kannada"), + ("ko", "Korean"), + ("ky", "Kyrgyz"), + ("lb", "Luxembourgish"), + ("lt", "Lithuanian"), + ("lv", "Latvian"), + ("mk", "Macedonian"), + ("ml", "Malayalam"), + ("mn", "Mongolian"), + ("mr", "Marathi"), + ("ms", "Malay"), + ("my", "Burmese"), + ("nb", "Norwegian Bokmål"), + ("ne", "Nepali"), + ("nl", "Dutch"), + ("nn", "Norwegian Nynorsk"), + ("os", "Ossetic"), + ("pa", "Punjabi"), + ("pl", "Polish"), + ("pt", "Portuguese"), + ("pt-br", "Brazilian Portuguese"), + ("ro", "Romanian"), + ("ru", "Russian"), + ("sk", "Slovak"), + ("sl", "Slovenian"), + ("sq", "Albanian"), + ("sr", "Serbian"), + ("sr-latn", "Serbian Latin"), + ("sv", "Swedish"), + ("sw", "Swahili"), + ("ta", "Tamil"), + ("te", "Telugu"), + ("tg", "Tajik"), + ("th", "Thai"), + ("tk", "Turkmen"), + ("tr", "Turkish"), + ("tt", "Tatar"), + ("udm", "Udmurt"), + ("ug", "Uyghur"), + ("uk", "Ukrainian"), + ("ur", "Urdu"), + ("uz", "Uzbek"), + ("vi", "Vietnamese"), + ("zh-hans", "Simplified Chinese"), + ("zh-hant", "Traditional Chinese"), + ], + max_length=7, + null=True, + verbose_name="language code", + ), + ), + ( + "content", + models.FileField( + upload_to=sio3pack.django.sinolpack.models.make_problem_filename, verbose_name="content" + ), + ), + ( + "package", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), + ), + ], + options={ + "verbose_name": "problem statement", + "verbose_name_plural": "problem statements", + }, + ), + migrations.CreateModel( + name="SinolpackNameTranslation", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "language", + models.CharField( + choices=[ + ("af", "Afrikaans"), + ("ar", "Arabic"), + ("ar-dz", "Algerian Arabic"), + ("ast", "Asturian"), + ("az", "Azerbaijani"), + ("bg", "Bulgarian"), + ("be", "Belarusian"), + ("bn", "Bengali"), + ("br", "Breton"), + ("bs", "Bosnian"), + ("ca", "Catalan"), + ("ckb", "Central Kurdish (Sorani)"), + ("cs", "Czech"), + ("cy", "Welsh"), + ("da", "Danish"), + ("de", "German"), + ("dsb", "Lower Sorbian"), + ("el", "Greek"), + ("en", "English"), + ("en-au", "Australian English"), + ("en-gb", "British English"), + ("eo", "Esperanto"), + ("es", "Spanish"), + ("es-ar", "Argentinian Spanish"), + ("es-co", "Colombian Spanish"), + ("es-mx", "Mexican Spanish"), + ("es-ni", "Nicaraguan Spanish"), + ("es-ve", "Venezuelan Spanish"), + ("et", "Estonian"), + ("eu", "Basque"), + ("fa", "Persian"), + ("fi", "Finnish"), + ("fr", "French"), + ("fy", "Frisian"), + ("ga", "Irish"), + ("gd", "Scottish Gaelic"), + ("gl", "Galician"), + ("he", "Hebrew"), + ("hi", "Hindi"), + ("hr", "Croatian"), + ("hsb", "Upper Sorbian"), + ("hu", "Hungarian"), + ("hy", "Armenian"), + ("ia", "Interlingua"), + ("id", "Indonesian"), + ("ig", "Igbo"), + ("io", "Ido"), + ("is", "Icelandic"), + ("it", "Italian"), + ("ja", "Japanese"), + ("ka", "Georgian"), + ("kab", "Kabyle"), + ("kk", "Kazakh"), + ("km", "Khmer"), + ("kn", "Kannada"), + ("ko", "Korean"), + ("ky", "Kyrgyz"), + ("lb", "Luxembourgish"), + ("lt", "Lithuanian"), + ("lv", "Latvian"), + ("mk", "Macedonian"), + ("ml", "Malayalam"), + ("mn", "Mongolian"), + ("mr", "Marathi"), + ("ms", "Malay"), + ("my", "Burmese"), + ("nb", "Norwegian Bokmål"), + ("ne", "Nepali"), + ("nl", "Dutch"), + ("nn", "Norwegian Nynorsk"), + ("os", "Ossetic"), + ("pa", "Punjabi"), + ("pl", "Polish"), + ("pt", "Portuguese"), + ("pt-br", "Brazilian Portuguese"), + ("ro", "Romanian"), + ("ru", "Russian"), + ("sk", "Slovak"), + ("sl", "Slovenian"), + ("sq", "Albanian"), + ("sr", "Serbian"), + ("sr-latn", "Serbian Latin"), + ("sv", "Swedish"), + ("sw", "Swahili"), + ("ta", "Tamil"), + ("te", "Telugu"), + ("tg", "Tajik"), + ("th", "Thai"), + ("tk", "Turkmen"), + ("tr", "Turkish"), + ("tt", "Tatar"), + ("udm", "Udmurt"), + ("ug", "Uyghur"), + ("uk", "Ukrainian"), + ("ur", "Urdu"), + ("uz", "Uzbek"), + ("vi", "Vietnamese"), + ("zh-hans", "Simplified Chinese"), + ("zh-hant", "Traditional Chinese"), + ], + max_length=7, + verbose_name="language code", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="name translation")), + ( + "package", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), + ), + ], + options={ + "verbose_name": "sinolpack's name translation", + "verbose_name_plural": "sinolpack's name translations", + "unique_together": {("package", "language")}, + }, + ), + ] diff --git a/src/sio3pack/django/sinolpack/models.py b/src/sio3pack/django/sinolpack/models.py index 1e57699..a4db5b2 100644 --- a/src/sio3pack/django/sinolpack/models.py +++ b/src/sio3pack/django/sinolpack/models.py @@ -1,9 +1,10 @@ +import os import yaml from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ +from django.utils.text import get_valid_filename -from sio3pack.django.sinolpack.utils import make_problem_filename from sio3pack.packages.sinolpack.enums import ModelSolutionKind try: @@ -12,6 +13,16 @@ FileField = models.FileField +def make_problem_filename(instance, filename): + if not isinstance(instance, SinolpackPackage): + try: + instance = instance.package + except AttributeError: + raise ValueError(f'make_problem_filename used on an object {type(instance)} which does not have ' + f'a package attribute') + return f'sio3pack/sinolpack/{instance.problem_id}/{get_valid_filename(filename)}' + + class SinolpackPackage(models.Model): """ A package for the sinolpack package type. @@ -19,7 +30,10 @@ class SinolpackPackage(models.Model): problem_id = models.IntegerField() short_name = models.CharField(max_length=30, verbose_name=_("short name")) - full_name = models.CharField(max_length=255, verbose_name=_("full name")) + full_name = models.CharField(max_length=255, default="", verbose_name=_("full name")) + + def __str__(self): + return f"" class SinolpackConfig(models.Model): @@ -36,6 +50,9 @@ def parsed_config(self): return {} return yaml.safe_load(self.config) + def __str__(self): + return f"" + class Meta: verbose_name = _("sinolpack's configuration") verbose_name_plural = _("sinolpack's configurations") @@ -46,10 +63,13 @@ class SinolpackNameTranslation(models.Model): Model to store translations of sinolpack package names. """ - package = models.OneToOneField(SinolpackPackage, on_delete=models.CASCADE) - language = models.CharField(max_length=2, choices=settings.LANGUAGES, verbose_name=_("language code")) + package = models.ForeignKey(SinolpackPackage, on_delete=models.CASCADE) + language = models.CharField(max_length=7, choices=settings.LANGUAGES, verbose_name=_("language code")) name = models.CharField(max_length=255, verbose_name=_("name translation")) + def __str__(self): + return f"" + class Meta: verbose_name = _("sinolpack's name translation") verbose_name_plural = _("sinolpack's name translations") @@ -57,12 +77,76 @@ class Meta: class SinolpackModelSolution(models.Model): - package = models.OneToOneField(SinolpackPackage, on_delete=models.CASCADE) + package = models.ForeignKey(SinolpackPackage, on_delete=models.CASCADE) name = models.CharField(max_length=30, verbose_name=_("name")) source_file = FileField(upload_to=make_problem_filename, verbose_name=_("source file")) - kind = models.CharField(max_length=1, choices=ModelSolutionKind.all(), verbose_name=_("kind")) + kind_name = models.CharField(max_length=1, choices=ModelSolutionKind.django_choices(), verbose_name=_("kind")) order_key = models.IntegerField(default=0) + def __str__(self): + return f"" + @property def short_name(self): return self.name.rsplit('.', 1)[0] + + @property + def kind(self): + return ModelSolutionKind(self.kind_name) + + +class SinolpackAdditionalFile(models.Model): + package = models.ForeignKey(SinolpackPackage, on_delete=models.CASCADE) + name = models.CharField(max_length=30, verbose_name=_("name")) + file = FileField(upload_to=make_problem_filename, verbose_name=_("file")) + + def __str__(self): + return f"" + + class Meta: + verbose_name = _("additional file") + verbose_name_plural = _("additional files") + + +class SinolpackProblemStatement(models.Model): + package = models.ForeignKey(SinolpackPackage, on_delete=models.CASCADE) + language = models.CharField( + max_length=7, blank=True, null=True, choices=settings.LANGUAGES, verbose_name=_("language code") + ) + content = FileField(upload_to=make_problem_filename, verbose_name=_("content")) + + @property + def filename(self): + return os.path.split(self.content.name)[1] + + @property + def download_name(self): + return self.package.short_name + self.extension + + @property + def extension(self): + return os.path.splitext(self.content.name)[1].lower() + + def __str__(self): + return f"" + + class Meta(object): + verbose_name = _("problem statement") + verbose_name_plural = _("problem statements") + + +class SinolpackAttachment(models.Model): + package = models.ForeignKey(SinolpackPackage, on_delete=models.CASCADE) + description = models.CharField(max_length=255, verbose_name=_("description")) + content = FileField(upload_to=make_problem_filename, verbose_name=_("file")) + + @property + def filename(self): + return os.path.split(self.content.name)[1] + + def __str__(self): + return f"" + + class Meta(object): + verbose_name = _("attachment") + verbose_name_plural = _("attachments") diff --git a/src/sio3pack/django/sinolpack/utils.py b/src/sio3pack/django/sinolpack/utils.py deleted file mode 100644 index 0e044ae..0000000 --- a/src/sio3pack/django/sinolpack/utils.py +++ /dev/null @@ -1,12 +0,0 @@ -from sio3pack.django.sinolpack.models import SinolpackPackage -from django.utils.text import get_valid_filename - - -def make_problem_filename(instance, filename): - if not isinstance(instance, SinolpackPackage): - try: - instance = instance.package - except AttributeError: - raise ValueError(f'make_problem_filename used on an object {type(instance)} which does not have ' - f'a package attribute') - return f'sio3pack/sinolpack/{instance.problem_id}/{get_valid_filename(filename)}' diff --git a/src/sio3pack/files/local_file.py b/src/sio3pack/files/local_file.py index 43b1c15..3d09c85 100644 --- a/src/sio3pack/files/local_file.py +++ b/src/sio3pack/files/local_file.py @@ -18,7 +18,7 @@ def get_file_matching_extension(cls, dir: str, filename: str, extensions: list[s :return: The file object. """ for ext in extensions: - path = os.path.join(dir, filename + ext) + path = os.path.join(dir, filename + '.' + ext) if os.path.exists(path): return cls(path) raise FileNotFoundError diff --git a/src/sio3pack/packages/sinolpack/enums.py b/src/sio3pack/packages/sinolpack/enums.py index e6da638..afeb93a 100644 --- a/src/sio3pack/packages/sinolpack/enums.py +++ b/src/sio3pack/packages/sinolpack/enums.py @@ -19,3 +19,7 @@ def from_regex(cls, group): @classmethod def all(cls): return [cls.NORMAL, cls.SLOW, cls.INCORRECT] + + @classmethod + def django_choices(cls): + return [(kind.value, kind.name) for kind in cls.all()] diff --git a/src/sio3pack/packages/sinolpack/model.py b/src/sio3pack/packages/sinolpack/model.py index 941fddf..eae6fce 100644 --- a/src/sio3pack/packages/sinolpack/model.py +++ b/src/sio3pack/packages/sinolpack/model.py @@ -1,10 +1,12 @@ import os import re import tempfile +from typing import Any import yaml from sio3pack import LocalFile +from sio3pack.files import File from sio3pack.graph import Graph, GraphManager, GraphOperation from sio3pack.packages.exceptions import ImproperlyConfigured from sio3pack.packages.package import Package @@ -167,6 +169,12 @@ def _process_package(self): # self.graph_manager = self._default_graph_manager() pass + def get_config(self) -> dict[str, Any]: + """ + Returns the configuration of the problem. + """ + return self.config + def _process_config_yml(self): """ Process the config.yml file. If it exists, it will be loaded into the config attribute. @@ -199,6 +207,20 @@ def _detect_full_name(self): except FileNotFoundError: pass + def get_titles(self) -> dict[str, str]: + """ + Returns a dictionary of problem titles, where keys are language codes and values are titles. + """ + return self.lang_titles + + def get_title(self, lang: str | None = None) -> str: + """ + Returns the problem title for a given language code. + """ + if lang is None: + return self.full_name + return self.lang_titles.get(lang, self.full_name) + def _detect_full_name_translations(self): """Creates problem's full name translations from the ``config.yml`` (keys matching the pattern ``title_[a-z]{2}``, where ``[a-z]{2}`` represents @@ -262,6 +284,12 @@ def get_model_solutions(self) -> list[tuple[ModelSolutionKind, LocalFile]]: """ return self.model_solutions + def get_additional_files(self) -> list[LocalFile]: + """ + Returns a list of additional files. + """ + return self.additional_files + def _process_prog_files(self): """ Process all files in the problem's program directory that are used. @@ -287,12 +315,26 @@ def _process_prog_files(self): self.additional_files.append( LocalFile.get_file_matching_extension( self.get_prog_dir(), self.short_name + file, extensions - ).filename + ) ) self.special_files[file] = True except FileNotFoundError: self.special_files[file] = False + def get_statements(self) -> dict[str, File]: + """ + Returns a dictionary of problem statements, where keys are language codes and values are files. + """ + return self.lang_statements + + def get_statement(self, lang: str | None = None) -> File: + """ + Returns the problem statement for a given language code. + """ + if lang is None: + return self.statement + return self.lang_statements.get(lang, None) + def _process_statements(self): """ Creates a problem statement from html or pdf source. @@ -301,6 +343,8 @@ def _process_statements(self): If `USE_SINOLPACK_MAKEFILES` is set to True in the OIOIOI settings, the pdf file will be compiled from a LaTeX source. """ + self.statement = None + self.lang_statements = {} docdir = self.get_doc_dir() if not os.path.exists(docdir): return @@ -309,7 +353,6 @@ def _process_statements(self): f"-{lang}" for lang, _ in self._get_from_django_settings("LANGUAGES", [("en", "English"), ("pl", "Polish")]) ] - self.lang_statements = {} for lang in lang_prefs: try: htmlzipfile = self.get_in_doc_dir(f"{self.short_name}zad{lang}.html.zip") @@ -340,13 +383,20 @@ def _process_statements(self): except FileNotFoundError: pass + def get_attachments(self) -> list[LocalFile]: + """ + Returns a list of attachments. + """ + return self.attachments + def _process_attachments(self): """ """ attachments_dir = self.get_attachments_dir() if not os.path.isdir(attachments_dir): + self.attachments = [] return self.attachments = [ - attachment + LocalFile(os.path.join(attachments_dir, attachment)) for attachment in os.listdir(attachments_dir) if os.path.isfile(os.path.join(attachments_dir, attachment)) ] From bda7bc595694291c28b9b217b8ccbd92bbf51fda Mon Sep 17 00:00:00 2001 From: Mateusz Masiarz Date: Mon, 9 Dec 2024 20:20:55 +0100 Subject: [PATCH 03/10] Add tests --- .../test_sio3pack/test_sinolpack.py | 66 +++++++++++++++++-- tests/test_packages/simple/prog/abc.cpp | 3 + tests/test_packages/simple/prog/abc3.cpp | 3 + tests/test_packages/simple/prog/abcb1.cpp | 3 + tests/test_packages/simple/prog/abcchk.cpp | 1 + tests/test_packages/simple/prog/abcs20.cpp | 3 + tests/utils.py | 8 +++ 7 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 tests/test_packages/simple/prog/abc.cpp create mode 100644 tests/test_packages/simple/prog/abc3.cpp create mode 100644 tests/test_packages/simple/prog/abcb1.cpp create mode 100644 tests/test_packages/simple/prog/abcchk.cpp create mode 100644 tests/test_packages/simple/prog/abcs20.cpp diff --git a/tests/test_django/test_sio3pack/test_sinolpack.py b/tests/test_django/test_sio3pack/test_sinolpack.py index 6516081..e1208b8 100644 --- a/tests/test_django/test_sio3pack/test_sinolpack.py +++ b/tests/test_django/test_sio3pack/test_sinolpack.py @@ -1,15 +1,14 @@ import pytest import sio3pack -from sio3pack.django.sinolpack.models import SinolpackPackage +from sio3pack.django.sinolpack.models import SinolpackPackage, SinolpackConfig, SinolpackModelSolution, \ + SinolpackAdditionalFile from sio3pack.packages import Sinolpack from tests.fixtures import Compression, PackageInfo, get_archived_package +from tests.utils import assert_contents_equal -@pytest.mark.django_db -@pytest.mark.parametrize("get_archived_package", [("simple", c) for c in Compression], indirect=True) -def test_simple(get_archived_package): - package_info: PackageInfo = get_archived_package() +def _save_and_test_simple(package_info: PackageInfo) -> tuple[Sinolpack, SinolpackPackage]: assert package_info.type == "sinolpack" package = sio3pack.from_file(package_info.path) assert isinstance(package, Sinolpack) @@ -17,6 +16,63 @@ def test_simple(get_archived_package): assert SinolpackPackage.objects.filter(problem_id=1).exists() db_package = SinolpackPackage.objects.get(problem_id=1) assert db_package.short_name == package.short_name + return package, db_package + + +@pytest.mark.django_db +@pytest.mark.parametrize("get_archived_package", [("simple", c) for c in Compression], indirect=True) +def test_simple(get_archived_package): + package_info: PackageInfo = get_archived_package() + package, db_package = _save_and_test_simple(package_info) + + assert package.get_title() == db_package.full_name with pytest.raises(sio3pack.PackageAlreadyExists): package.save_to_db(1) + + +@pytest.mark.django_db +@pytest.mark.parametrize("get_archived_package", [("simple", c) for c in Compression], indirect=True) +def test_config(get_archived_package): + package_info: PackageInfo = get_archived_package() + package, db_package = _save_and_test_simple(package_info) + config = SinolpackConfig.objects.get(package=db_package) + assert package.config == config.parsed_config + + +@pytest.mark.django_db +@pytest.mark.parametrize("get_archived_package", [("simple", c) for c in Compression], indirect=True) +def test_translated_titles(get_archived_package): + pytest.skip("Not implemented") + + +@pytest.mark.django_db +@pytest.mark.parametrize("get_archived_package", [("simple", c) for c in Compression], indirect=True) +def test_model_solutions(get_archived_package): + package_info: PackageInfo = get_archived_package() + package, db_package = _save_and_test_simple(package_info) + + model_solutions = package.get_model_solutions() + db_model_solutions = SinolpackModelSolution.objects.filter(package=db_package) + assert len(model_solutions) == db_model_solutions.count() + for order, (kind, solution) in enumerate(model_solutions): + ms = db_model_solutions.get(order_key=order) + assert ms.name == solution.filename + assert ms.kind == kind + assert_contents_equal(ms.source_file.read().decode('utf-8'), solution.read()) + + +@pytest.mark.django_db +@pytest.mark.parametrize("get_archived_package", [("simple", c) for c in Compression], indirect=True) +def test_additional_files(get_archived_package): + package_info: PackageInfo = get_archived_package() + package, db_package = _save_and_test_simple(package_info) + additional_files = package.get_additional_files() + db_additional_files = SinolpackAdditionalFile.objects.filter(package=db_package) + assert db_additional_files.count() == 1 + assert len(additional_files) == db_additional_files.count() + + for file in additional_files: + af = db_additional_files.get(name=file.filename) + assert_contents_equal(af.file.read().decode('utf-8'), file.read()) + assert af.name == file.filename diff --git a/tests/test_packages/simple/prog/abc.cpp b/tests/test_packages/simple/prog/abc.cpp new file mode 100644 index 0000000..98eaacf --- /dev/null +++ b/tests/test_packages/simple/prog/abc.cpp @@ -0,0 +1,3 @@ +#include + +int main() {} diff --git a/tests/test_packages/simple/prog/abc3.cpp b/tests/test_packages/simple/prog/abc3.cpp new file mode 100644 index 0000000..98eaacf --- /dev/null +++ b/tests/test_packages/simple/prog/abc3.cpp @@ -0,0 +1,3 @@ +#include + +int main() {} diff --git a/tests/test_packages/simple/prog/abcb1.cpp b/tests/test_packages/simple/prog/abcb1.cpp new file mode 100644 index 0000000..98eaacf --- /dev/null +++ b/tests/test_packages/simple/prog/abcb1.cpp @@ -0,0 +1,3 @@ +#include + +int main() {} diff --git a/tests/test_packages/simple/prog/abcchk.cpp b/tests/test_packages/simple/prog/abcchk.cpp new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/tests/test_packages/simple/prog/abcchk.cpp @@ -0,0 +1 @@ +main(){} diff --git a/tests/test_packages/simple/prog/abcs20.cpp b/tests/test_packages/simple/prog/abcs20.cpp new file mode 100644 index 0000000..98eaacf --- /dev/null +++ b/tests/test_packages/simple/prog/abcs20.cpp @@ -0,0 +1,3 @@ +#include + +int main() {} diff --git a/tests/utils.py b/tests/utils.py index e69de29..394e43e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -0,0 +1,8 @@ +def assert_contents_equal(content1, content2): + if isinstance(content1, bytes): + content1 = content1.decode('utf-8') + if isinstance(content2, bytes): + content2 = content2.decode('utf-8') + content1 = content1.replace('\r\n', '\n') + content2 = content2.replace('\r\n', '\n') + assert content1 == content2 From 15241ed1ed5b2639a181c1df67b137d8b0604c5e Mon Sep 17 00:00:00 2001 From: Mateusz Masiarz Date: Mon, 9 Dec 2024 20:33:35 +0100 Subject: [PATCH 04/10] Reformat --- .github/workflows/formatter.yml | 4 ++-- pyproject.toml | 9 ++++++++- src/sio3pack/django/sinolpack/handler.py | 9 +++++++-- src/sio3pack/django/sinolpack/models.py | 12 +++++++----- src/sio3pack/files/local_file.py | 2 +- src/sio3pack/packages/sinolpack/enums.py | 6 +++--- src/sio3pack/packages/sinolpack/model.py | 4 +--- tests/test_django/test_sio3pack/test_sinolpack.py | 10 ++++++---- tests/utils.py | 8 ++++---- 9 files changed, 39 insertions(+), 25 deletions(-) diff --git a/.github/workflows/formatter.yml b/.github/workflows/formatter.yml index e930f84..175a59c 100644 --- a/.github/workflows/formatter.yml +++ b/.github/workflows/formatter.yml @@ -19,5 +19,5 @@ jobs: pip install isort black - name: Run isort and black run: | - isort -c . - black --check . + isort -c src tests + black --check src tests diff --git a/pyproject.toml b/pyproject.toml index 8dcf968..d18517b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,14 @@ build-backend = "setuptools.build_meta" line_length = 120 multi_line_output = 1 include_trailing_comma = true +skip = ["migrations", ".venv"] [tool.black] line_length = 120 -exclude = "migrations/" +exclude = ''' +/^( + migrations + | sio3pack$ + | \.venv$ +)/ +''' diff --git a/src/sio3pack/django/sinolpack/handler.py b/src/sio3pack/django/sinolpack/handler.py index e0f73a4..eebf186 100644 --- a/src/sio3pack/django/sinolpack/handler.py +++ b/src/sio3pack/django/sinolpack/handler.py @@ -5,8 +5,13 @@ from django.db import transaction from sio3pack import LocalFile -from sio3pack.django.sinolpack.models import SinolpackPackage, SinolpackConfig, SinolpackNameTranslation, \ - SinolpackModelSolution, SinolpackAdditionalFile, SinolpackProblemStatement, SinolpackAttachment +from sio3pack.django.sinolpack.models import (SinolpackAdditionalFile, + SinolpackAttachment, + SinolpackConfig, + SinolpackModelSolution, + SinolpackNameTranslation, + SinolpackPackage, + SinolpackProblemStatement,) from sio3pack.packages.exceptions import PackageAlreadyExists from sio3pack.packages.package.django.handler import DjangoHandler diff --git a/src/sio3pack/django/sinolpack/models.py b/src/sio3pack/django/sinolpack/models.py index a4db5b2..fa8a591 100644 --- a/src/sio3pack/django/sinolpack/models.py +++ b/src/sio3pack/django/sinolpack/models.py @@ -1,9 +1,10 @@ import os + import yaml from django.conf import settings from django.db import models -from django.utils.translation import gettext_lazy as _ from django.utils.text import get_valid_filename +from django.utils.translation import gettext_lazy as _ from sio3pack.packages.sinolpack.enums import ModelSolutionKind @@ -18,9 +19,10 @@ def make_problem_filename(instance, filename): try: instance = instance.package except AttributeError: - raise ValueError(f'make_problem_filename used on an object {type(instance)} which does not have ' - f'a package attribute') - return f'sio3pack/sinolpack/{instance.problem_id}/{get_valid_filename(filename)}' + raise ValueError( + f"make_problem_filename used on an object {type(instance)} which does not have " f"a package attribute" + ) + return f"sio3pack/sinolpack/{instance.problem_id}/{get_valid_filename(filename)}" class SinolpackPackage(models.Model): @@ -88,7 +90,7 @@ def __str__(self): @property def short_name(self): - return self.name.rsplit('.', 1)[0] + return self.name.rsplit(".", 1)[0] @property def kind(self): diff --git a/src/sio3pack/files/local_file.py b/src/sio3pack/files/local_file.py index 3d09c85..59cd75c 100644 --- a/src/sio3pack/files/local_file.py +++ b/src/sio3pack/files/local_file.py @@ -18,7 +18,7 @@ def get_file_matching_extension(cls, dir: str, filename: str, extensions: list[s :return: The file object. """ for ext in extensions: - path = os.path.join(dir, filename + '.' + ext) + path = os.path.join(dir, filename + "." + ext) if os.path.exists(path): return cls(path) raise FileNotFoundError diff --git a/src/sio3pack/packages/sinolpack/enums.py b/src/sio3pack/packages/sinolpack/enums.py index afeb93a..5cd91cb 100644 --- a/src/sio3pack/packages/sinolpack/enums.py +++ b/src/sio3pack/packages/sinolpack/enums.py @@ -2,9 +2,9 @@ class ModelSolutionKind(Enum): - NORMAL = '' - SLOW = 's' - INCORRECT = 'b' + NORMAL = "" + SLOW = "s" + INCORRECT = "b" @classmethod def from_regex(cls, group): diff --git a/src/sio3pack/packages/sinolpack/model.py b/src/sio3pack/packages/sinolpack/model.py index eae6fce..b2f736c 100644 --- a/src/sio3pack/packages/sinolpack/model.py +++ b/src/sio3pack/packages/sinolpack/model.py @@ -313,9 +313,7 @@ def _process_prog_files(self): for file in ("ingen", "inwer", "soc", "chk"): try: self.additional_files.append( - LocalFile.get_file_matching_extension( - self.get_prog_dir(), self.short_name + file, extensions - ) + LocalFile.get_file_matching_extension(self.get_prog_dir(), self.short_name + file, extensions) ) self.special_files[file] = True except FileNotFoundError: diff --git a/tests/test_django/test_sio3pack/test_sinolpack.py b/tests/test_django/test_sio3pack/test_sinolpack.py index e1208b8..5811ec1 100644 --- a/tests/test_django/test_sio3pack/test_sinolpack.py +++ b/tests/test_django/test_sio3pack/test_sinolpack.py @@ -1,8 +1,10 @@ import pytest import sio3pack -from sio3pack.django.sinolpack.models import SinolpackPackage, SinolpackConfig, SinolpackModelSolution, \ - SinolpackAdditionalFile +from sio3pack.django.sinolpack.models import (SinolpackAdditionalFile, + SinolpackConfig, + SinolpackModelSolution, + SinolpackPackage,) from sio3pack.packages import Sinolpack from tests.fixtures import Compression, PackageInfo, get_archived_package from tests.utils import assert_contents_equal @@ -59,7 +61,7 @@ def test_model_solutions(get_archived_package): ms = db_model_solutions.get(order_key=order) assert ms.name == solution.filename assert ms.kind == kind - assert_contents_equal(ms.source_file.read().decode('utf-8'), solution.read()) + assert_contents_equal(ms.source_file.read().decode("utf-8"), solution.read()) @pytest.mark.django_db @@ -74,5 +76,5 @@ def test_additional_files(get_archived_package): for file in additional_files: af = db_additional_files.get(name=file.filename) - assert_contents_equal(af.file.read().decode('utf-8'), file.read()) + assert_contents_equal(af.file.read().decode("utf-8"), file.read()) assert af.name == file.filename diff --git a/tests/utils.py b/tests/utils.py index 394e43e..fc057ba 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,8 +1,8 @@ def assert_contents_equal(content1, content2): if isinstance(content1, bytes): - content1 = content1.decode('utf-8') + content1 = content1.decode("utf-8") if isinstance(content2, bytes): - content2 = content2.decode('utf-8') - content1 = content1.replace('\r\n', '\n') - content2 = content2.replace('\r\n', '\n') + content2 = content2.decode("utf-8") + content1 = content1.replace("\r\n", "\n") + content2 = content2.replace("\r\n", "\n") assert content1 == content2 From d099797908c2068c12bfee9dfcdb0f9dd3f65db3 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Tue, 10 Dec 2024 14:49:25 +0100 Subject: [PATCH 05/10] regex refactor --- src/sio3pack/packages/sinolpack/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sio3pack/packages/sinolpack/model.py b/src/sio3pack/packages/sinolpack/model.py index b2f736c..0686ac6 100644 --- a/src/sio3pack/packages/sinolpack/model.py +++ b/src/sio3pack/packages/sinolpack/model.py @@ -246,7 +246,7 @@ def get_model_solution_regex(self): Returns the regex used to determine model solutions. """ extensions = self.get_submittable_extensions() - return rf"^{self.short_name}[0-9]*([bs]?)[0-9]*(_.*)?\.(" + "|".join(extensions) + ")" + return rf"^{self.short_name}[0-9]*([bs]?)[0-9]*(_.*)?\.({"|".join(extensions)})" def _get_model_solutions(self) -> list[tuple[ModelSolutionKind, LocalFile]]: """ From 6301da4e46e625c64b1619c0f59c1e11232c3b4b Mon Sep 17 00:00:00 2001 From: Tomasz Kwiatkowski Date: Thu, 12 Dec 2024 11:51:11 +0100 Subject: [PATCH 06/10] Create common django app --- .../django => django/common}/__init__.py | 0 src/sio3pack/django/common/handler.py | 66 ++++++++++ .../migrations/0001_initial.py} | 113 ++++-------------- .../django/common/migrations/__init__.py | 0 src/sio3pack/django/common/models.py | 94 +++++++++++++++ src/sio3pack/django/sinolpack/handler.py | 45 +------ .../sinolpack/migrations/0001_initial.py | 77 +++++++++++- src/sio3pack/django/sinolpack/models.py | 84 +------------ .../packages/package/{django => }/handler.py | 8 -- src/sio3pack/packages/package/model.py | 2 +- src/sio3pack/packages/sinolpack/model.py | 2 +- tests/test_django/test_django/settings.py | 1 + .../test_sio3pack/test_sinolpack.py | 12 +- 13 files changed, 268 insertions(+), 236 deletions(-) rename src/sio3pack/{packages/package/django => django/common}/__init__.py (100%) create mode 100644 src/sio3pack/django/common/handler.py rename src/sio3pack/django/{sinolpack/migrations/0002_sinolpackpackage_full_name_and_more.py => common/migrations/0001_initial.py} (74%) create mode 100644 src/sio3pack/django/common/migrations/__init__.py create mode 100644 src/sio3pack/django/common/models.py rename src/sio3pack/packages/package/{django => }/handler.py (53%) diff --git a/src/sio3pack/packages/package/django/__init__.py b/src/sio3pack/django/common/__init__.py similarity index 100% rename from src/sio3pack/packages/package/django/__init__.py rename to src/sio3pack/django/common/__init__.py diff --git a/src/sio3pack/django/common/handler.py b/src/sio3pack/django/common/handler.py new file mode 100644 index 0000000..19847bf --- /dev/null +++ b/src/sio3pack/django/common/handler.py @@ -0,0 +1,66 @@ +from typing import Type + +from django.core.files import File +from django.db import transaction + +from sio3pack.django.common.models import SIO3Package, SIO3PackModelSolution, SIO3PackNameTranslation, SIO3PackStatement +from sio3pack.files.local_file import LocalFile +from sio3pack.packages.exceptions import ImproperlyConfigured, PackageAlreadyExists + + +class DjangoHandler: + def __init__(self, package: Type["Package"], problem_id: int): + self.package = package + self.problem_id = problem_id + self.db_package = None + + @transaction.atomic + def save_to_db(self): + """ + Save the package to the database. + """ + if SIO3Package.objects.filter(problem_id=self.problem_id).exists(): + raise PackageAlreadyExists(self.problem_id) + + self.db_package = SIO3Package.objects.create( + problem_id=self.problem_id, + short_name=self.package.short_name, + full_name=self.package.full_name, + ) + + self._save_translated_titles() + self._save_model_solutions() + self._save_problem_statements() + + def _save_translated_titles(self): + """ + Save the translated titles to the database. + """ + for lang, title in self.package.get_titles().items(): + SIO3PackNameTranslation.objects.create( + package=self.db_package, + language=lang, + name=title, + ) + + def _save_model_solutions(self): + for order, solution in enumerate(self.package.get_model_solutions()): + instance = SIO3PackModelSolution( + package=self.db_package, + name=solution.filename, + order_key=order, + ) + instance.source_file.save(solution.filename, File(open(solution.path, "rb"))) + + def _save_problem_statements(self): + def _add_statement(language: str, statement: LocalFile): + instance = SIO3PackStatement( + package=self.db_package, + language=language, + ) + instance.content.save(statement.filename, File(open(statement.path, "rb"))) + + if self.package.get_statement(): + _add_statement("", self.package.get_statement()) + for lang, statement in self.package.get_statements().items(): + _add_statement(lang, statement) diff --git a/src/sio3pack/django/sinolpack/migrations/0002_sinolpackpackage_full_name_and_more.py b/src/sio3pack/django/common/migrations/0001_initial.py similarity index 74% rename from src/sio3pack/django/sinolpack/migrations/0002_sinolpackpackage_full_name_and_more.py rename to src/sio3pack/django/common/migrations/0001_initial.py index d19caf7..6c2b137 100644 --- a/src/sio3pack/django/sinolpack/migrations/0002_sinolpackpackage_full_name_and_more.py +++ b/src/sio3pack/django/common/migrations/0001_initial.py @@ -1,110 +1,43 @@ -# Generated by Django 5.1.3 on 2024-12-09 19:17 +# Generated by Django 5.1.4 on 2024-12-12 10:46 import django.db.models.deletion -import sio3pack.django.sinolpack.models +import sio3pack.django.common.models from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ("sinolpack", "0001_initial"), - ] + initial = True + + dependencies = [] operations = [ - migrations.AddField( - model_name="sinolpackpackage", - name="full_name", - field=models.CharField(default="", max_length=255, verbose_name="full name"), - ), - migrations.AlterField( - model_name="sinolpackpackage", - name="short_name", - field=models.CharField(max_length=30, verbose_name="short name"), - ), migrations.CreateModel( - name="SinolpackAdditionalFile", + name="SIO3Package", fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("name", models.CharField(max_length=30, verbose_name="name")), - ( - "file", - models.FileField( - upload_to=sio3pack.django.sinolpack.models.make_problem_filename, verbose_name="file" - ), - ), - ( - "package", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), - ), + ("problem_id", models.IntegerField()), + ("short_name", models.CharField(max_length=30, verbose_name="short name")), + ("full_name", models.CharField(default="", max_length=255, verbose_name="full name")), ], - options={ - "verbose_name": "additional file", - "verbose_name_plural": "additional files", - }, ), migrations.CreateModel( - name="SinolpackAttachment", + name="SIO3PackModelSolution", fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("description", models.CharField(max_length=255, verbose_name="description")), - ( - "content", - models.FileField( - upload_to=sio3pack.django.sinolpack.models.make_problem_filename, verbose_name="file" - ), - ), - ( - "package", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), - ), - ], - options={ - "verbose_name": "attachment", - "verbose_name_plural": "attachments", - }, - ), - migrations.CreateModel( - name="SinolpackConfig", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("config", models.TextField(verbose_name="config")), - ( - "package", - models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), - ), - ], - options={ - "verbose_name": "sinolpack's configuration", - "verbose_name_plural": "sinolpack's configurations", - }, - ), - migrations.CreateModel( - name="SinolpackModelSolution", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("name", models.CharField(max_length=30, verbose_name="name")), + ("name", models.CharField(max_length=255, verbose_name="name")), ( "source_file", models.FileField( - upload_to=sio3pack.django.sinolpack.models.make_problem_filename, verbose_name="source file" - ), - ), - ( - "kind_name", - models.CharField( - choices=[("", "NORMAL"), ("s", "SLOW"), ("b", "INCORRECT")], max_length=1, verbose_name="kind" + upload_to=sio3pack.django.common.models.make_problem_filename, verbose_name="source file" ), ), ("order_key", models.IntegerField(default=0)), - ( - "package", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), - ), + ("package", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="common.sio3package")), ], ), migrations.CreateModel( - name="SinolpackProblemStatement", + name="SIO3PackStatement", fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ( @@ -220,13 +153,10 @@ class Migration(migrations.Migration): ( "content", models.FileField( - upload_to=sio3pack.django.sinolpack.models.make_problem_filename, verbose_name="content" + upload_to=sio3pack.django.common.models.make_problem_filename, verbose_name="content" ), ), - ( - "package", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), - ), + ("package", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="common.sio3package")), ], options={ "verbose_name": "problem statement", @@ -234,7 +164,7 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name="SinolpackNameTranslation", + name="SIO3PackNameTranslation", fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ( @@ -346,14 +276,11 @@ class Migration(migrations.Migration): ), ), ("name", models.CharField(max_length=255, verbose_name="name translation")), - ( - "package", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sinolpack.sinolpackpackage"), - ), + ("package", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="common.sio3package")), ], options={ - "verbose_name": "sinolpack's name translation", - "verbose_name_plural": "sinolpack's name translations", + "verbose_name": "sio3pack's name translation", + "verbose_name_plural": "sio3pack's name translations", "unique_together": {("package", "language")}, }, ), diff --git a/src/sio3pack/django/common/migrations/__init__.py b/src/sio3pack/django/common/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/sio3pack/django/common/models.py b/src/sio3pack/django/common/models.py new file mode 100644 index 0000000..7198414 --- /dev/null +++ b/src/sio3pack/django/common/models.py @@ -0,0 +1,94 @@ +import os + +from django.conf import settings +from django.db import models +from django.utils.text import get_valid_filename +from django.utils.translation import gettext_lazy as _ + +try: + from oioioi.filetracker.fields import FileField +except ImportError: + FileField = models.FileField + + +def make_problem_filename(instance, filename): + if not isinstance(instance, SIO3Package): + try: + instance = instance.package + except AttributeError: + raise ValueError( + f"make_problem_filename used on an object {type(instance)} which does not have " f"a package attribute" + ) + return f"sio3pack/{instance.problem_id}/{get_valid_filename(filename)}" + + +class SIO3Package(models.Model): + """ + A generic package type. + """ + + problem_id = models.IntegerField() + short_name = models.CharField(max_length=30, verbose_name=_("short name")) + full_name = models.CharField(max_length=255, default="", verbose_name=_("full name")) + + def __str__(self): + return f"" + + +class SIO3PackNameTranslation(models.Model): + """ + Model to store translated task's title. + """ + + package = models.ForeignKey(SIO3Package, on_delete=models.CASCADE) + language = models.CharField(max_length=7, choices=settings.LANGUAGES, verbose_name=_("language code")) + name = models.CharField(max_length=255, verbose_name=_("name translation")) + + def __str__(self): + return f"" + + class Meta: + verbose_name = _("sio3pack's name translation") + verbose_name_plural = _("sio3pack's name translations") + unique_together = ("package", "language") + + +class SIO3PackModelSolution(models.Model): + package = models.ForeignKey(SIO3Package, on_delete=models.CASCADE) + name = models.CharField(max_length=255, verbose_name=_("name")) + source_file = FileField(upload_to=make_problem_filename, verbose_name=_("source file")) + order_key = models.IntegerField(default=0) + + def __str__(self): + return f"" + + @property + def short_name(self): + return self.name.rsplit(".", 1)[0] + + +class SIO3PackStatement(models.Model): + package = models.ForeignKey(SIO3Package, on_delete=models.CASCADE) + language = models.CharField( + max_length=7, blank=True, null=True, choices=settings.LANGUAGES, verbose_name=_("language code") + ) + content = FileField(upload_to=make_problem_filename, verbose_name=_("content")) + + @property + def filename(self): + return os.path.split(self.content.name)[1] + + @property + def download_name(self): + return self.package.short_name + self.extension + + @property + def extension(self): + return os.path.splitext(self.content.name)[1].lower() + + def __str__(self): + return f"" + + class Meta(object): + verbose_name = _("problem statement") + verbose_name_plural = _("problem statements") diff --git a/src/sio3pack/django/sinolpack/handler.py b/src/sio3pack/django/sinolpack/handler.py index eebf186..f1081f4 100644 --- a/src/sio3pack/django/sinolpack/handler.py +++ b/src/sio3pack/django/sinolpack/handler.py @@ -4,16 +4,11 @@ from django.core.files import File from django.db import transaction -from sio3pack import LocalFile +from sio3pack.django.common.handler import DjangoHandler from sio3pack.django.sinolpack.models import (SinolpackAdditionalFile, SinolpackAttachment, SinolpackConfig, - SinolpackModelSolution, - SinolpackNameTranslation, - SinolpackPackage, - SinolpackProblemStatement,) -from sio3pack.packages.exceptions import PackageAlreadyExists -from sio3pack.packages.package.django.handler import DjangoHandler + SinolpackModelSolution,) class SinolpackDjangoHandler(DjangoHandler): @@ -27,20 +22,10 @@ def save_to_db(self): """ Save the package to the database. """ - if SinolpackPackage.objects.filter(problem_id=self.problem_id).exists(): - raise PackageAlreadyExists(self.problem_id) - - self.db_package = SinolpackPackage.objects.create( - problem_id=self.problem_id, - short_name=self.package.short_name, - full_name=self.package.full_name, - ) + super(SinolpackDjangoHandler, self).save_to_db() self._save_config() - self._save_translated_titles() - self._save_model_solutions() self._save_additional_files() - self._save_problem_statements() self._save_attachments() def _save_config(self): @@ -53,17 +38,6 @@ def _save_config(self): config=yaml.dump(config), ) - def _save_translated_titles(self): - """ - Save the translated titles to the database. - """ - for lang, title in self.package.get_titles().items(): - SinolpackNameTranslation.objects.create( - package=self.db_package, - language=lang, - name=title, - ) - def _save_model_solutions(self): for order, (kind, solution) in enumerate(self.package.get_model_solutions()): instance = SinolpackModelSolution( @@ -82,19 +56,6 @@ def _save_additional_files(self): ) instance.file.save(file.filename, File(open(file.path, "rb"))) - def _save_problem_statements(self): - def _add_statement(language: str, statement: LocalFile): - instance = SinolpackProblemStatement( - package=self.db_package, - language=language, - ) - instance.content.save(statement.filename, File(open(statement.path, "rb"))) - - if self.package.get_statement(): - _add_statement("", self.package.get_statement()) - for lang, statement in self.package.get_statements().items(): - _add_statement(lang, statement) - def _save_attachments(self): for attachment in self.package.get_attachments(): instance = SinolpackAttachment( diff --git a/src/sio3pack/django/sinolpack/migrations/0001_initial.py b/src/sio3pack/django/sinolpack/migrations/0001_initial.py index c342290..2816b3d 100644 --- a/src/sio3pack/django/sinolpack/migrations/0001_initial.py +++ b/src/sio3pack/django/sinolpack/migrations/0001_initial.py @@ -1,5 +1,7 @@ -# Generated by Django 5.1.3 on 2024-12-01 17:22 +# Generated by Django 5.1.4 on 2024-12-12 10:46 +import django.db.models.deletion +import sio3pack.django.common.models from django.db import migrations, models @@ -7,15 +9,80 @@ class Migration(migrations.Migration): initial = True - dependencies = [] + dependencies = [ + ("common", "0001_initial"), + ] operations = [ migrations.CreateModel( - name="SinolpackPackage", + name="SinolpackModelSolution", + fields=[ + ( + "sio3packmodelsolution_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="common.sio3packmodelsolution", + ), + ), + ( + "kind_name", + models.CharField( + choices=[("", "NORMAL"), ("s", "SLOW"), ("b", "INCORRECT")], max_length=1, verbose_name="kind" + ), + ), + ], + bases=("common.sio3packmodelsolution",), + ), + migrations.CreateModel( + name="SinolpackAdditionalFile", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "file", + models.FileField( + upload_to=sio3pack.django.common.models.make_problem_filename, verbose_name="file" + ), + ), + ("package", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="common.sio3package")), + ], + options={ + "verbose_name": "additional file", + "verbose_name_plural": "additional files", + }, + ), + migrations.CreateModel( + name="SinolpackAttachment", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("description", models.CharField(max_length=255, verbose_name="description")), + ( + "content", + models.FileField( + upload_to=sio3pack.django.common.models.make_problem_filename, verbose_name="file" + ), + ), + ("package", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="common.sio3package")), + ], + options={ + "verbose_name": "attachment", + "verbose_name_plural": "attachments", + }, + ), + migrations.CreateModel( + name="SinolpackConfig", fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("problem_id", models.IntegerField()), - ("short_name", models.CharField(max_length=100)), + ("config", models.TextField(verbose_name="config")), + ("package", models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to="common.sio3package")), ], + options={ + "verbose_name": "sinolpack's configuration", + "verbose_name_plural": "sinolpack's configurations", + }, ), ] diff --git a/src/sio3pack/django/sinolpack/models.py b/src/sio3pack/django/sinolpack/models.py index fa8a591..9eead00 100644 --- a/src/sio3pack/django/sinolpack/models.py +++ b/src/sio3pack/django/sinolpack/models.py @@ -1,11 +1,10 @@ import os import yaml -from django.conf import settings from django.db import models -from django.utils.text import get_valid_filename from django.utils.translation import gettext_lazy as _ +from sio3pack.django.common.models import SIO3Package, SIO3PackModelSolution, make_problem_filename from sio3pack.packages.sinolpack.enums import ModelSolutionKind try: @@ -14,36 +13,12 @@ FileField = models.FileField -def make_problem_filename(instance, filename): - if not isinstance(instance, SinolpackPackage): - try: - instance = instance.package - except AttributeError: - raise ValueError( - f"make_problem_filename used on an object {type(instance)} which does not have " f"a package attribute" - ) - return f"sio3pack/sinolpack/{instance.problem_id}/{get_valid_filename(filename)}" - - -class SinolpackPackage(models.Model): - """ - A package for the sinolpack package type. - """ - - problem_id = models.IntegerField() - short_name = models.CharField(max_length=30, verbose_name=_("short name")) - full_name = models.CharField(max_length=255, default="", verbose_name=_("full name")) - - def __str__(self): - return f"" - - class SinolpackConfig(models.Model): """ Model to store ``config.yml`` present in Sinolpack packages. """ - package = models.OneToOneField(SinolpackPackage, on_delete=models.CASCADE) + package = models.OneToOneField(SIO3Package, on_delete=models.CASCADE) config = models.TextField(verbose_name=_("config")) @property @@ -60,30 +35,8 @@ class Meta: verbose_name_plural = _("sinolpack's configurations") -class SinolpackNameTranslation(models.Model): - """ - Model to store translations of sinolpack package names. - """ - - package = models.ForeignKey(SinolpackPackage, on_delete=models.CASCADE) - language = models.CharField(max_length=7, choices=settings.LANGUAGES, verbose_name=_("language code")) - name = models.CharField(max_length=255, verbose_name=_("name translation")) - - def __str__(self): - return f"" - - class Meta: - verbose_name = _("sinolpack's name translation") - verbose_name_plural = _("sinolpack's name translations") - unique_together = ("package", "language") - - -class SinolpackModelSolution(models.Model): - package = models.ForeignKey(SinolpackPackage, on_delete=models.CASCADE) - name = models.CharField(max_length=30, verbose_name=_("name")) - source_file = FileField(upload_to=make_problem_filename, verbose_name=_("source file")) +class SinolpackModelSolution(SIO3PackModelSolution): kind_name = models.CharField(max_length=1, choices=ModelSolutionKind.django_choices(), verbose_name=_("kind")) - order_key = models.IntegerField(default=0) def __str__(self): return f"" @@ -98,7 +51,7 @@ def kind(self): class SinolpackAdditionalFile(models.Model): - package = models.ForeignKey(SinolpackPackage, on_delete=models.CASCADE) + package = models.ForeignKey(SIO3Package, on_delete=models.CASCADE) name = models.CharField(max_length=30, verbose_name=_("name")) file = FileField(upload_to=make_problem_filename, verbose_name=_("file")) @@ -110,35 +63,8 @@ class Meta: verbose_name_plural = _("additional files") -class SinolpackProblemStatement(models.Model): - package = models.ForeignKey(SinolpackPackage, on_delete=models.CASCADE) - language = models.CharField( - max_length=7, blank=True, null=True, choices=settings.LANGUAGES, verbose_name=_("language code") - ) - content = FileField(upload_to=make_problem_filename, verbose_name=_("content")) - - @property - def filename(self): - return os.path.split(self.content.name)[1] - - @property - def download_name(self): - return self.package.short_name + self.extension - - @property - def extension(self): - return os.path.splitext(self.content.name)[1].lower() - - def __str__(self): - return f"" - - class Meta(object): - verbose_name = _("problem statement") - verbose_name_plural = _("problem statements") - - class SinolpackAttachment(models.Model): - package = models.ForeignKey(SinolpackPackage, on_delete=models.CASCADE) + package = models.ForeignKey(SIO3Package, on_delete=models.CASCADE) description = models.CharField(max_length=255, verbose_name=_("description")) content = FileField(upload_to=make_problem_filename, verbose_name=_("file")) diff --git a/src/sio3pack/packages/package/django/handler.py b/src/sio3pack/packages/package/handler.py similarity index 53% rename from src/sio3pack/packages/package/django/handler.py rename to src/sio3pack/packages/package/handler.py index e838fca..ff535c5 100644 --- a/src/sio3pack/packages/package/django/handler.py +++ b/src/sio3pack/packages/package/handler.py @@ -1,14 +1,6 @@ -from typing import Type - from sio3pack.packages.exceptions import ImproperlyConfigured -class DjangoHandler: - def __init__(self, package: Type["Package"], problem_id: int): - self.package = package - self.problem_id = problem_id - - class NoDjangoHandler: def __call__(self, *args, **kwargs): raise ImproperlyConfigured("sio3pack is not installed with Django support.") diff --git a/src/sio3pack/packages/package/model.py b/src/sio3pack/packages/package/model.py index 9438957..1f31da4 100644 --- a/src/sio3pack/packages/package/model.py +++ b/src/sio3pack/packages/package/model.py @@ -5,7 +5,7 @@ from sio3pack.files import File from sio3pack.graph import Graph, GraphOperation from sio3pack.packages.exceptions import UnknownPackageType -from sio3pack.packages.package.django.handler import NoDjangoHandler +from sio3pack.packages.package.handler import NoDjangoHandler from sio3pack.test import Test from sio3pack.utils.archive import Archive from sio3pack.utils.classinit import RegisteredSubclassesBase diff --git a/src/sio3pack/packages/sinolpack/model.py b/src/sio3pack/packages/sinolpack/model.py index 0686ac6..e605da0 100644 --- a/src/sio3pack/packages/sinolpack/model.py +++ b/src/sio3pack/packages/sinolpack/model.py @@ -246,7 +246,7 @@ def get_model_solution_regex(self): Returns the regex used to determine model solutions. """ extensions = self.get_submittable_extensions() - return rf"^{self.short_name}[0-9]*([bs]?)[0-9]*(_.*)?\.({"|".join(extensions)})" + return rf"^{self.short_name}[0-9]*([bs]?)[0-9]*(_.*)?\.({" | ".join(extensions)})" def _get_model_solutions(self) -> list[tuple[ModelSolutionKind, LocalFile]]: """ diff --git a/tests/test_django/test_django/settings.py b/tests/test_django/test_django/settings.py index 68bec3e..f93df2b 100644 --- a/tests/test_django/test_django/settings.py +++ b/tests/test_django/test_django/settings.py @@ -38,6 +38,7 @@ "django.contrib.messages", "django.contrib.staticfiles", # Here add all sio3pack apps + "sio3pack.django.common", "sio3pack.django.sinolpack", ] diff --git a/tests/test_django/test_sio3pack/test_sinolpack.py b/tests/test_django/test_sio3pack/test_sinolpack.py index 5811ec1..77f0789 100644 --- a/tests/test_django/test_sio3pack/test_sinolpack.py +++ b/tests/test_django/test_sio3pack/test_sinolpack.py @@ -1,22 +1,20 @@ import pytest import sio3pack -from sio3pack.django.sinolpack.models import (SinolpackAdditionalFile, - SinolpackConfig, - SinolpackModelSolution, - SinolpackPackage,) +from sio3pack.django.common.models import SIO3Package +from sio3pack.django.sinolpack.models import SinolpackAdditionalFile, SinolpackConfig, SinolpackModelSolution from sio3pack.packages import Sinolpack from tests.fixtures import Compression, PackageInfo, get_archived_package from tests.utils import assert_contents_equal -def _save_and_test_simple(package_info: PackageInfo) -> tuple[Sinolpack, SinolpackPackage]: +def _save_and_test_simple(package_info: PackageInfo) -> tuple[Sinolpack, SIO3Package]: assert package_info.type == "sinolpack" package = sio3pack.from_file(package_info.path) assert isinstance(package, Sinolpack) package.save_to_db(1) - assert SinolpackPackage.objects.filter(problem_id=1).exists() - db_package = SinolpackPackage.objects.get(problem_id=1) + assert SIO3Package.objects.filter(problem_id=1).exists() + db_package = SIO3Package.objects.get(problem_id=1) assert db_package.short_name == package.short_name return package, db_package From 86db194890c60323c37de48a48dbe61b6bb20504 Mon Sep 17 00:00:00 2001 From: Tomasz Kwiatkowski Date: Thu, 12 Dec 2024 11:55:17 +0100 Subject: [PATCH 07/10] Fix string --- src/sio3pack/packages/sinolpack/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sio3pack/packages/sinolpack/model.py b/src/sio3pack/packages/sinolpack/model.py index e605da0..c27d8e9 100644 --- a/src/sio3pack/packages/sinolpack/model.py +++ b/src/sio3pack/packages/sinolpack/model.py @@ -246,7 +246,7 @@ def get_model_solution_regex(self): Returns the regex used to determine model solutions. """ extensions = self.get_submittable_extensions() - return rf"^{self.short_name}[0-9]*([bs]?)[0-9]*(_.*)?\.({" | ".join(extensions)})" + return rf"^{self.short_name}[0-9]*([bs]?)[0-9]*(_.*)?\.({'|'.join(extensions)})" def _get_model_solutions(self) -> list[tuple[ModelSolutionKind, LocalFile]]: """ From 877ece7c701f9f1f7d1f1cadb46f38473bba7d6e Mon Sep 17 00:00:00 2001 From: Tomasz Kwiatkowski Date: Thu, 12 Dec 2024 11:57:40 +0100 Subject: [PATCH 08/10] Change multi line output --- pyproject.toml | 2 +- src/sio3pack/django/sinolpack/handler.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d18517b..46a985f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [tool.isort] line_length = 120 -multi_line_output = 1 +multi_line_output = 3 include_trailing_comma = true skip = ["migrations", ".venv"] diff --git a/src/sio3pack/django/sinolpack/handler.py b/src/sio3pack/django/sinolpack/handler.py index f1081f4..62116b2 100644 --- a/src/sio3pack/django/sinolpack/handler.py +++ b/src/sio3pack/django/sinolpack/handler.py @@ -5,10 +5,12 @@ from django.db import transaction from sio3pack.django.common.handler import DjangoHandler -from sio3pack.django.sinolpack.models import (SinolpackAdditionalFile, - SinolpackAttachment, - SinolpackConfig, - SinolpackModelSolution,) +from sio3pack.django.sinolpack.models import ( + SinolpackAdditionalFile, + SinolpackAttachment, + SinolpackConfig, + SinolpackModelSolution, +) class SinolpackDjangoHandler(DjangoHandler): From 94cab5ea4ae2840298e6e94ca38512f85bab7035 Mon Sep 17 00:00:00 2001 From: jrozek Date: Thu, 19 Dec 2024 13:01:48 +0100 Subject: [PATCH 09/10] reversing changes in pyproject.toml --- pyproject.toml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 46a985f..6961ec6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,14 +6,7 @@ build-backend = "setuptools.build_meta" line_length = 120 multi_line_output = 3 include_trailing_comma = true -skip = ["migrations", ".venv"] [tool.black] line_length = 120 -exclude = ''' -/^( - migrations - | sio3pack$ - | \.venv$ -)/ -''' +exclude = "migrations/" From 169d59b309d1bb6ae61d5e20c5f8f0f0729d8c0e Mon Sep 17 00:00:00 2001 From: jrozek Date: Thu, 19 Dec 2024 13:12:17 +0100 Subject: [PATCH 10/10] fix previous commit --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 6961ec6..3d16678 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ build-backend = "setuptools.build_meta" line_length = 120 multi_line_output = 3 include_trailing_comma = true +skip = ["migrations", ".venv"] [tool.black] line_length = 120