from uuid import uuid4 from django.db import models from django.db.models import Count, Q from tinymce.models import HTMLField from apps.competition.models import Competition from apps.core.models import BaseModel from apps.review.models import ReviewStatusChoices, Review 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( null=True, blank=True ) competition = models.ForeignKey(Competition, on_delete=models.CASCADE) title = models.CharField(verbose_name="заголовок", max_length=50) description = HTMLField(verbose_name="описание", max_length=300) max_attempts = models.PositiveSmallIntegerField(null=True, blank=True) 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="файл с правильным ответом", ) 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", ) 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() slug = models.SlugField() description = models.TextField() max_value = models.PositiveSmallIntegerField() class CompetitionTaskAttachment(BaseModel): def file_upload_at(instance, filename): return f"/attachment/{instance.id}/file" task = models.ForeignKey(CompetitionTask, on_delete=models.CASCADE, verbose_name="задание") file = models.FileField(upload_to=file_upload_at, verbose_name="файл") bind_at = models.FilePathField(verbose_name="путь сохранения") public = models.BooleanField(default=False, verbose_name="публичный") 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" def submission_stdout_upload_to(instance, filename) -> str: return f"/submissions/{instance.id}/stdout" 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="вывод чекера" ) # 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_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, )