mirror of
https://gitlab.com/megazordpobeda/DataRush.git
synced 2026-05-22 23:17:09 +00:00
Merge branch 'master' of https://gitlab.prodcontest.ru/team-15/project
This commit is contained in:
@@ -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))
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -27,6 +27,7 @@ router = Router(tags=["user"])
|
||||
)
|
||||
def sign_up(request, data: RegisterSchema):
|
||||
user = User(**data.dict())
|
||||
user.password = user.make_password()
|
||||
user.full_clean()
|
||||
user.save()
|
||||
|
||||
@@ -47,7 +48,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,17 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 13:49
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('competition', '0002_competition_tasks_alter_competition_participants_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='competition',
|
||||
name='tasks',
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
@@ -14,26 +15,26 @@ class Competition(BaseModel):
|
||||
EDU = "edu", "Образовательный"
|
||||
COMPETITIVE = "competitive", "Соревновательный"
|
||||
|
||||
title = models.CharField(max_length=100, verbose_name="Название")
|
||||
description = models.TextField(verbose_name="Описание")
|
||||
title = models.CharField(max_length=100, verbose_name="аазвание")
|
||||
description = HTMLField(verbose_name="описание")
|
||||
image_url = models.FileField(
|
||||
verbose_name="Изображение соревнования", null=True, blank=True
|
||||
verbose_name="изображение соревнования", null=True, blank=True
|
||||
)
|
||||
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)
|
||||
@@ -41,6 +42,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(
|
||||
|
||||
@@ -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,51 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-01 13:49
|
||||
|
||||
import apps.task.models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('competition', '0003_remove_competition_tasks'),
|
||||
('task', '0002_alter_competitiontask_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='competitiontask',
|
||||
name='max_attemps',
|
||||
field=models.PositiveSmallIntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontask',
|
||||
name='answer_file_path',
|
||||
field=models.TextField(blank=True, default='stdout', null=True, verbose_name='куда сохранять решения'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontask',
|
||||
name='competition',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontask',
|
||||
name='correct_answer_file',
|
||||
field=models.FileField(blank=True, null=True, upload_to=apps.task.models.CompetitionTask.answer_file_upload_to, verbose_name='файл с правильным ответом'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontask',
|
||||
name='criteries',
|
||||
field=models.JSONField(blank=True, null=True, verbose_name='критерии'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontask',
|
||||
name='title',
|
||||
field=models.CharField(max_length=50, verbose_name='заголовок'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontask',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('input', 'Ввод правильного ответа'), ('checker', 'Вывод кода'), ('review', 'Ручная')], max_length=8, verbose_name='тип проверки'),
|
||||
),
|
||||
]
|
||||
@@ -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,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)
|
||||
description = HTMLField(verbose_name="описание", max_length=300)
|
||||
max_attemps = models.PositiveSmallIntegerField()
|
||||
type = models.CharField(
|
||||
choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки"
|
||||
@@ -50,14 +51,6 @@ 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):
|
||||
@@ -71,7 +64,7 @@ class CompetitionTask(BaseModel):
|
||||
verbose_name_plural = "задания"
|
||||
|
||||
|
||||
class CompetetionTaskSumbission(BaseModel):
|
||||
class CompetitionTaskSubmission(BaseModel):
|
||||
class StatusChoices(models.TextChoices):
|
||||
SENT = "sent"
|
||||
CHECKING = "checking"
|
||||
|
||||
@@ -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")
|
||||
@@ -5,3 +5,4 @@ class UsersConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.user"
|
||||
label = "user"
|
||||
verbose_name = "Пользователи"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +14,7 @@ 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",
|
||||
"gunicorn>=23.0.0",
|
||||
"httpx>=0.28.1",
|
||||
|
||||
Reference in New Issue
Block a user