Merge branch 'master' of gitlab.prodcontest.ru:team-15/project

This commit is contained in:
ITQ
2025-03-01 18:19:59 +03:00
33 changed files with 740 additions and 67 deletions
+6 -6
View File
@@ -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))
+16 -1
View File
@@ -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
+8 -8
View File
@@ -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)
+1 -1
View File
@@ -46,7 +46,7 @@ def sign_in(request, data: LoginSchema):
user = User.objects.filter(email=data.email).first()
if not user:
raise AuthenticationError
if user.password != data.password:
if not user.check_password(data.password):
raise AuthenticationError
token = BearerAuth.generate_jwt(user)
@@ -0,0 +1,49 @@
# Generated by Django 5.1.6 on 2025-03-01 14:46
import tinymce.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('competition', '0003_remove_competition_tasks'),
]
operations = [
migrations.AlterField(
model_name='competition',
name='description',
field=tinymce.models.HTMLField(verbose_name='описание'),
),
migrations.AlterField(
model_name='competition',
name='end_date',
field=models.DateTimeField(blank=True, null=True, verbose_name='дедлайн участия'),
),
migrations.AlterField(
model_name='competition',
name='image_url',
field=models.FileField(blank=True, null=True, upload_to='', verbose_name='изображение соревнования'),
),
migrations.AlterField(
model_name='competition',
name='participation_type',
field=models.CharField(choices=[('edu', 'Образовательный'), ('competitive', 'Соревновательный')], max_length=11, verbose_name='тип соревнования'),
),
migrations.AlterField(
model_name='competition',
name='start_date',
field=models.DateTimeField(blank=True, null=True, verbose_name='дедлайн участия'),
),
migrations.AlterField(
model_name='competition',
name='title',
field=models.CharField(max_length=100, verbose_name='аазвание'),
),
migrations.AlterField(
model_name='competition',
name='type',
field=models.CharField(choices=[('solo', 'Индивидуальный')], max_length=10, verbose_name='тип участия'),
),
]
+10 -7
View File
@@ -1,6 +1,7 @@
from datetime import datetime
from django.db import models
from tinymce.models import HTMLField
from apps.core.models import BaseModel
from apps.user.models import User
@@ -17,29 +18,29 @@ class Competition(BaseModel):
def image_url_upload_to(instance, filename):
return f"/competitions/{instance.id}/image"
title = models.CharField(max_length=100, verbose_name="Название")
description = models.TextField(verbose_name="Описание")
title = models.CharField(max_length=100, verbose_name="название")
description = models.TextField(verbose_name="описание")
image_url = models.FileField(
verbose_name="Изображение соревнования",
verbose_name="изображение соревнования",
null=True,
blank=True,
upload_to=image_url_upload_to,
)
end_date = models.DateTimeField(
verbose_name="Дедлайн участия", null=True, blank=True
verbose_name="дедлайн участия", null=True, blank=True
)
start_date = models.DateTimeField(
verbose_name="Дедлайн участия", null=True, blank=True
verbose_name="дедлайн участия", null=True, blank=True
)
type = models.CharField(
max_length=10,
choices=CompetitionType.choices,
verbose_name="Тип участия",
verbose_name="тип участия",
)
participation_type = models.CharField(
max_length=11,
choices=CompetitionParticipationType.choices,
verbose_name="Тип соревнования",
verbose_name="тип соревнования",
)
participants = models.ManyToManyField(
User, related_name="participants", blank=True, editable=False
@@ -48,6 +49,8 @@ class Competition(BaseModel):
def __str__(self):
return self.title
class Meta:
verbose_name = "соревнование"
verbose_name_plural = "соревнования"
@@ -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(
@@ -0,0 +1,26 @@
# Generated by Django 5.1.6 on 2025-03-01 14:47
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('review', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Review',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('state', models.CharField(choices=[('not_checked', 'Not Checked'), ('checking', 'Checking'), ('checked', 'Checked')], max_length=11)),
('reviewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.reviewer')),
],
options={
'abstract': False,
},
),
]
@@ -0,0 +1,20 @@
# Generated by Django 5.1.6 on 2025-03-01 14:47
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('review', '0002_review'),
('task', '0005_alter_competitiontask_description_and_more'),
]
operations = [
migrations.AddField(
model_name='review',
name='submission',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontasksubmission'),
),
]
+12
View File
@@ -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)
@@ -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)),
@@ -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'),
),
]
@@ -0,0 +1,14 @@
# Generated by Django 5.1.6 on 2025-03-01 14:39
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('task', '0002_competetiontasksumbission_reviewers'),
('task', '0003_competitiontask_max_attemps_and_more'),
]
operations = [
]
@@ -0,0 +1,48 @@
# Generated by Django 5.1.6 on 2025-03-01 14:47
import apps.task.models
import django.db.models.deletion
import tinymce.models
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('task', '0004_merge_20250301_1739'),
('user', '0002_alter_user_email_alter_user_password_and_more'),
]
operations = [
migrations.AlterField(
model_name='competitiontask',
name='description',
field=tinymce.models.HTMLField(max_length=300, verbose_name='описание'),
),
migrations.AlterField(
model_name='competitiontask',
name='max_attemps',
field=models.PositiveSmallIntegerField(),
),
migrations.CreateModel(
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)),
('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)),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontask')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.user')),
],
options={
'abstract': False,
},
),
migrations.DeleteModel(
name='CompetetionTaskSumbission',
),
]
+4 -3
View File
@@ -1,6 +1,7 @@
from uuid import uuid4
from django.db import models
from tinymce.models import HTMLField
from apps.competition.models import Competition
from apps.core.models import BaseModel
@@ -19,8 +20,8 @@ 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)
description = HTMLField(verbose_name="описание", max_length=300)
max_attemps = models.PositiveSmallIntegerField()
type = models.CharField(
choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки"
)
@@ -63,7 +64,7 @@ class CompetitionTask(BaseModel):
verbose_name_plural = "задания"
class CompetetionTaskSumbission(BaseModel):
class CompetitionTaskSubmission(BaseModel):
class StatusChoices(models.TextChoices):
SENT = "sent"
CHECKING = "checking"
+9
View File
@@ -0,0 +1,9 @@
from django.contrib import admin
from apps.user.models import User
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
list_display = ("email", "username")
search_fields = ("id", "email", "username")
+1
View File
@@ -5,3 +5,4 @@ class UsersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.user"
label = "user"
verbose_name = "Пользователи"
@@ -0,0 +1,28 @@
# Generated by Django 5.1.6 on 2025-03-01 14:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(max_length=254, unique=True, verbose_name='почта'),
),
migrations.AlterField(
model_name='user',
name='password',
field=models.TextField(verbose_name='пароль'),
),
migrations.AlterField(
model_name='user',
name='username',
field=models.SlugField(unique=True, verbose_name='юзернейм'),
),
]
+10 -3
View File
@@ -1,4 +1,5 @@
from django.db import models
from django.contrib.auth.hashers import check_password, make_password
from apps.core.models import BaseModel
@@ -9,9 +10,15 @@ class UserRole(models.Choices):
class User(BaseModel):
email = models.EmailField(unique=True, verbose_name="Почта")
username = models.SlugField(unique=True, verbose_name="Юзернейм")
password = models.TextField(verbose_name="Пароль")
email = models.EmailField(unique=True, verbose_name="почта")
username = models.SlugField(unique=True, verbose_name="юзернейм")
password = models.TextField(verbose_name="пароль")
def make_password(self):
return make_password(self.password)
def check_password(self, password):
return check_password(self.password, password)
status = models.CharField(
max_length=10, choices=UserRole, default="student"
+21
View File
@@ -441,6 +441,7 @@ INSTALLED_APPS = [
"django_guid",
"ninja",
"minio_storage",
"tinymce",
# Internal apps
"apps.core",
"apps.user",
@@ -449,6 +450,22 @@ INSTALLED_APPS = [
"apps.task",
]
# tinymce
TINYMCE_DEFAULT_CONFIG = {
"theme": "silver",
"height": 500,
"menubar": False,
"plugins": "advlist,autolink,lists,link,image,charmap,print,preview,anchor,"
"searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste,"
"code,help,wordcount",
"toolbar": "undo redo | formatselect | "
"bold italic backcolor | alignleft aligncenter "
"alignright alignjustify | bullist numlist outdent indent | "
"removeformat | help",
"skin": "oxide-dark",
"content_css": "dark"
}
# GUID
DJANGO_GUID = {
@@ -466,6 +483,10 @@ DJANGO_GUID = {
LANGUAGE_COOKIE_AGE = 31449600
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.Argon2PasswordHasher",
]
LANGUAGE_COOKIE_DOMAIN = None
LANGUAGE_COOKIE_HTTPONLY = False
+2
View File
@@ -12,6 +12,8 @@ admin.site.index_title = "DataRush"
urlpatterns = [
# tinymce
path('tinymce/', include('tinymce.urls')),
# Admin urls
path("admin/", admin.site.urls),
# API urls
+3
View File
@@ -4,6 +4,7 @@ version = "0.1.0"
readme = "README.md"
requires-python = ">=3.10,<3.12"
dependencies = [
"argon2-cffi>=23.1.0",
"celery>=5.4.0",
"colorlog>=6.9.0",
"django-cors-headers>=4.6.0",
@@ -13,7 +14,9 @@ dependencies = [
"django-health-check>=3.18.3",
"django-minio-storage>=0.5.7",
"django-ninja>=1.3.0",
"django-pagedown>=2.2.1",
"django-stubs-ext>=5.1.3",
"django-tinymce>=4.1.0",
"gunicorn>=23.0.0",
"httpx>=0.28.1",
"pillow>=11.1.0",