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 ninja import ModelSchema
from ninja import ModelSchema, Schema
from apps.competition.models import Competition
from apps.competition.models import Competition, State
class CompetitionOut(ModelSchema):
@@ -12,12 +13,31 @@ class CompetitionOut(ModelSchema):
model = Competition
fields = "__all__"
class StateOut(ModelSchema):
class Meta:
model = State
fields = (
"state",
)
class StateIn(Schema):
state: Literal["started", "not_started", "finished"]
class CompetitionListInstanceOut(ModelSchema):
id: UUID
is_participating: 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:
model = Competition
fields = (
+37 -6
View File
@@ -1,12 +1,14 @@
from http import HTTPStatus as status
from typing import Literal
from uuid import UUID
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.http import HttpRequest, Http404
from ninja import Router
import api.v1.schemas as global_schemas
from api.v1.auth import BearerAuth
from api.v1.competition import schemas
from apps.competition.models import Competition, State
router = Router(tags=["competition"])
@@ -18,11 +20,12 @@ router = Router(tags=["competition"])
status.BAD_REQUEST: global_schemas.BadRequestError,
status.UNAUTHORIZED: global_schemas.UnauthorizedError,
},
auth=BearerAuth(),
)
def get_competition(
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(
@@ -32,8 +35,36 @@ def get_competition(
status.BAD_REQUEST: global_schemas.BadRequestError,
status.UNAUTHORIZED: global_schemas.UnauthorizedError,
},
auth=BearerAuth(),
)
def list_competitions(
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 api.v1 import handlers
from api.v1.auth import BearerAuth
from api.v1.competition.views import router as competition_router
from api.v1.ping.views import router as ping_router
from api.v1.user.views import router as user_router
@@ -12,6 +13,7 @@ router = NinjaAPI(
version="1",
description="API docs for DataRush",
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,
},
),
]
+14 -2
View File
@@ -1,8 +1,9 @@
from django.db import models
from apps.core.models import BaseModel
from apps.user.models import User
class Competition(BaseModel):
class CompetitionType(models.TextChoices):
SOLO = "solo"
@@ -12,7 +13,6 @@ class CompetitionParticipationType(models.TextChoices):
COMPETITIVE = "competitive"
class Competition(BaseModel):
title = models.CharField(max_length=100, verbose_name="Название")
description = models.TextField(verbose_name="Описание")
image_url = models.FileField(
@@ -34,7 +34,19 @@ class Competition(BaseModel):
choices=CompetitionParticipationType.choices,
verbose_name="Тип соревнования",
)
participants = models.ManyToManyField(User, related_name="participants")
class Meta:
verbose_name = "соревнование"
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="Юзернейм")
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):
return self.username