mirror of
https://gitlab.com/megazordpobeda/DataRush.git
synced 2026-05-22 23:17:09 +00:00
00121fff89
[body] [footer(s)]
219 lines
7.8 KiB
Python
219 lines
7.8 KiB
Python
from uuid import uuid4
|
|
|
|
from django.db import models
|
|
from django.db.models import Count, Q
|
|
from mdeditor.fields import MDTextField
|
|
|
|
from apps.competition.models import Competition
|
|
from apps.core.models import BaseModel
|
|
from apps.review.models import Review, Reviewer, ReviewStatusChoices
|
|
from apps.user.models import User
|
|
|
|
|
|
class CompetitionTask(BaseModel):
|
|
class CompetitionTaskType(models.TextChoices):
|
|
INPUT = "input", "Ввод правильного ответа"
|
|
CHECKER = "checker", "Ввод кода"
|
|
REVIEW = "review", "Ручная"
|
|
|
|
def answer_file_upload_to(instance, filename) -> str:
|
|
return f"tasks/{instance.id}/answer/{uuid4()}/{filename}"
|
|
|
|
in_competition_position = models.PositiveSmallIntegerField(
|
|
verbose_name="позиция в соревновании"
|
|
)
|
|
competition = models.ForeignKey(
|
|
Competition,
|
|
on_delete=models.CASCADE,
|
|
verbose_name="привязанное соревнование",
|
|
)
|
|
title = models.CharField(verbose_name="заголовок", max_length=50)
|
|
description = MDTextField(verbose_name="описание")
|
|
max_attempts = models.PositiveSmallIntegerField(
|
|
null=True, blank=True, verbose_name="максимальное кол-во попыток"
|
|
)
|
|
type = models.CharField(
|
|
choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки"
|
|
)
|
|
|
|
# only when "input" or "checker" type
|
|
correct_answer_file = models.FileField(
|
|
upload_to=answer_file_upload_to,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="файл с правильным ответом",
|
|
help_text="Имеет смысл только при автоматической (ввод ответа или кода) проверке.",
|
|
)
|
|
points = models.IntegerField(
|
|
null=True, blank=True, verbose_name="общий балл за задание"
|
|
)
|
|
|
|
# only when "checker" type
|
|
answer_file_path = models.TextField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="куда сделать вывод программы участнику",
|
|
help_text=(
|
|
"Путь до файла в котором ожидается результат. "
|
|
"Пример: stdout или ./output.txt. Имеет смысл только при автоматическом типе проверки."
|
|
),
|
|
default="stdout",
|
|
)
|
|
|
|
# only when "review" type
|
|
reviewers = models.ManyToManyField(
|
|
Reviewer,
|
|
blank=True,
|
|
verbose_name="ревьюверы",
|
|
help_text=(
|
|
"Справа отображаются действующие проверяющие, слева - доступные для выбора. "
|
|
"Для перемещения можно кликнуть 2 раза по проверяющему. Имеет смысл только"
|
|
" при ручном типе проверки."
|
|
),
|
|
)
|
|
submission_reviewers_count = models.PositiveSmallIntegerField(
|
|
default=1,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="кол-во проверяющих для зачета задачи",
|
|
)
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
class Meta:
|
|
verbose_name = "задание"
|
|
verbose_name_plural = "задания"
|
|
|
|
|
|
class CompetitionTaskCriteria(BaseModel):
|
|
task = models.ForeignKey(
|
|
CompetitionTask, on_delete=models.CASCADE, related_name="criteries"
|
|
)
|
|
|
|
name = models.TextField(verbose_name="название")
|
|
slug = models.SlugField(verbose_name="техническое название")
|
|
description = models.TextField(verbose_name="описание критерии")
|
|
max_value = models.PositiveSmallIntegerField(
|
|
verbose_name="максимальное кол-во баллов"
|
|
)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class Meta:
|
|
verbose_name = "критерий"
|
|
verbose_name_plural = "критерии"
|
|
|
|
|
|
class CompetitionTaskAttachment(BaseModel):
|
|
def file_upload_at(instance, filename) -> str:
|
|
return f"attachments/{instance.id}/file/{filename}"
|
|
|
|
task = models.ForeignKey(
|
|
CompetitionTask, on_delete=models.CASCADE, verbose_name="задание"
|
|
)
|
|
file = models.FileField(upload_to=file_upload_at, verbose_name="файл")
|
|
bind_at = models.CharField(verbose_name="путь сохранения", max_length=255)
|
|
public = models.BooleanField(default=False, verbose_name="публичный")
|
|
|
|
class Meta:
|
|
verbose_name = "вложение"
|
|
verbose_name_plural = "вложения"
|
|
|
|
|
|
class CompetitionTaskSubmission(BaseModel):
|
|
class StatusChoices(models.TextChoices):
|
|
SENT = "sent", "Отправлено на проверку"
|
|
CHECKING = "checking", "Проверка"
|
|
CHECKED = "checked", "Проверено"
|
|
|
|
def submission_content_upload_to(instance, filename) -> str:
|
|
return f"submissions/{instance.id}/content/{filename}"
|
|
|
|
def submission_stdout_upload_to(instance, filename) -> str:
|
|
return f"submissions/{instance.id}/stdout/{filename}"
|
|
|
|
user = models.ForeignKey(
|
|
User, on_delete=models.CASCADE, verbose_name="пользователь"
|
|
)
|
|
task = models.ForeignKey(
|
|
CompetitionTask, on_delete=models.CASCADE, verbose_name="задание"
|
|
)
|
|
|
|
status = models.CharField(
|
|
choices=StatusChoices.choices,
|
|
default=StatusChoices.SENT,
|
|
max_length=8,
|
|
verbose_name="статус",
|
|
)
|
|
|
|
# code or text or file
|
|
content = models.FileField(
|
|
upload_to=submission_content_upload_to,
|
|
verbose_name="содержание посылки",
|
|
)
|
|
|
|
# only if task type is checker
|
|
stdout = models.FileField(
|
|
upload_to=submission_stdout_upload_to,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="вывод программы",
|
|
help_text="Используется только при проверке чекером",
|
|
)
|
|
|
|
# depends on task type:
|
|
# - input: {"correct": boolean}
|
|
# - file: {"total_points": integer, "by_criteria": ["criteria_name": integer]}
|
|
# - code: {"correct": boolean}
|
|
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
|
|
earned_points = models.IntegerField(
|
|
null=True, blank=True, verbose_name="баллы за задание"
|
|
)
|
|
|
|
checked_at = models.DateTimeField(
|
|
null=True, blank=True, verbose_name="дата проверки"
|
|
)
|
|
plagiarism_detected = models.BooleanField(
|
|
default=False, verbose_name="обнаружен плагиат"
|
|
)
|
|
timestamp = models.DateTimeField(
|
|
auto_now_add=True, verbose_name="дата отправки"
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = "посылка"
|
|
verbose_name_plural = "посылки"
|
|
|
|
def __str__(self):
|
|
return str(self.id)
|
|
|
|
def send_on_review(self):
|
|
if not self.task.reviewers.exists():
|
|
return
|
|
|
|
reviewers_count = self.task.submission_reviewers_count
|
|
reviewers = self.task.reviewers.annotate(
|
|
pending_count=Count(
|
|
"review",
|
|
filter=Q(
|
|
review__state__in=[
|
|
ReviewStatusChoices.NOT_CHECKED,
|
|
ReviewStatusChoices.CHECKING,
|
|
]
|
|
),
|
|
)
|
|
).order_by("pending_count")[
|
|
:reviewers_count
|
|
] # да это медленно работает и чо
|
|
|
|
for reviewer in reviewers:
|
|
Review.objects.update_or_create(
|
|
reviewer=reviewer,
|
|
submission=self,
|
|
)
|