Files

128 lines
8.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
from django.conf import settings
from .validators import validate_file_extension
from ckeditor_uploader.fields import RichTextUploadingField
class Subject(models.Model):
name = models.CharField('Название предмета', max_length=100)
class Meta:
verbose_name = 'Предмет'
verbose_name_plural = 'Предметы'
def __str__(self):
return self.name
class Assignment(models.Model):
title = models.CharField('Название задания', max_length=255)
short_description = models.TextField('Краткое описание (для карточки)', blank=True, null=True, help_text='Отображается на главной странице в карточке задания. Если пусто, используется начало полного описания.')
description = RichTextUploadingField('Описание задания', blank=True, null=True)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, verbose_name='Предмет')
is_published = models.BooleanField('Опубликовано', default=True, help_text='Снимите галочку, чтобы скрыть задание (как черновик).')
assign_to_all = models.BooleanField('Назначить всем', default=True, help_text='Если включено, задание будет доступно всем ученикам.')
assigned_students = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='assigned_assignments', verbose_name='Назначенные ученики', blank=True, limit_choices_to={'role': 'STUDENT'}, help_text='Выберите учеников, если задание не для всех.')
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, limit_choices_to={'role__in': ['TEACHER', 'ADMIN']}, verbose_name='Кем создано')
created_at = models.DateTimeField('Дата создания', auto_now_add=True)
deadline = models.DateTimeField('Дедлайн', blank=True, null=True, help_text='После этого времени ответы не принимаются.')
time_limit = models.PositiveIntegerField('Лимит времени (минуты)', blank=True, null=True, help_text='Например, 235 для 3ч 55м. Оставьте пустым, если без таймера.')
allow_late_submissions = models.BooleanField('Разрешить дорешивание', default=True, help_text='Если включено, ученики смогут сдавать задание после дедлайна с пометкой "После дедлайна".')
passing_threshold = models.PositiveIntegerField('Порог прохождения (в баллах)', default=10, help_text='Количество баллов, необходимое для прохождения. Например, 10.')
class Meta:
verbose_name = 'Задание'
verbose_name_plural = 'Задания'
def __str__(self):
return f"{self.title} ({self.subject})"
class Question(models.Model):
TYPE_CHOICES = (
('TEXT_AUTO', 'Краткий ответ (автопроверка)'),
('FILE_MANUAL', 'Файл/Развернутый ответ (ручная проверка)'),
('TABLE_AUTO', 'Таблица (2 столбца, автопроверка)'),
)
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE, related_name='questions', verbose_name='Задание')
text = RichTextUploadingField('Текст задачи')
attached_file = models.FileField('Файл (опционально)', upload_to='questions/', blank=True, null=True, validators=[validate_file_extension])
question_type = models.CharField('Тип ответа', max_length=20, choices=TYPE_CHOICES, default='TEXT_AUTO')
table_rows = models.PositiveIntegerField('Количество строк в таблице', default=2, blank=True, null=True, help_text='Укажите количество строк. Столбцов всегда 2.')
correct_answer = models.CharField('Правильный ответ (для автопроверки)', max_length=255, blank=True, null=True, help_text='Оставьте пустым для ручной проверки. Для таблицы вводите ответы в формате "Ответ1|Ответ2;Ответ3|Ответ4" (строки через точку с запятой, столбцы через прямую черту).')
points = models.PositiveIntegerField('Баллы за правильный ответ', default=1, help_text='Количество баллов, которое дается за эту задачу (для автопроверки).')
order = models.PositiveIntegerField('Порядок', default=0)
class Meta:
verbose_name = 'Задача'
verbose_name_plural = 'Задачи'
ordering = ['order', 'id']
def __str__(self):
number = self.order if self.order else self.id
return f"Задача {number} к {self.assignment.title}"
class Submission(models.Model):
STATUS_CHOICES = (
('IN_PROGRESS', 'В процессе выполнения'),
('PENDING', 'Ожидает проверки'),
('GRADED', 'Проверено'),
)
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE, related_name='submissions', verbose_name='Задание')
student = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, limit_choices_to={'role': 'STUDENT'}, verbose_name='Ученик')
grade = models.CharField('Оценка', max_length=10, blank=True, null=True)
teacher_feedback = models.TextField('Комментарий учителя', blank=True, null=True)
status = models.CharField('Статус', max_length=20, choices=STATUS_CHOICES, default='PENDING')
started_at = models.DateTimeField('Время начала', blank=True, null=True)
submitted_at = models.DateTimeField('Дата отправки', auto_now_add=True)
is_time_expired = models.BooleanField('Время вышло', default=False)
is_late = models.BooleanField('Сдано после дедлайна', default=False)
class Meta:
verbose_name = 'Ответ ученика'
verbose_name_plural = 'Ответы учеников'
def __str__(self):
return f"Ответ от {self.student.username} на {self.assignment.title}"
def get_statistics(self):
if not self.grade:
return None
try:
parts = self.grade.split('/')
earned = float(parts[0].strip())
total = float(parts[1].strip())
percentage = (earned / total) * 100 if total > 0 else 0
threshold_points = self.assignment.passing_threshold
is_passed = earned >= threshold_points
total_int = int(total) if total.is_integer() else total
earned_int = int(earned) if earned.is_integer() else earned
return {
'earned': earned_int,
'total': total_int,
'percentage': percentage,
'percentage_str': f"{percentage:.1f}",
'is_passed': is_passed,
'threshold_points': threshold_points,
'threshold_text': 'Выше порога' if is_passed else f'Ниже порога (Мин: {threshold_points} б)',
'color': '#2ea043' if is_passed else '#f85149'
}
except Exception:
return None
class Answer(models.Model):
submission = models.ForeignKey(Submission, on_delete=models.CASCADE, related_name='answers', verbose_name='Решение (Submission)')
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='answers', verbose_name='Задача')
text_answer = models.TextField('Текстовый ответ', blank=True, null=True)
file_answer = models.FileField('Файл с ответом', upload_to='answers/%Y/%m/%d/', blank=True, null=True, validators=[validate_file_extension])
is_correct = models.BooleanField('Правильно?', null=True, blank=True)
points = models.PositiveIntegerField('Баллы', blank=True, null=True)
comment = models.TextField('Комментарий', blank=True, null=True)
class Meta:
verbose_name = 'Ответ на задачу'
verbose_name_plural = 'Ответы на задачи'
def __str__(self):
return f"Ответ на {self.question}"