From 332ca694a89ed1f3391e9ef0ff45a0323041215c 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: Sat, 1 Mar 2025 17:20:45 +0300 Subject: [PATCH] feat: added review logic --- services/backend/api/v1/review/schemas.py | 12 ++++++------ services/backend/api/v1/review/views.py | 17 ++++++++++++++++- services/backend/api/v1/task/views.py | 16 ++++++++-------- services/backend/apps/competition/models.py | 2 ++ .../core/management/commands/generate_data.py | 4 ++-- services/backend/apps/review/models.py | 12 ++++++++++++ .../apps/task/migrations/0001_initial.py | 4 ++-- ...002_competetiontasksumbission_reviewers.py | 19 +++++++++++++++++++ services/backend/apps/task/models.py | 12 ++++++++++-- 9 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 services/backend/apps/task/migrations/0002_competetiontasksumbission_reviewers.py diff --git a/services/backend/api/v1/review/schemas.py b/services/backend/api/v1/review/schemas.py index 70bf9b3..831550b 100644 --- a/services/backend/api/v1/review/schemas.py +++ b/services/backend/api/v1/review/schemas.py @@ -4,8 +4,8 @@ from uuid import UUID from django.http import HttpRequest from ninja import ModelSchema, Schema -from apps.review.models import Reviewer -from apps.task.models import CompetetionTaskSumbission +from apps.review.models import Reviewer, Review +from apps.task.models import CompetitionTaskSubmission class PingOut(Schema): @@ -25,13 +25,13 @@ class SubmissionOut(ModelSchema): status: Literal["sent", "checking", "checked"] class Meta: - model = CompetetionTaskSumbission + model = CompetitionTaskSubmission exclude = ("user",) class SubmissionsOut(Schema): - submissions: list[SubmissionOut] = [] + submissions: list = None @staticmethod - def resolve_submissions(self, context: HttpRequest) -> list[SubmissionOut]: - return list(CompetetionTaskSumbission.objects.all()) + def resolve_submissions(self, context) -> list[SubmissionOut]: + return list(Review.objects.filter(reviewer=context.get("request").auth)) \ No newline at end of file diff --git a/services/backend/api/v1/review/views.py b/services/backend/api/v1/review/views.py index d628faf..66f608e 100644 --- a/services/backend/api/v1/review/views.py +++ b/services/backend/api/v1/review/views.py @@ -1,10 +1,15 @@ +import logging from http import HTTPStatus as status +from uuid import UUID from django.http import HttpRequest +from django.shortcuts import get_object_or_404 from ninja import Router from api.v1 import schemas as global_schemas from api.v1.review import schemas +from api.v1.task.schemas import TaskSubmissionIn +from apps.task.models import CompetitionTaskSubmission router = Router(tags=["review"]) @@ -16,7 +21,7 @@ router = Router(tags=["review"]) }, description="Список отправок, на проверку которых назначен ревьюер" ) -def get_submissions(request: HttpRequest, token) -> tuple[status, schemas.SubmissionsOut]: +def get_submissions(request: HttpRequest, token: str) -> tuple[status, schemas.SubmissionsOut]: return status.OK, schemas.SubmissionsOut() @@ -30,3 +35,13 @@ def get_submissions(request: HttpRequest, token) -> tuple[status, schemas.Submis ) def get_reviewer_profile(request: HttpRequest, token: str): return status.OK, request.auth + +@router.get( + "{token}/submissions/{submition_id}", + response={ + status.OK: schemas.SubmissionOut, + }, +) +def get_submission(request: HttpRequest, token: str, submition_id: UUID) -> tuple[status, schemas.SubmissionsOut]: + submission = get_object_or_404(CompetitionTaskSubmission, id=submition_id) + return status.OK, submission diff --git a/services/backend/api/v1/task/views.py b/services/backend/api/v1/task/views.py index 208dd36..85d3cc1 100644 --- a/services/backend/api/v1/task/views.py +++ b/services/backend/api/v1/task/views.py @@ -13,7 +13,7 @@ from api.v1.task.schemas import ( ) from apps.competition.models import State from apps.task.models import ( - CompetetionTaskSumbission, + CompetitionTaskSubmission, Competition, CompetitionTask, ) @@ -96,23 +96,23 @@ def submit_task( ) if task.type == CompetitionTask.CompetitionTaskType.INPUT: - CompetetionTaskSumbission.objects.create( + CompetitionTaskSubmission.objects.create( user=user, task=task, - status=CompetetionTaskSumbission.StatusChoices.CHECKED, + status=CompetitionTaskSubmission.StatusChoices.CHECKED, result={"correct": submission.content == task.answer_file_path}, ) if task.type == CompetitionTask.CompetitionTaskType.REVIEW: - CompetetionTaskSumbission.objects.create( + CompetitionTaskSubmission.objects.create( user=user, task=task, - status=CompetetionTaskSumbission.StatusChoices.SENT, + status=CompetitionTaskSubmission.StatusChoices.SENT, ) if task.type == CompetitionTask.CompetitionTaskType.CHECKER: - CompetetionTaskSumbission.objects.create( + CompetitionTaskSubmission.objects.create( user=user, task=task, - status=CompetetionTaskSumbission.StatusChoices.CHECKING, + status=CompetitionTaskSubmission.StatusChoices.CHECKING, ) - return TaskSubmissionOut(id=CompetetionTaskSumbission.id) + return TaskSubmissionOut(id=CompetitionTaskSubmission.id) diff --git a/services/backend/apps/competition/models.py b/services/backend/apps/competition/models.py index 0c6bd19..bd52ab7 100644 --- a/services/backend/apps/competition/models.py +++ b/services/backend/apps/competition/models.py @@ -41,6 +41,8 @@ class Competition(BaseModel): def __str__(self): return self.title + + class Meta: verbose_name = "соревнование" verbose_name_plural = "соревнования" diff --git a/services/backend/apps/core/management/commands/generate_data.py b/services/backend/apps/core/management/commands/generate_data.py index c781811..479fd23 100644 --- a/services/backend/apps/core/management/commands/generate_data.py +++ b/services/backend/apps/core/management/commands/generate_data.py @@ -8,7 +8,7 @@ from django.core.management.base import BaseCommand from django.utils import timezone from apps.competition.models import Competition, State -from apps.task.models import CompetetionTaskSumbission, CompetitionTask +from apps.task.models import CompetitionTaskSubmission, CompetitionTask from apps.user.models import User, UserRole @@ -105,7 +105,7 @@ class Command(BaseCommand): b"Submission content", name=f"submission_{uuid.uuid4().hex}.txt", ) - submission = CompetetionTaskSumbission.objects.create( + submission = CompetitionTaskSubmission.objects.create( user=user, task=task, earned_points=random.randint( diff --git a/services/backend/apps/review/models.py b/services/backend/apps/review/models.py index 02b74af..60eb198 100644 --- a/services/backend/apps/review/models.py +++ b/services/backend/apps/review/models.py @@ -1,6 +1,7 @@ from django.db import models from apps.core.models import BaseModel +from apps.task.models import CompetitionTaskSubmission class Reviewer(BaseModel): @@ -8,3 +9,14 @@ class Reviewer(BaseModel): surname = models.CharField(max_length=100) token = models.CharField(max_length=100) + +class Review(BaseModel): + class ReviewStatusChoices(models.TextChoices): + NOT_CHECKED = "not_checked" + CHECKING = "checking" + CHECKED = "checked" + + reviewer = models.ForeignKey(Reviewer, on_delete=models.CASCADE) + submission = models.ForeignKey(CompetitionTaskSubmission, on_delete=models.CASCADE) + + state = models.CharField(choices=ReviewStatusChoices.choices, max_length=11) \ No newline at end of file diff --git a/services/backend/apps/task/migrations/0001_initial.py b/services/backend/apps/task/migrations/0001_initial.py index c3e32f9..e65c59e 100644 --- a/services/backend/apps/task/migrations/0001_initial.py +++ b/services/backend/apps/task/migrations/0001_initial.py @@ -38,8 +38,8 @@ class Migration(migrations.Migration): 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)), - ('content', models.FileField(upload_to=apps.task.models.CompetetionTaskSumbission.submission_content_upload_to)), - ('stdout', models.FileField(blank=True, null=True, upload_to=apps.task.models.CompetetionTaskSumbission.submission_stdout_upload_to)), + ('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)), ('earned_points', models.IntegerField()), ('timestamp', models.DateTimeField(auto_now_add=True)), diff --git a/services/backend/apps/task/migrations/0002_competetiontasksumbission_reviewers.py b/services/backend/apps/task/migrations/0002_competetiontasksumbission_reviewers.py new file mode 100644 index 0000000..bda15a2 --- /dev/null +++ b/services/backend/apps/task/migrations/0002_competetiontasksumbission_reviewers.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.6 on 2025-03-01 12:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('review', '0001_initial'), + ('task', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='competetiontasksumbission', + name='reviewers', + field=models.ManyToManyField(blank=True, related_name='reviewers', to='review.reviewer'), + ), + ] diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index 72bb349..f9b9a4c 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -20,7 +20,7 @@ class CompetitionTask(BaseModel): competition = models.ForeignKey(Competition, on_delete=models.CASCADE) title = models.CharField(verbose_name="заголовок", max_length=50) description = models.TextField(verbose_name="описание", max_length=300) - max_attemps = models.PositiveSmallIntegerField(default=0) + max_attemps = models.PositiveSmallIntegerField() type = models.CharField( choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки" ) @@ -50,6 +50,14 @@ class CompetitionTask(BaseModel): blank=True, null=True, verbose_name="критерии", + default=lambda: [ + { + "name": "CHANGE ME", + "slug": "CHANGE ME", + "max_value": 0, + "min_value": 0, + } + ], ) def clean(self): @@ -63,7 +71,7 @@ class CompetitionTask(BaseModel): verbose_name_plural = "задания" -class CompetetionTaskSumbission(BaseModel): +class CompetitionTaskSubmission(BaseModel): class StatusChoices(models.TextChoices): SENT = "sent" CHECKING = "checking"