From 1cd6505f7a369b1bf1feef6b1e4cab5aaf6b607b Mon Sep 17 00:00:00 2001 From: Timur Date: Sat, 1 Mar 2025 16:15:37 +0300 Subject: [PATCH] add basic admin for task --- services/backend/apps/competition/admin.py | 2 + ...alter_competition_participants_and_more.py | 35 +++++++++++++++ services/backend/apps/task/apps.py | 1 + ..._alter_competitiontask_options_and_more.py | 45 +++++++++++++++++++ services/backend/apps/task/models.py | 34 ++++++++++---- 5 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 services/backend/apps/competition/migrations/0002_competition_tasks_alter_competition_participants_and_more.py create mode 100644 services/backend/apps/task/migrations/0002_alter_competitiontask_options_and_more.py diff --git a/services/backend/apps/competition/admin.py b/services/backend/apps/competition/admin.py index 59e6800..2aca32c 100644 --- a/services/backend/apps/competition/admin.py +++ b/services/backend/apps/competition/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from apps.competition.models import Competition +from apps.task.admin import CompetitionTaskInline @admin.register(Competition) @@ -8,3 +9,4 @@ class CompetitionAdmin(admin.ModelAdmin): list_display = ("title", "end_date", "type",) search_fields = ("title", "description",) list_filter = ("type", "participation_type",) + inlines = [CompetitionTaskInline] diff --git a/services/backend/apps/competition/migrations/0002_competition_tasks_alter_competition_participants_and_more.py b/services/backend/apps/competition/migrations/0002_competition_tasks_alter_competition_participants_and_more.py new file mode 100644 index 0000000..5cfcfdf --- /dev/null +++ b/services/backend/apps/competition/migrations/0002_competition_tasks_alter_competition_participants_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 5.1.6 on 2025-03-01 12:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('competition', '0001_initial'), + ('task', '0001_initial'), + ('user', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='competition', + name='tasks', + field=models.ManyToManyField(blank=True, related_name='tasks', to='task.competitiontask'), + ), + migrations.AlterField( + model_name='competition', + name='participants', + field=models.ManyToManyField(blank=True, editable=False, related_name='participants', to='user.user'), + ), + migrations.AlterField( + model_name='competition', + name='participation_type', + field=models.CharField(choices=[('edu', 'Образовательный'), ('competitive', 'Соревновательный')], max_length=11, verbose_name='Тип соревнования'), + ), + migrations.AlterField( + model_name='competition', + name='type', + field=models.CharField(choices=[('solo', 'Индивидуальный')], max_length=10, verbose_name='Тип участия'), + ), + ] diff --git a/services/backend/apps/task/apps.py b/services/backend/apps/task/apps.py index 46f853d..4fe9436 100644 --- a/services/backend/apps/task/apps.py +++ b/services/backend/apps/task/apps.py @@ -4,3 +4,4 @@ from django.apps import AppConfig class CompetitionsConfig(AppConfig): name = "apps.task" label = "task" + verbose_name = "Задания" diff --git a/services/backend/apps/task/migrations/0002_alter_competitiontask_options_and_more.py b/services/backend/apps/task/migrations/0002_alter_competitiontask_options_and_more.py new file mode 100644 index 0000000..4a14698 --- /dev/null +++ b/services/backend/apps/task/migrations/0002_alter_competitiontask_options_and_more.py @@ -0,0 +1,45 @@ +# Generated by Django 5.1.6 on 2025-03-01 12:21 + +import apps.task.models +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('competition', '0002_competition_tasks_alter_competition_participants_and_more'), + ('task', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='competitiontask', + options={'verbose_name': 'задание', 'verbose_name_plural': 'задания'}, + ), + migrations.AlterField( + model_name='competitiontask', + name='competition', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition', verbose_name='соревнование'), + ), + migrations.AlterField( + model_name='competitiontask', + name='correct_answer_file', + field=models.FileField(blank=True, null=True, upload_to=apps.task.models.CompetitionTask.answer_file_upload_to, verbose_name='правильный ответ'), + ), + migrations.AlterField( + model_name='competitiontask', + name='criteries', + field=models.JSONField(blank=True, null=True, verbose_name='критерии проверки'), + ), + migrations.AlterField( + model_name='competitiontask', + name='points', + field=models.IntegerField(blank=True, null=True, verbose_name='баллы за задание'), + ), + migrations.AlterField( + model_name='competitiontask', + name='type', + field=models.CharField(choices=[('input', 'Input'), ('checker', 'Checker'), ('review', 'Review')], max_length=8, verbose_name='тип задания'), + ), + ] diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index 08fe998..bd3511e 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -10,33 +10,49 @@ from apps.user.models import User class CompetitionTask(BaseModel): class CompetitionTaskType(models.TextChoices): - INPUT = "input" - CHECKER = "checker" - REVIEW = "review" + INPUT = "input", "Ввод правильного ответа" + CHECKER = "checker", "Вывод кода" + REVIEW = "review", "Ручная" def answer_file_upload_to(instance, filename) -> str: return f"/tasks/{instance.id}/answer/{uuid4()}/filename" competition = models.ForeignKey(Competition, on_delete=models.CASCADE) - title = models.TextField(verbose_name="заголовок", max_length=50) + title = models.CharField(verbose_name="заголовок", max_length=50) description = models.TextField(verbose_name="описание", max_length=300) - type = models.CharField(choices=CompetitionTaskType, max_length=8) + type = models.CharField(choices=CompetitionTaskType, max_length=8, + verbose_name="тип проверки") # only when "input" or "checker" type correct_answer_file = models.FileField( - upload_to=answer_file_upload_to, null=True, blank=True + upload_to=answer_file_upload_to, null=True, blank=True, + verbose_name="файл с правильным ответом" ) - points = models.IntegerField(null=True, blank=True) + points = models.IntegerField(null=True, blank=True, + verbose_name="баллы за задание") # only when "checker" type - answer_file_path = models.TextField(null=True, blank=True) + answer_file_path = models.TextField(null=True, blank=True, + verbose_name="куда сохранять решения", + default="stdout") # only when "review" type - criteries = models.JSONField(blank=True, null=True) + # todo make it more humanize + criteries = models.JSONField(blank=True, null=True, + verbose_name="критерии", + default=lambda: [{"name": "CHANGE ME", "slug": "CHANGE ME", "max_value": 0, "min_value": 0}] + ) def clean(self): ContestTaskCriteriesValidator()(self) + def __str__(self): + return self.title + + class Meta: + verbose_name = "задание" + verbose_name_plural = "задания" + class CompetetionTaskSumbission(BaseModel): class StatusChoices(models.TextChoices):