feat: added competition logic

This commit is contained in:
Андрей Сумин
2025-03-01 02:33:06 +03:00
parent 930fb23bbc
commit 999f5b0690
9 changed files with 167 additions and 19 deletions
+22 -2
View File
@@ -1,8 +1,9 @@
from typing import Literal
from uuid import UUID from uuid import UUID
from ninja import ModelSchema from ninja import ModelSchema, Schema
from apps.competition.models import Competition from apps.competition.models import Competition, State
class CompetitionOut(ModelSchema): class CompetitionOut(ModelSchema):
@@ -12,12 +13,31 @@ class CompetitionOut(ModelSchema):
model = Competition model = Competition
fields = "__all__" fields = "__all__"
class StateOut(ModelSchema):
class Meta:
model = State
fields = (
"state",
)
class StateIn(Schema):
state: Literal["started", "not_started", "finished"]
class CompetitionListInstanceOut(ModelSchema): class CompetitionListInstanceOut(ModelSchema):
id: UUID id: UUID
is_participating: bool is_participating: bool
completed: bool completed: bool
@staticmethod
def resolve_is_participating(self, context):
user = context["request"].auth
return self.participants.filter(id=user.id).exists()
@staticmethod
def resolve_completed(self, context):
user = context["request"].auth
return State.objects.filter(competition=self, user=user, state="finished").exists()
class Meta: class Meta:
model = Competition model = Competition
fields = ( fields = (
+37 -6
View File
@@ -1,12 +1,14 @@
from http import HTTPStatus as status from http import HTTPStatus as status
from typing import Literal
from uuid import UUID from uuid import UUID
from django.shortcuts import get_object_or_404
from django.http import HttpRequest from django.http import HttpRequest, Http404
from ninja import Router from ninja import Router
import api.v1.schemas as global_schemas import api.v1.schemas as global_schemas
from api.v1.auth import BearerAuth from api.v1.auth import BearerAuth
from api.v1.competition import schemas from api.v1.competition import schemas
from apps.competition.models import Competition, State
router = Router(tags=["competition"]) router = Router(tags=["competition"])
@@ -18,11 +20,12 @@ router = Router(tags=["competition"])
status.BAD_REQUEST: global_schemas.BadRequestError, status.BAD_REQUEST: global_schemas.BadRequestError,
status.UNAUTHORIZED: global_schemas.UnauthorizedError, status.UNAUTHORIZED: global_schemas.UnauthorizedError,
}, },
auth=BearerAuth(),
) )
def get_competition( def get_competition(
request: HttpRequest, competition_id: UUID request: HttpRequest, competition_id: UUID
) -> tuple[status, schemas.CompetitionOut]: ... ) -> tuple[status, schemas.CompetitionOut]:
competition = get_object_or_404(Competition, id=competition_id)
return status.OK, competition
@router.get( @router.get(
@@ -32,8 +35,36 @@ def get_competition(
status.BAD_REQUEST: global_schemas.BadRequestError, status.BAD_REQUEST: global_schemas.BadRequestError,
status.UNAUTHORIZED: global_schemas.UnauthorizedError, status.UNAUTHORIZED: global_schemas.UnauthorizedError,
}, },
auth=BearerAuth(),
) )
def list_competitions( def list_competitions(
request: HttpRequest, is_participating: bool request: HttpRequest, is_participating: bool
) -> tuple[status, list[schemas.CompetitionListInstanceOut]]: ... ) -> tuple[status, list[schemas.CompetitionListInstanceOut]]:
user = request.auth
if is_participating:
competitions = Competition.objects.filter(participants=user)
else:
competitions = Competition.objects.exclude(participants=user)
return status.OK, competitions
@router.post(
"competitions/{competition_id}/state",
response={
status.OK: schemas.StateOut,
status.BAD_REQUEST: global_schemas.BadRequestError,
status.UNAUTHORIZED: global_schemas.UnauthorizedError,
}
)
def change_competition_state(
request: HttpRequest,
competition_id: UUID,
state: schemas.StateIn,
) -> tuple[status, schemas.StateOut]:
user = request.auth
competition = get_object_or_404(Competition, id=competition_id)
state_obj, _ = State.objects.update_or_create(
user=user,
competition=competition,
state=state.state
)
return status.OK, schemas.StateOut.from_orm(state_obj)
+2
View File
@@ -3,6 +3,7 @@ from functools import partial
from ninja import NinjaAPI from ninja import NinjaAPI
from api.v1 import handlers from api.v1 import handlers
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.user.views import router as user_router from api.v1.user.views import router as user_router
@@ -12,6 +13,7 @@ router = NinjaAPI(
version="1", version="1",
description="API docs for DataRush", description="API docs for DataRush",
openapi_url="/docs/openapi.json", openapi_url="/docs/openapi.json",
auth=BearerAuth()
) )
@@ -0,0 +1,19 @@
# 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'),
),
]
@@ -0,0 +1,28 @@
# 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,
},
),
]
+16 -4
View File
@@ -1,18 +1,18 @@
from django.db import models from django.db import models
from apps.core.models import BaseModel from apps.core.models import BaseModel
from apps.user.models import User
class Competition(BaseModel):
class CompetitionType(models.TextChoices): class CompetitionType(models.TextChoices):
SOLO = "solo" SOLO = "solo"
class CompetitionParticipationType(models.TextChoices): class CompetitionParticipationType(models.TextChoices):
EDU = "edu" EDU = "edu"
COMPETITIVE = "competitive" COMPETITIVE = "competitive"
class Competition(BaseModel):
title = models.CharField(max_length=100, verbose_name="Название") title = models.CharField(max_length=100, verbose_name="Название")
description = models.TextField(verbose_name="Описание") description = models.TextField(verbose_name="Описание")
image_url = models.FileField( image_url = models.FileField(
@@ -34,7 +34,19 @@ class Competition(BaseModel):
choices=CompetitionParticipationType.choices, choices=CompetitionParticipationType.choices,
verbose_name="Тип соревнования", verbose_name="Тип соревнования",
) )
participants = models.ManyToManyField(User, related_name="participants")
class Meta: class Meta:
verbose_name = "соревнование" verbose_name = "соревнование"
verbose_name_plural = "соревнования" verbose_name_plural = "соревнования"
class State(BaseModel):
class StateChoices(models.TextChoices):
NOT_STARTED = 'not_started'
STARTED = 'started'
FINISHED = 'finished'
user = models.ForeignKey(User, on_delete=models.CASCADE)
competition = models.ForeignKey(Competition, on_delete=models.CASCADE)
state = models.CharField(choices=StateChoices.choices, max_length=11)
@@ -0,0 +1,18 @@
# 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),
),
]
@@ -0,0 +1,18 @@
# 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),
),
]
+1 -1
View File
@@ -13,7 +13,7 @@ class User(BaseModel):
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(max_length=10, choices=UserRole.choices, default=UserRole.STUDENT) status = models.CharField(max_length=10, choices=UserRole, default="student")
def __str__(self): def __str__(self):
return self.username return self.username