mirror of
https://gitlab.com/megazordpobeda/DataRush.git
synced 2026-05-23 17:57:10 +00:00
Merge branch 'feature/review'
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
from abc import ABC
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.urls import resolve
|
||||||
|
from ninja.errors import AuthenticationError
|
||||||
|
from ninja.security import APIKeyQuery
|
||||||
|
from ninja.security.apikey import APIKeyBase
|
||||||
|
|
||||||
|
from apps.review.models import Reviewer
|
||||||
|
|
||||||
|
class APIKeyPath(APIKeyBase, ABC):
|
||||||
|
openapi_in: str = "path"
|
||||||
|
|
||||||
|
def _get_key(self, request: HttpRequest) -> Optional[str]:
|
||||||
|
func, args, kwargs = resolve(request.path)
|
||||||
|
return kwargs.get(self.param_name)
|
||||||
|
|
||||||
|
class ReviewerAuth(APIKeyPath):
|
||||||
|
param_name = "token"
|
||||||
|
|
||||||
|
def authenticate(self, request, token):
|
||||||
|
if not (reviewer := Reviewer.objects.filter(token=token).first()):
|
||||||
|
raise AuthenticationError
|
||||||
|
return reviewer
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
from typing import List, Literal
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from ninja import Schema, ModelSchema
|
||||||
|
|
||||||
|
from apps.review.models import Reviewer
|
||||||
|
from apps.task.models import CompetetionTaskSumbission
|
||||||
|
|
||||||
|
|
||||||
|
class PingOut(Schema):
|
||||||
|
status: str = "ok"
|
||||||
|
|
||||||
|
class ReviewerOut(ModelSchema):
|
||||||
|
id: UUID
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Reviewer
|
||||||
|
exclude = ("token",)
|
||||||
|
|
||||||
|
class SubmissionOut(ModelSchema):
|
||||||
|
id: UUID
|
||||||
|
status: Literal["sent", "checking", "checked"]
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CompetetionTaskSumbission
|
||||||
|
exclude = (
|
||||||
|
"user",
|
||||||
|
)
|
||||||
|
|
||||||
|
class SubmissionsOut(Schema):
|
||||||
|
submissions: list[SubmissionOut] = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_submissions(self, context: HttpRequest) -> List[SubmissionOut]:
|
||||||
|
print(CompetetionTaskSumbission.objects.all())
|
||||||
|
return list(CompetetionTaskSumbission.objects.all())
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from http import HTTPStatus as status
|
||||||
|
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from ninja import Router
|
||||||
|
|
||||||
|
from api.v1.review import schemas
|
||||||
|
from api.v1 import schemas as global_schemas
|
||||||
|
|
||||||
|
router = Router(tags=["review"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"{token}/tasks",
|
||||||
|
response={
|
||||||
|
status.OK: schemas.SubmissionsOut,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def ping(request: HttpRequest, token) -> tuple[status, schemas.SubmissionsOut]:
|
||||||
|
return status.OK, schemas.SubmissionsOut()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"{token}",
|
||||||
|
response={
|
||||||
|
status.OK: schemas.ReviewerOut,
|
||||||
|
status.UNAUTHORIZED: global_schemas.UnauthorizedError
|
||||||
|
},
|
||||||
|
description="token есть и в сваггер авторизации, но оно не работает, не верьте. подставляйте токен вручную в query"
|
||||||
|
)
|
||||||
|
def get_reviewer(
|
||||||
|
request: HttpRequest,
|
||||||
|
token: str
|
||||||
|
):
|
||||||
|
return status.OK, request.auth
|
||||||
@@ -6,8 +6,9 @@ from api.v1 import handlers
|
|||||||
from api.v1.auth import BearerAuth
|
from api.v1.auth import BearerAuth
|
||||||
from api.v1.competition.views import router as competition_router
|
from api.v1.competition.views import router as competition_router
|
||||||
from api.v1.ping.views import router as ping_router
|
from api.v1.ping.views import router as ping_router
|
||||||
from api.v1.task.views import router as task_router
|
from api.v1.review.auth import ReviewerAuth
|
||||||
from api.v1.user.views import router as user_router
|
from api.v1.user.views import router as user_router
|
||||||
|
from api.v1.review.views import router as review_router
|
||||||
|
|
||||||
router = NinjaAPI(
|
router = NinjaAPI(
|
||||||
title="DataRush API",
|
title="DataRush API",
|
||||||
@@ -32,10 +33,12 @@ router.add_router(
|
|||||||
auth=BearerAuth(),
|
auth=BearerAuth(),
|
||||||
)
|
)
|
||||||
router.add_router(
|
router.add_router(
|
||||||
"",
|
"review",
|
||||||
task_router,
|
review_router,
|
||||||
|
auth=ReviewerAuth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for exception, handler in handlers.exception_handlers:
|
for exception, handler in handlers.exception_handlers:
|
||||||
router.add_exception_handler(exception, partial(handler, router=router))
|
router.add_exception_handler(exception, partial(handler, router=router))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Generated by Django 5.1.6 on 2025-02-28 21:27
|
# Generated by Django 5.1.6 on 2025-03-01 08:47
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
import uuid
|
import uuid
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
('user', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
@@ -23,10 +25,23 @@ class Migration(migrations.Migration):
|
|||||||
('start_date', models.DateTimeField(blank=True, null=True, verbose_name='Дедлайн участия')),
|
('start_date', models.DateTimeField(blank=True, null=True, verbose_name='Дедлайн участия')),
|
||||||
('type', models.CharField(choices=[('solo', 'Solo')], max_length=10, verbose_name='Тип участия')),
|
('type', models.CharField(choices=[('solo', 'Solo')], max_length=10, verbose_name='Тип участия')),
|
||||||
('participation_type', models.CharField(choices=[('edu', 'Edu'), ('competitive', 'Competitive')], max_length=11, verbose_name='Тип соревнования')),
|
('participation_type', models.CharField(choices=[('edu', 'Edu'), ('competitive', 'Competitive')], max_length=11, verbose_name='Тип соревнования')),
|
||||||
|
('participants', models.ManyToManyField(related_name='participants', to='user.user')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'соревнование',
|
'verbose_name': 'соревнование',
|
||||||
'verbose_name_plural': 'соревнования',
|
'verbose_name_plural': 'соревнования',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='State',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('state', models.CharField(choices=[('not_started', 'Not Started'), ('started', 'Started'), ('finished', 'Finished')], max_length=11)),
|
||||||
|
('competition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.user')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.1.6 on 2025-02-28 22:40
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('competition', '0001_initial'),
|
|
||||||
('user', '0002_user_status'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='competition',
|
|
||||||
name='participants',
|
|
||||||
field=models.ManyToManyField(related_name='participants', to='user.user'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Generated by Django 5.1.6 on 2025-02-28 23:26
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('competition', '0002_competition_participants'),
|
|
||||||
('user', '0003_alter_user_status'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='State',
|
|
||||||
fields=[
|
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('state', models.CharField(choices=[('not_started', 'Not Started'), ('started', 'Started'), ('finished', 'Finished')], max_length=11)),
|
|
||||||
('competition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.user')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CoreConfig(AppConfig):
|
||||||
|
name = "apps.review"
|
||||||
|
label = "review"
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-01 08:47
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Reviewer',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=100)),
|
||||||
|
('surname', models.CharField(max_length=100)),
|
||||||
|
('token', models.CharField(max_length=100)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from apps.core.models import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Reviewer(BaseModel):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
surname = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
token = models.CharField(max_length=100)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-01 09:42
|
||||||
|
|
||||||
|
import apps.task.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('competition', '0001_initial'),
|
||||||
|
('user', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CompetitionTask',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('title', models.TextField(max_length=50, verbose_name='заголовок')),
|
||||||
|
('description', models.TextField(max_length=300, verbose_name='описание')),
|
||||||
|
('type', models.CharField(choices=[('input', 'Input'), ('checker', 'Checker'), ('review', 'Review')], max_length=8)),
|
||||||
|
('correct_answer_file', models.FileField(upload_to=apps.task.models.CompetitionTask.answer_file_upload_to)),
|
||||||
|
('answer_file_path', models.TextField()),
|
||||||
|
('criteries', models.JSONField(blank=True, null=True)),
|
||||||
|
('competition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CompetetionTaskSumbission',
|
||||||
|
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(upload_to=apps.task.models.CompetetionTaskSumbission.submission_stdout_upload_to)),
|
||||||
|
('result', models.JSONField(default={})),
|
||||||
|
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.user')),
|
||||||
|
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontask')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from random import choice
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -7,7 +8,6 @@ from apps.competition.models import Competition
|
|||||||
from apps.core.models import BaseModel
|
from apps.core.models import BaseModel
|
||||||
from apps.user.models import User
|
from apps.user.models import User
|
||||||
|
|
||||||
|
|
||||||
class CompetitionTask(BaseModel):
|
class CompetitionTask(BaseModel):
|
||||||
class CompetitionTaskType(models.TextChoices):
|
class CompetitionTaskType(models.TextChoices):
|
||||||
INPUT = "input"
|
INPUT = "input"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.1.6 on 2025-02-28 20:46
|
# Generated by Django 5.1.6 on 2025-03-01 08:47
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
@@ -19,6 +19,7 @@ class Migration(migrations.Migration):
|
|||||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='Почта')),
|
('email', models.EmailField(max_length=254, unique=True, verbose_name='Почта')),
|
||||||
('username', models.SlugField(unique=True, verbose_name='Юзернейм')),
|
('username', models.SlugField(unique=True, verbose_name='Юзернейм')),
|
||||||
('password', models.TextField(verbose_name='Пароль')),
|
('password', models.TextField(verbose_name='Пароль')),
|
||||||
|
('status', models.CharField(choices=[('student', 'Student'), ('metodist', 'Metodist')], default='student', max_length=10)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'пользователь',
|
'verbose_name': 'пользователь',
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.6 on 2025-02-28 22:40
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('user', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='status',
|
|
||||||
field=models.CharField(choices=[('student', 'Student'), ('metodist', 'Metodist')], default='student', max_length=10),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.6 on 2025-02-28 22:45
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('user', '0002_user_status'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='user',
|
|
||||||
name='status',
|
|
||||||
field=models.CharField(choices=[('student', 'Student'), ('metodist', 'Metodist')], default='student', max_length=10),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -445,7 +445,8 @@ INSTALLED_APPS = [
|
|||||||
"apps.core",
|
"apps.core",
|
||||||
"apps.user",
|
"apps.user",
|
||||||
"apps.competition",
|
"apps.competition",
|
||||||
"apps.task",
|
"apps.review",
|
||||||
|
"apps.task"
|
||||||
]
|
]
|
||||||
|
|
||||||
# GUID
|
# GUID
|
||||||
|
|||||||
Reference in New Issue
Block a user