From 5e8742ee108ba6f9c11293c2d1597ded6babb7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=A1=D1=83=D0=BC?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sun, 2 Mar 2025 13:34:54 +0300 Subject: [PATCH] fix: fixed admin bug and added 12rojgpouehgsrfojn --- .../competition/migrations/0001_initial.py | 2 +- .../core/management/commands/generate_data.py | 17 ++++---- .../apps/review/migrations/0001_initial.py | 30 ++++++-------- .../apps/review/migrations/0002_initial.py | 27 ++++++++++++ services/backend/apps/review/models.py | 2 +- services/backend/apps/task/admin.py | 2 +- .../apps/task/migrations/0001_initial.py | 14 ++++--- ...02_competitiontask_attachments_and_more.py | 40 ------------------ ...0003_remove_competitiontask_attachments.py | 17 -------- services/backend/apps/task/models.py | 41 +++++++------------ .../apps/team/migrations/0001_initial.py | 2 +- .../apps/user/migrations/0001_initial.py | 3 +- .../user/migrations/0002_user_created_at.py | 18 -------- 13 files changed, 75 insertions(+), 140 deletions(-) create mode 100644 services/backend/apps/review/migrations/0002_initial.py delete mode 100644 services/backend/apps/task/migrations/0002_competitiontask_attachments_and_more.py delete mode 100644 services/backend/apps/task/migrations/0003_remove_competitiontask_attachments.py delete mode 100644 services/backend/apps/user/migrations/0002_user_created_at.py diff --git a/services/backend/apps/competition/migrations/0001_initial.py b/services/backend/apps/competition/migrations/0001_initial.py index 223f6b8..1ada8da 100644 --- a/services/backend/apps/competition/migrations/0001_initial.py +++ b/services/backend/apps/competition/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 06:13 +# Generated by Django 5.1.6 on 2025-03-02 10:28 import apps.competition.models import datetime diff --git a/services/backend/apps/core/management/commands/generate_data.py b/services/backend/apps/core/management/commands/generate_data.py index bc3b9de..0b8b31c 100644 --- a/services/backend/apps/core/management/commands/generate_data.py +++ b/services/backend/apps/core/management/commands/generate_data.py @@ -20,8 +20,8 @@ class Command(BaseCommand): self.stdout.write("Starting data generation...") users = self.create_users(5) competitions = self.create_competitions(2, users) + self.reviewers = self.create_reviewers(2) tasks = self.create_tasks(competitions) - self.reviewers = self.create_reviewers(1) self.create_submissions(tasks, users) self.create_states(competitions, users) self.stdout.write("Data generation completed.") @@ -108,8 +108,14 @@ class Command(BaseCommand): ) tasks.append(task) self.stdout.write(f"Created task: {title} (type: {task_type})") + self.add_reviewers_to_task(tasks) return tasks + def add_reviewers_to_task(self, tasks): + for task in tasks: + task.reviewers.set(self.reviewers) + task.save() + def create_submissions(self, tasks, users): for task in tasks: # Each task will get between 1 and 3 submissions @@ -133,15 +139,6 @@ class Command(BaseCommand): self.stdout.write( f"Created submission for task '{task.title}' by user '{user.username}'" ) - self.add_reviewers(submission) - - def add_reviewers(self, submission): - for reviewer in self.reviewers: - if random.choice([True, False]): - Review.objects.create( - submission=submission, - reviewer=reviewer, - ) def create_states(self, competitions, users): # For each competition, create a State for some of its participants diff --git a/services/backend/apps/review/migrations/0001_initial.py b/services/backend/apps/review/migrations/0001_initial.py index 70108e7..1d0ac7b 100644 --- a/services/backend/apps/review/migrations/0001_initial.py +++ b/services/backend/apps/review/migrations/0001_initial.py @@ -1,6 +1,5 @@ -# Generated by Django 5.1.6 on 2025-03-02 09:31 +# Generated by Django 5.1.6 on 2025-03-02 10:28 -import django.db.models.deletion import uuid from django.db import migrations, models @@ -10,10 +9,21 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('task', '0003_remove_competitiontask_attachments'), ] operations = [ + migrations.CreateModel( + name='Review', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('evaluation', models.JSONField(blank=True, default=list, null=True, verbose_name='выполнение')), + ('state', models.CharField(choices=[('not_checked', 'Not Checked'), ('checking', 'Checking'), ('checked', 'Checked')], default='not_checked', max_length=11, verbose_name='состояние')), + ], + options={ + 'verbose_name': 'проверка', + 'verbose_name_plural': 'проверки', + }, + ), migrations.CreateModel( name='Reviewer', fields=[ @@ -27,18 +37,4 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'проверяющие', }, ), - migrations.CreateModel( - name='Review', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('evaluation', models.JSONField(blank=True, default=list, null=True, verbose_name='выполнение')), - ('state', models.CharField(choices=[('not_checked', 'Not Checked'), ('checking', 'Checking'), ('checked', 'Checked')], default='not_checked', max_length=11, verbose_name='состояние')), - ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='task.competitiontasksubmission', verbose_name='посылка')), - ('reviewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.reviewer', verbose_name='проверяющий')), - ], - options={ - 'verbose_name': 'проверка', - 'verbose_name_plural': 'проверки', - }, - ), ] diff --git a/services/backend/apps/review/migrations/0002_initial.py b/services/backend/apps/review/migrations/0002_initial.py new file mode 100644 index 0000000..2d6ee5c --- /dev/null +++ b/services/backend/apps/review/migrations/0002_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 5.1.6 on 2025-03-02 10:28 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('review', '0001_initial'), + ('task', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='review', + name='submission', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='task.competitiontasksubmission', verbose_name='посылка'), + ), + migrations.AddField( + model_name='review', + name='reviewer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.reviewer', verbose_name='проверяющий'), + ), + ] diff --git a/services/backend/apps/review/models.py b/services/backend/apps/review/models.py index a7ff21f..5fd4fb9 100644 --- a/services/backend/apps/review/models.py +++ b/services/backend/apps/review/models.py @@ -27,7 +27,7 @@ class Review(BaseModel): reviewer = models.ForeignKey(Reviewer, on_delete=models.CASCADE, verbose_name="проверяющий") submission = models.ForeignKey( - "CompetitionTaskSubmission", + "task.CompetitionTaskSubmission", on_delete=models.CASCADE, related_name="reviews", verbose_name="посылка" diff --git a/services/backend/apps/task/admin.py b/services/backend/apps/task/admin.py index ce7d553..dd3ed25 100644 --- a/services/backend/apps/task/admin.py +++ b/services/backend/apps/task/admin.py @@ -19,7 +19,7 @@ class CompetitionTaskSubmissionAdmin(admin.ModelAdmin): list_display = ("task", "user", "status",) search_fields = ("task__id", "task__title", "user__username", "user__email") filter = ("plagiarism_checked",) - ordering = "-timestamp" + ordering = ["-timestamp"] def has_add_permission(self, request, obj=None): return False diff --git a/services/backend/apps/task/migrations/0001_initial.py b/services/backend/apps/task/migrations/0001_initial.py index cf4fbdc..c2cbaa8 100644 --- a/services/backend/apps/task/migrations/0001_initial.py +++ b/services/backend/apps/task/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 06:13 +# Generated by Django 5.1.6 on 2025-03-02 10:28 import apps.task.models import django.db.models.deletion @@ -13,6 +13,7 @@ class Migration(migrations.Migration): dependencies = [ ('competition', '0001_initial'), + ('review', '0001_initial'), ('user', '0001_initial'), ] @@ -30,6 +31,7 @@ class Migration(migrations.Migration): ('points', models.IntegerField(blank=True, null=True, verbose_name='баллы за задание')), ('answer_file_path', models.TextField(blank=True, default='stdout', help_text='Путь до файла в котором ожидается результат. Пример: stdout или ./output.txt', null=True, verbose_name='куда сделать вывод программы участнику')), ('competition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition')), + ('reviewers', models.ManyToManyField(blank=True, to='review.reviewer')), ], options={ 'verbose_name': 'задание', @@ -40,10 +42,10 @@ class Migration(migrations.Migration): name='CompetitionTaskAttachment', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('file', models.FileField(upload_to=apps.task.models.CompetitionTaskAttachment.file_upload_at)), - ('bind_at', models.FilePathField()), - ('public', models.BooleanField(default=False)), - ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontask')), + ('file', models.FileField(upload_to=apps.task.models.CompetitionTaskAttachment.file_upload_at, verbose_name='файл')), + ('bind_at', models.FilePathField(verbose_name='путь сохранения')), + ('public', models.BooleanField(default=False, verbose_name='публичный')), + ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontask', verbose_name='задание')), ], options={ 'abstract': False, @@ -67,7 +69,7 @@ class Migration(migrations.Migration): name='CompetitionTaskSubmission', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('status', models.CharField(choices=[('sent', 'Sent'), ('checking', 'Checking'), ('checked', 'Checked')], default='sent', max_length=8)), + ('status', models.CharField(choices=[('sent', 'Sent'), ('checking', 'Checking'), ('checked', 'Checked')], default='sent', max_length=8, verbose_name='статус')), ('content', models.FileField(upload_to=apps.task.models.CompetitionTaskSubmission.submission_content_upload_to)), ('stdout', models.FileField(blank=True, null=True, upload_to=apps.task.models.CompetitionTaskSubmission.submission_stdout_upload_to)), ('result', models.JSONField(blank=True, default=None, null=True)), diff --git a/services/backend/apps/task/migrations/0002_competitiontask_attachments_and_more.py b/services/backend/apps/task/migrations/0002_competitiontask_attachments_and_more.py deleted file mode 100644 index 9f88f60..0000000 --- a/services/backend/apps/task/migrations/0002_competitiontask_attachments_and_more.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-02 08:50 - -import apps.task.models -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('task', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='competitiontask', - name='attachments', - field=models.ManyToManyField(blank=True, related_name='tasks_attachments', to='task.competitiontaskattachment'), - ), - migrations.AlterField( - model_name='competitiontaskattachment', - name='bind_at', - field=models.FilePathField(verbose_name='путь сохранения'), - ), - migrations.AlterField( - model_name='competitiontaskattachment', - name='file', - field=models.FileField(upload_to=apps.task.models.CompetitionTaskAttachment.file_upload_at, verbose_name='файл'), - ), - migrations.AlterField( - model_name='competitiontaskattachment', - name='public', - field=models.BooleanField(default=False, verbose_name='публичный'), - ), - migrations.AlterField( - model_name='competitiontaskattachment', - name='task', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontask', verbose_name='задание'), - ), - ] diff --git a/services/backend/apps/task/migrations/0003_remove_competitiontask_attachments.py b/services/backend/apps/task/migrations/0003_remove_competitiontask_attachments.py deleted file mode 100644 index 0e5d430..0000000 --- a/services/backend/apps/task/migrations/0003_remove_competitiontask_attachments.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-02 09:31 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('task', '0002_competitiontask_attachments_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='competitiontask', - name='attachments', - ), - ] diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index ef8e94c..b2741ed 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -6,7 +6,7 @@ from tinymce.models import HTMLField from apps.competition.models import Competition from apps.core.models import BaseModel -from apps.review.models import Review, ReviewStatusChoices +from apps.review.models import Review, ReviewStatusChoices, Reviewer from apps.user.models import User @@ -50,6 +50,9 @@ class CompetitionTask(BaseModel): default="stdout", ) + # only when "review" type + reviewers = models.ManyToManyField(Reviewer, blank=True) + def __str__(self): return self.title @@ -93,10 +96,8 @@ class CompetitionTaskSubmission(BaseModel): def submission_stdout_upload_to(instance, filename) -> str: return f"/submissions/{instance.id}/stdout" - user = models.ForeignKey(User, on_delete=models.CASCADE, - verbose_name="пользователь") - task = models.ForeignKey(CompetitionTask, on_delete=models.CASCADE, - verbose_name="задание") + user = models.ForeignKey(User, on_delete=models.CASCADE) + task = models.ForeignKey(CompetitionTask, on_delete=models.CASCADE) status = models.CharField( choices=StatusChoices.choices, @@ -106,38 +107,24 @@ class CompetitionTaskSubmission(BaseModel): ) # code or text or file - content = models.FileField(upload_to=submission_content_upload_to, - verbose_name="код/файл посылки") + content = models.FileField(upload_to=submission_content_upload_to) # only if task type is checker stdout = models.FileField( - upload_to=submission_stdout_upload_to, null=True, blank=True, - verbose_name="вывод чекера" + upload_to=submission_stdout_upload_to, null=True, blank=True ) # depends on task type: # - input: {"correct": boolean} # - file: {"total_points": integer, "by_criteria": ["criteria_name": integer]} # - code: {"correct": boolean} - result = models.JSONField(default=None, null=True, blank=True, - verbose_name="результат проверки") + result = models.JSONField(default=None, null=True, blank=True) # just more readable result representation, maybe will be calcuated somehow more complex depends on criteria - earned_points = models.IntegerField(null=True, blank=True, - verbose_name="получено баллов") + earned_points = models.IntegerField(null=True, blank=True) - checked_at = models.DateTimeField(null=True, blank=True, - verbose_name="дата и время проверки") - plagiarism_checked = models.BooleanField(default=False, - verbose_name="проверено на плагиат") - timestamp = models.DateTimeField(auto_now_add=True, - verbose_name="дата отправки") - - def __str__(self): - return str(self.id) - - class Meta: - verbose_name = "посылка" - verbose_name_plural = "посылки" + checked_at = models.DateTimeField(null=True, blank=True) + plagiarism_checked = models.BooleanField(default=False) + timestamp = models.DateTimeField(auto_now_add=True) def send_on_review(self): if not self.task.reviewers.exists(): @@ -158,7 +145,7 @@ class CompetitionTaskSubmission(BaseModel): .order_by("pending_count") .first() ) - Review.objects.create( + review = Review.objects.create( reviewer=reviewer, submission=self, ) diff --git a/services/backend/apps/team/migrations/0001_initial.py b/services/backend/apps/team/migrations/0001_initial.py index 27317a3..7e4fca4 100644 --- a/services/backend/apps/team/migrations/0001_initial.py +++ b/services/backend/apps/team/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 06:13 +# Generated by Django 5.1.6 on 2025-03-02 10:28 import django.db.models.deletion import uuid diff --git a/services/backend/apps/user/migrations/0001_initial.py b/services/backend/apps/user/migrations/0001_initial.py index 12a0407..fe09ceb 100644 --- a/services/backend/apps/user/migrations/0001_initial.py +++ b/services/backend/apps/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-03-02 00:16 +# Generated by Django 5.1.6 on 2025-03-02 10:28 import uuid from django.db import migrations, models @@ -19,6 +19,7 @@ class Migration(migrations.Migration): ('email', models.EmailField(max_length=254, unique=True, verbose_name='почта')), ('username', models.SlugField(unique=True, verbose_name='юзернейм')), ('password', models.TextField(verbose_name='пароль')), + ('created_at', models.DateTimeField(auto_now=True)), ('status', models.CharField(choices=[('student', 'Student'), ('metodist', 'Metodist')], default='student', max_length=10)), ], options={ diff --git a/services/backend/apps/user/migrations/0002_user_created_at.py b/services/backend/apps/user/migrations/0002_user_created_at.py deleted file mode 100644 index 83094ec..0000000 --- a/services/backend/apps/user/migrations/0002_user_created_at.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-02 09:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('user', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='created_at', - field=models.DateTimeField(auto_now=True), - ), - ]