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:
+1
-1
@@ -253,7 +253,7 @@ services:
|
|||||||
- name: web
|
- name: web
|
||||||
target: 80
|
target: 80
|
||||||
published: 8003
|
published: 8003
|
||||||
host_ip: 127.0.0.1
|
host_ip: 0.0.0.0
|
||||||
protocol: tcp
|
protocol: tcp
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
secrets:
|
secrets:
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Installing required libs..."
|
|
||||||
pip install -r checker_requirements.txt
|
|
||||||
|
|
||||||
echo "Starting Celery worker..."
|
|
||||||
celery -A config worker -l INFO
|
|
||||||
@@ -1 +1 @@
|
|||||||
admin
|
J2NofXLJa57mpHVQVdNFaltSmg9gjI
|
||||||
@@ -24,6 +24,4 @@ FROM docker.io/nginx:latest
|
|||||||
|
|
||||||
COPY --from=builder /app/static /usr/share/nginx/html
|
COPY --from=builder /app/static /usr/share/nginx/html
|
||||||
|
|
||||||
COPY checker_requirements.txt /usr/share/nginx/html
|
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
@@ -41,7 +41,11 @@ def get_submission(
|
|||||||
review = Review.objects.get(reviewer=reviewer, submission=submission)
|
review = Review.objects.get(reviewer=reviewer, submission=submission)
|
||||||
if review.state == ReviewStatusChoices.NOT_CHECKED.value:
|
if review.state == ReviewStatusChoices.NOT_CHECKED.value:
|
||||||
review.state = ReviewStatusChoices.CHECKING.value
|
review.state = ReviewStatusChoices.CHECKING.value
|
||||||
|
review.submission.state = (
|
||||||
|
CompetitionTaskSubmission.StatusChoices.CHECKING.value
|
||||||
|
)
|
||||||
review.save()
|
review.save()
|
||||||
|
review.submission.save()
|
||||||
|
|
||||||
return status.OK, submission
|
return status.OK, submission
|
||||||
|
|
||||||
@@ -79,13 +83,22 @@ def evaluate_submission(
|
|||||||
evaluation = evaluation_info.dict()["evaluation"]
|
evaluation = evaluation_info.dict()["evaluation"]
|
||||||
review.evaluation = evaluation
|
review.evaluation = evaluation
|
||||||
review.state = ReviewStatusChoices.CHECKED.value
|
review.state = ReviewStatusChoices.CHECKED.value
|
||||||
review.submission.reviewed_at = datetime.now()
|
review.submission.checked_at = datetime.now()
|
||||||
|
|
||||||
points = 0
|
points = 0
|
||||||
for criterea in evaluation:
|
for criterea in evaluation:
|
||||||
points += criterea["mark"]
|
points += criterea["mark"]
|
||||||
review.submission.earned_points = points
|
review.submission.earned_points = (
|
||||||
|
points # TODO: оценка не от последнего проверяющего а средняя по всем
|
||||||
|
)
|
||||||
review.save()
|
review.save()
|
||||||
|
|
||||||
|
all_checked = not submission.reviews.exclude(
|
||||||
|
state=ReviewStatusChoices.CHECKED
|
||||||
|
).exists()
|
||||||
|
if all_checked:
|
||||||
|
review.submission.status = (
|
||||||
|
CompetitionTaskSubmission.StatusChoices.CHECKED.value
|
||||||
|
)
|
||||||
|
review.submission.save()
|
||||||
return status.OK, review.submission
|
return status.OK, review.submission
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from ninja import ModelSchema, Schema
|
from ninja import ModelSchema, Schema
|
||||||
|
|
||||||
from apps.task.models import CompetitionTask, CompetitionTaskSubmission
|
from apps.task.models import CompetitionTask, CompetitionTaskSubmission, CompetitionTaskAttachment
|
||||||
|
|
||||||
|
|
||||||
class TaskOutSchema(ModelSchema):
|
class TaskOutSchema(ModelSchema):
|
||||||
@@ -29,4 +29,10 @@ class HistorySubmissionOut(ModelSchema):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CompetitionTaskSubmission
|
model = CompetitionTaskSubmission
|
||||||
fields = ("id", "earned_points", "timestamp")
|
fields = ("id", "earned_points", "timestamp", "content",)
|
||||||
|
|
||||||
|
|
||||||
|
class TaskAttachmentSchema(ModelSchema):
|
||||||
|
class Meta:
|
||||||
|
model = CompetitionTaskAttachment
|
||||||
|
fields = ("id", "file", "public",)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from api.v1.ping.schemas import PingOut
|
|||||||
from api.v1.schemas import ForbiddenError, NotFoundError, UnauthorizedError
|
from api.v1.schemas import ForbiddenError, NotFoundError, UnauthorizedError
|
||||||
from api.v1.task.schemas import (
|
from api.v1.task.schemas import (
|
||||||
HistorySubmissionOut,
|
HistorySubmissionOut,
|
||||||
|
TaskAttachmentSchema,
|
||||||
TaskOutSchema,
|
TaskOutSchema,
|
||||||
TaskSubmissionOut,
|
TaskSubmissionOut,
|
||||||
)
|
)
|
||||||
@@ -15,6 +16,7 @@ from apps.competition.models import State
|
|||||||
from apps.task.models import (
|
from apps.task.models import (
|
||||||
Competition,
|
Competition,
|
||||||
CompetitionTask,
|
CompetitionTask,
|
||||||
|
CompetitionTaskAttachment,
|
||||||
CompetitionTaskSubmission,
|
CompetitionTaskSubmission,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,6 +115,7 @@ def submit_task(
|
|||||||
status=CompetitionTaskSubmission.StatusChoices.SENT,
|
status=CompetitionTaskSubmission.StatusChoices.SENT,
|
||||||
content=content,
|
content=content,
|
||||||
)
|
)
|
||||||
|
submission.send_on_review()
|
||||||
if task.type == CompetitionTask.CompetitionTaskType.CHECKER:
|
if task.type == CompetitionTask.CompetitionTaskType.CHECKER:
|
||||||
submission = CompetitionTaskSubmission.objects.create(
|
submission = CompetitionTaskSubmission.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
@@ -140,3 +143,17 @@ def get_submissions_history(request, competition_id: UUID, task_id: UUID):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return status.OK, submissions_history
|
return status.OK, submissions_history
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"competitions/{competition_id}/tasks/{task_id}/attachments",
|
||||||
|
response={
|
||||||
|
status.OK: list[TaskAttachmentSchema],
|
||||||
|
status.UNAUTHORIZED: UnauthorizedError,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def get_task_attachments(request, competition_id: UUID, task_id: UUID):
|
||||||
|
task = get_object_or_404(CompetitionTask, id=task_id)
|
||||||
|
return status.OK, CompetitionTaskAttachment.objects.filter(
|
||||||
|
competition_id=competition_id, task=task, user=request.auth
|
||||||
|
)
|
||||||
|
|||||||
@@ -22,4 +22,4 @@ class LoginSchema(ModelSchema):
|
|||||||
class UserSchema(ModelSchema):
|
class UserSchema(ModelSchema):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ["id", "email", "username"]
|
fields = ["id", "email", "username", "created_at",]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from datetime import datetime
|
||||||
from http import HTTPStatus as status
|
from http import HTTPStatus as status
|
||||||
|
|
||||||
from django.contrib.auth.hashers import check_password, make_password
|
from django.contrib.auth.hashers import check_password, make_password
|
||||||
@@ -35,6 +36,7 @@ router = Router(tags=["user"])
|
|||||||
def sign_up(request, data: RegisterSchema):
|
def sign_up(request, data: RegisterSchema):
|
||||||
user = User(**data.dict(exclude={"password"}))
|
user = User(**data.dict(exclude={"password"}))
|
||||||
user.password = make_password(data.password)
|
user.password = make_password(data.password)
|
||||||
|
user.created_at = datetime.now()
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
token = BearerAuth.generate_jwt(user)
|
token = BearerAuth.generate_jwt(user)
|
||||||
|
|||||||
@@ -2,4 +2,3 @@ from django.contrib import admin
|
|||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
|
|
||||||
admin.site.unregister(Group)
|
admin.site.unregister(Group)
|
||||||
admin.site.unregister(User)
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from apps.review.models import Review, Reviewer
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Reviewer)
|
||||||
|
class ReviewAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("name", "surname",)
|
||||||
|
search_fields = ("name", "surname",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Review)
|
||||||
|
class ReviewAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "reviewer", "submission",)
|
||||||
|
search_fields = ("id", "reviewer__id", "reviewer__name", "reviewer__surname",
|
||||||
|
"submission__id", "submission__content")
|
||||||
|
list_filter = ("submission__plagiarism_checked", "submission__status",)
|
||||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
|||||||
class CoreConfig(AppConfig):
|
class CoreConfig(AppConfig):
|
||||||
name = "apps.review"
|
name = "apps.review"
|
||||||
label = "review"
|
label = "review"
|
||||||
|
verbose_name = "Проверка"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Generated by Django 5.1.6 on 2025-03-02 06:13
|
# Generated by Django 5.1.6 on 2025-03-02 09:31
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
import uuid
|
import uuid
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
@@ -9,30 +10,35 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
('task', '0003_remove_competitiontask_attachments'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
|
||||||
name='Review',
|
|
||||||
fields=[
|
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('evaluation', models.JSONField(blank=True, default=list, null=True)),
|
|
||||||
('state', models.CharField(choices=[('not_checked', 'Not Checked'), ('checking', 'Checking'), ('checked', 'Checked')], default='not_checked', max_length=11)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Reviewer',
|
name='Reviewer',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
('name', models.CharField(max_length=100)),
|
('name', models.CharField(max_length=100, verbose_name='имя')),
|
||||||
('surname', models.CharField(max_length=100)),
|
('surname', models.CharField(max_length=100, verbose_name='фамилия')),
|
||||||
('token', models.CharField(max_length=100)),
|
('token', models.CharField(max_length=100, verbose_name='токен для входа')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'verbose_name': 'проверяющий',
|
||||||
|
'verbose_name_plural': 'проверяющие',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Review',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('evaluation', models.JSONField(blank=True, default=list, null=True, verbose_name='выполнение')),
|
||||||
|
('state', models.CharField(choices=[('not_checked', 'Not Checked'), ('checking', 'Checking'), ('checked', 'Checked')], default='not_checked', max_length=11, verbose_name='состояние')),
|
||||||
|
('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='task.competitiontasksubmission', verbose_name='посылка')),
|
||||||
|
('reviewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.reviewer', verbose_name='проверяющий')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'проверка',
|
||||||
|
'verbose_name_plural': 'проверки',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
# Generated by Django 5.1.6 on 2025-03-02 06:13
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('review', '0001_initial'),
|
|
||||||
('task', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='review',
|
|
||||||
name='submission',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='task.competitiontasksubmission'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='review',
|
|
||||||
name='reviewer',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.reviewer'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,14 +1,20 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from apps.core.models import BaseModel
|
from apps.core.models import BaseModel
|
||||||
from apps.task.models import CompetitionTaskSubmission
|
|
||||||
|
|
||||||
|
|
||||||
class Reviewer(BaseModel):
|
class Reviewer(BaseModel):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100, verbose_name="имя")
|
||||||
surname = models.CharField(max_length=100)
|
surname = models.CharField(max_length=100, verbose_name="фамилия")
|
||||||
|
|
||||||
token = models.CharField(max_length=100)
|
token = models.CharField(max_length=100, verbose_name="токен для входа")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name + " " + self.surname
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "проверяющий"
|
||||||
|
verbose_name_plural = "проверяющие"
|
||||||
|
|
||||||
|
|
||||||
class ReviewStatusChoices(models.TextChoices):
|
class ReviewStatusChoices(models.TextChoices):
|
||||||
@@ -18,16 +24,27 @@ class ReviewStatusChoices(models.TextChoices):
|
|||||||
|
|
||||||
|
|
||||||
class Review(BaseModel):
|
class Review(BaseModel):
|
||||||
reviewer = models.ForeignKey(Reviewer, on_delete=models.CASCADE)
|
reviewer = models.ForeignKey(Reviewer, on_delete=models.CASCADE,
|
||||||
|
verbose_name="проверяющий")
|
||||||
submission = models.ForeignKey(
|
submission = models.ForeignKey(
|
||||||
CompetitionTaskSubmission,
|
"CompetitionTaskSubmission",
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="reviews",
|
related_name="reviews",
|
||||||
|
verbose_name="посылка"
|
||||||
)
|
)
|
||||||
|
|
||||||
evaluation = models.JSONField(default=list, null=True, blank=True)
|
evaluation = models.JSONField(default=list, null=True, blank=True,
|
||||||
|
verbose_name="выполнение")
|
||||||
state = models.CharField(
|
state = models.CharField(
|
||||||
choices=ReviewStatusChoices.choices,
|
choices=ReviewStatusChoices.choices,
|
||||||
default=ReviewStatusChoices.NOT_CHECKED.value,
|
default=ReviewStatusChoices.NOT_CHECKED.value,
|
||||||
max_length=11,
|
max_length=11,
|
||||||
|
verbose_name="состояние"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.id)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "проверка"
|
||||||
|
verbose_name_plural = "проверки"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from apps.task.models import CompetitionTask, CompetitionTaskAttachment
|
from apps.task.models import CompetitionTask, CompetitionTaskAttachment, \
|
||||||
|
CompetitionTaskSubmission
|
||||||
|
|
||||||
|
|
||||||
class CompletionAttachmentInline(admin.StackedInline):
|
class CompletionAttachmentInline(admin.StackedInline):
|
||||||
@@ -11,7 +12,20 @@ class CompletionAttachmentInline(admin.StackedInline):
|
|||||||
@admin.register(CompetitionTask)
|
@admin.register(CompetitionTask)
|
||||||
class CompetitionTaskAdmin(admin.ModelAdmin):
|
class CompetitionTaskAdmin(admin.ModelAdmin):
|
||||||
list_display = ("title", "type", "points")
|
list_display = ("title", "type", "points")
|
||||||
inlines = [CompletionAttachmentInline]
|
|
||||||
|
|
||||||
|
@admin.register(CompetitionTaskSubmission)
|
||||||
|
class CompetitionTaskSubmissionAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("task", "user", "status",)
|
||||||
|
search_fields = ("task__id", "task__title", "user__username", "user__email")
|
||||||
|
filter = ("plagiarism_checked",)
|
||||||
|
ordering = "-timestamp"
|
||||||
|
|
||||||
|
def has_add_permission(self, request, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class CompetitionTaskInline(admin.StackedInline):
|
class CompetitionTaskInline(admin.StackedInline):
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-02 08:50
|
||||||
|
|
||||||
|
import apps.task.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('task', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='competitiontask',
|
||||||
|
name='attachments',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='tasks_attachments', to='task.competitiontaskattachment'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='competitiontaskattachment',
|
||||||
|
name='bind_at',
|
||||||
|
field=models.FilePathField(verbose_name='путь сохранения'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='competitiontaskattachment',
|
||||||
|
name='file',
|
||||||
|
field=models.FileField(upload_to=apps.task.models.CompetitionTaskAttachment.file_upload_at, verbose_name='файл'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='competitiontaskattachment',
|
||||||
|
name='public',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='публичный'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='competitiontaskattachment',
|
||||||
|
name='task',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontask', verbose_name='задание'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-02 09:31
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('task', '0002_competitiontask_attachments_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='competitiontask',
|
||||||
|
name='attachments',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Count, Q
|
||||||
from tinymce.models import HTMLField
|
from tinymce.models import HTMLField
|
||||||
|
|
||||||
from apps.competition.models import Competition
|
from apps.competition.models import Competition
|
||||||
from apps.core.models import BaseModel
|
from apps.core.models import BaseModel
|
||||||
|
from apps.review.models import Review, ReviewStatusChoices
|
||||||
from apps.user.models import User
|
from apps.user.models import User
|
||||||
|
|
||||||
|
|
||||||
@@ -71,10 +73,12 @@ class CompetitionTaskAttachment(BaseModel):
|
|||||||
def file_upload_at(instance, filename):
|
def file_upload_at(instance, filename):
|
||||||
return f"/attachment/{instance.id}/file"
|
return f"/attachment/{instance.id}/file"
|
||||||
|
|
||||||
task = models.ForeignKey(CompetitionTask, on_delete=models.CASCADE)
|
task = models.ForeignKey(CompetitionTask, on_delete=models.CASCADE,
|
||||||
file = models.FileField(upload_to=file_upload_at)
|
verbose_name="задание")
|
||||||
bind_at = models.FilePathField()
|
file = models.FileField(upload_to=file_upload_at,
|
||||||
public = models.BooleanField(default=False)
|
verbose_name="файл")
|
||||||
|
bind_at = models.FilePathField(verbose_name="путь сохранения")
|
||||||
|
public = models.BooleanField(default=False, verbose_name="публичный")
|
||||||
|
|
||||||
|
|
||||||
class CompetitionTaskSubmission(BaseModel):
|
class CompetitionTaskSubmission(BaseModel):
|
||||||
@@ -89,31 +93,72 @@ class CompetitionTaskSubmission(BaseModel):
|
|||||||
def submission_stdout_upload_to(instance, filename) -> str:
|
def submission_stdout_upload_to(instance, filename) -> str:
|
||||||
return f"/submissions/{instance.id}/stdout"
|
return f"/submissions/{instance.id}/stdout"
|
||||||
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE,
|
||||||
task = models.ForeignKey(CompetitionTask, on_delete=models.CASCADE)
|
verbose_name="пользователь")
|
||||||
|
task = models.ForeignKey(CompetitionTask, on_delete=models.CASCADE,
|
||||||
|
verbose_name="задание")
|
||||||
|
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
choices=StatusChoices.choices,
|
choices=StatusChoices.choices,
|
||||||
default=StatusChoices.SENT,
|
default=StatusChoices.SENT,
|
||||||
max_length=8,
|
max_length=8,
|
||||||
|
verbose_name="статус"
|
||||||
)
|
)
|
||||||
|
|
||||||
# code or text or file
|
# code or text or file
|
||||||
content = models.FileField(upload_to=submission_content_upload_to)
|
content = models.FileField(upload_to=submission_content_upload_to,
|
||||||
|
verbose_name="код/файл посылки")
|
||||||
|
|
||||||
# only if task type is checker
|
# only if task type is checker
|
||||||
stdout = models.FileField(
|
stdout = models.FileField(
|
||||||
upload_to=submission_stdout_upload_to, null=True, blank=True
|
upload_to=submission_stdout_upload_to, null=True, blank=True,
|
||||||
|
verbose_name="вывод чекера"
|
||||||
)
|
)
|
||||||
|
|
||||||
# depends on task type:
|
# depends on task type:
|
||||||
# - input: {"correct": boolean}
|
# - input: {"correct": boolean}
|
||||||
# - file: {"total_points": integer, "by_criteria": ["criteria_name": integer]}
|
# - file: {"total_points": integer, "by_criteria": ["criteria_name": integer]}
|
||||||
# - code: {"correct": boolean}
|
# - code: {"correct": boolean}
|
||||||
result = models.JSONField(default=None, null=True, blank=True)
|
result = models.JSONField(default=None, null=True, blank=True,
|
||||||
|
verbose_name="результат проверки")
|
||||||
# just more readable result representation, maybe will be calcuated somehow more complex depends on criteria
|
# just more readable result representation, maybe will be calcuated somehow more complex depends on criteria
|
||||||
earned_points = models.IntegerField(null=True, blank=True)
|
earned_points = models.IntegerField(null=True, blank=True,
|
||||||
|
verbose_name="получено баллов")
|
||||||
|
|
||||||
checked_at = models.DateTimeField(null=True, blank=True)
|
checked_at = models.DateTimeField(null=True, blank=True,
|
||||||
plagiarism_checked = models.BooleanField(default=False)
|
verbose_name="дата и время проверки")
|
||||||
timestamp = models.DateTimeField(auto_now_add=True)
|
plagiarism_checked = models.BooleanField(default=False,
|
||||||
|
verbose_name="проверено на плагиат")
|
||||||
|
timestamp = models.DateTimeField(auto_now_add=True,
|
||||||
|
verbose_name="дата отправки")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.id)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "посылка"
|
||||||
|
verbose_name_plural = "посылки"
|
||||||
|
|
||||||
|
def send_on_review(self):
|
||||||
|
if not self.task.reviewers.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
reviewer = (
|
||||||
|
self.task.reviewers.annotate(
|
||||||
|
pending_count=Count(
|
||||||
|
"review",
|
||||||
|
filter=Q(
|
||||||
|
review__state__in=[
|
||||||
|
ReviewStatusChoices.NOT_CHECKED,
|
||||||
|
ReviewStatusChoices.CHECKING,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by("pending_count")
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
Review.objects.create(
|
||||||
|
reviewer=reviewer,
|
||||||
|
submission=self,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from apps.core.models import BaseModel
|
from apps.core.models import BaseModel
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-02 09:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('user', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -14,6 +14,8 @@ 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="пароль")
|
||||||
|
|
||||||
|
created_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def make_password(password: str):
|
def make_password(password: str):
|
||||||
return make_password(password)
|
return make_password(password)
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
pandas==2.2.3
|
|
||||||
numpy==2.2.3
|
|
||||||
matplotlib==3.10.1
|
|
||||||
scipy==1.15.2
|
|
||||||
scikit-learn==1.6.1
|
|
||||||
seaborn==0.13.2
|
|
||||||
statsmodels==0.14.4
|
|
||||||
Generated
+5921
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user