"""
Модели данных для системы проверки домашних заданий.
Содержит модели для:
- Профилей пользователей (студенты и преподаватели)
- Домашних заданий
- Отправленных работ студентов
"""
import os
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
User = get_user_model()
[документация]
def validate_file_size(file):
"""
Валидатор размера файла (максимум 10 МБ).
Args:
file: Загружаемый файл
Raises:
ValidationError: Если размер файла превышает лимит
"""
max_size_mb = 10
if file.size > max_size_mb * 1024 * 1024:
raise ValidationError(f"Максимальный размер файла {max_size_mb}МБ. Ваш файл: {file.size / (1024 * 1024):.2f}МБ")
[документация]
def validate_file_extension(file):
"""
Валидатор расширения файла.
Args:
file: Загружаемый файл
Raises:
ValidationError: Если расширение файла не разрешено
"""
ext = os.path.splitext(file.name)[1].lower()
valid_extensions = [".pdf", ".doc", ".docx", ".txt", ".py", ".zip", ".jpg", ".jpeg", ".png"]
if ext not in valid_extensions:
raise ValidationError(f"Неподдерживаемый формат файла. Разрешены: {', '.join(valid_extensions)}")
[документация]
class UserProfile(models.Model):
"""Профиль пользователя с ролью"""
ROLE_CHOICES = [
("student", "Студент"),
("teacher", "Преподаватель"),
]
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
role = models.CharField(max_length=10, choices=ROLE_CHOICES, default="student", verbose_name="Роль")
class Meta:
verbose_name = "Профиль пользователя"
verbose_name_plural = "Профили пользователей"
def __str__(self):
return f"{self.user.username} - {self.get_role_display()}"
@property
def is_student(self):
return self.role == "student"
@property
def is_teacher(self):
return self.role == "teacher"
[документация]
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""Автоматически создаем профиль при создании пользователя"""
if created:
UserProfile.objects.create(user=instance)
[документация]
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""Сохраняем профиль при сохранении пользователя"""
if hasattr(instance, "profile"):
instance.profile.save()
[документация]
class Course(models.Model):
"""Модель курса"""
title = models.CharField(max_length=200, verbose_name="Название курса")
description = models.TextField(verbose_name="Описание курса")
teachers = models.ManyToManyField(
User,
related_name="teaching_courses",
verbose_name="Преподаватели",
limit_choices_to={"profile__role": "teacher"},
)
students = models.ManyToManyField(
User,
related_name="enrolled_courses",
blank=True,
verbose_name="Студенты",
limit_choices_to={"profile__role": "student"},
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
class Meta:
verbose_name = "Курс"
verbose_name_plural = "Курсы"
ordering = ["title"]
def __str__(self):
return self.title
[документация]
class Homework(models.Model):
"""Модель домашнего задания"""
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name="homeworks", verbose_name="Курс")
title = models.CharField(max_length=200, verbose_name="Заголовок")
description = models.TextField(verbose_name="Описание")
due_date = models.DateTimeField(verbose_name="Срок сдачи")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
class Meta:
verbose_name = "Домашнее задание"
verbose_name_plural = "Домашние задания"
ordering = ["-created_at"]
def __str__(self):
return f"{self.course.title} - {self.title}"
[документация]
class Submission(models.Model):
"""Модель отправки работы студентом"""
homework = models.ForeignKey(
Homework, on_delete=models.CASCADE, related_name="submissions", verbose_name="Домашнее задание"
)
student = models.ForeignKey(User, on_delete=models.CASCADE, related_name="submissions", verbose_name="Студент")
solution_file = models.FileField(
upload_to="submissions/",
verbose_name="Файл с решением",
validators=[validate_file_size, validate_file_extension],
)
submitted_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата отправки")
grade = models.IntegerField(null=True, blank=True, verbose_name="Оценка")
feedback = models.TextField(blank=True, verbose_name="Отзыв преподавателя")
class Meta:
verbose_name = "Отправка работы"
verbose_name_plural = "Отправки работ"
ordering = ["-submitted_at"]
unique_together = ["homework", "student"]
def __str__(self):
return f"{self.student.username} - {self.homework.title}"
[документация]
class CourseEnrollmentRequest(models.Model):
"""Модель заявки студента на зачисление на курс"""
STATUS_CHOICES = [
("pending", "На рассмотрении"),
("approved", "Одобрена"),
("rejected", "Отклонена"),
]
course = models.ForeignKey(
Course,
on_delete=models.CASCADE,
related_name="enrollment_requests",
verbose_name="Курс",
)
student = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="course_requests",
verbose_name="Студент",
limit_choices_to={"profile__role": "student"},
)
status = models.CharField(
max_length=10,
choices=STATUS_CHOICES,
default="pending",
verbose_name="Статус",
)
message = models.TextField(
blank=True,
verbose_name="Сообщение от студента",
help_text="Опциональное сообщение преподавателю",
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата подачи")
processed_at = models.DateTimeField(null=True, blank=True, verbose_name="Дата обработки")
processed_by = models.ForeignKey(
User,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="processed_requests",
verbose_name="Обработал",
)
class Meta:
verbose_name = "Заявка на курс"
verbose_name_plural = "Заявки на курсы"
ordering = ["-created_at"]
unique_together = ["course", "student"]
def __str__(self):
return f"{self.student.username} → {self.course.title} ({self.get_status_display()})"