import os from django.db import models from django.utils.translation import gettext_lazy as _ from filer.fields.image import FilerImageField from filer.fields.file import FilerFileField from apps.account.models import StudentUser def lesson_file_upload_to(instance, filename): return os.path.join(f"lessons/{filename}") class Lesson(models.Model): """ Base Lesson model that contains the actual content (video file or link) """ class ContentTypeChoices(models.TextChoices): YOUTUBE_LINK = 'youtube_link', _('Youtube Link') VIDEO_FILE = 'video_file', _('Video File') title = models.CharField(max_length=255, verbose_name=_('Lesson Title')) content_type = models.CharField(max_length=50, choices=ContentTypeChoices.choices, verbose_name=_('Content Type')) content_file = models.FileField( null=True, blank=True, upload_to=lesson_file_upload_to, ) video_link = models.CharField(max_length=500, null=True, blank=True, verbose_name=_('Link')) duration = models.PositiveIntegerField(verbose_name=_('Duration (in minutes)')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) def __str__(self): return self.title class Meta: verbose_name = _("Lesson") verbose_name_plural = _("Lessons") indexes = [ models.Index(fields=['content_type']), models.Index(fields=['created_at']), ] class CourseLesson(models.Model): """ Intermediate model that connects Course with Lesson """ course = models.ForeignKey("course.Course", on_delete=models.CASCADE, related_name='lessons', verbose_name=_('Course')) lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, related_name='course_lessons', verbose_name=_('Lesson')) title = models.CharField(max_length=255, verbose_name=_('Course Lesson Title'), null=True, blank=True) priority = models.IntegerField(null=True, blank=True, verbose_name=_('Priority')) is_active = models.BooleanField(default=True, verbose_name=_('Is Active')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) def __str__(self): title = self.title or self.lesson.title return f"{self.course.title} - {title}" def is_completed_by(self, student): return self.completions.filter(student=student).exists() @property def content_type(self): return self.lesson.content_type @property def content_file(self): return self.lesson.content_file @property def video_link(self): return self.lesson.video_link @property def duration(self): return self.lesson.duration def save(self, *args, **kwargs): # If title is not provided, use the lesson's title if not self.title: self.title = self.lesson.title if self.priority is None: # If priority is not set, set it to the next available priority max_priority = self.course.lessons.aggregate(max_priority=models.Max('priority'))['max_priority'] self.priority = (max_priority or 0) + 1 else: self._adjust_priorities() super().save(*args, **kwargs) def _adjust_priorities(self): # Adjust priorities of other lessons in the course lessons = self.course.lessons.exclude(pk=self.pk) # Shift priorities for lessons with the same or higher priority lessons.filter(priority__gte=self.priority).update(priority=models.F('priority') + 1) class Meta: verbose_name = _("Course Lesson") verbose_name_plural = _("Course Lessons") indexes = [ models.Index(fields=['course']), models.Index(fields=['lesson']), models.Index(fields=['priority']), models.Index(fields=['is_active']), models.Index(fields=['course', 'priority']), models.Index(fields=['course', 'is_active']), ] class LessonCompletion(models.Model): student = models.ForeignKey( StudentUser, on_delete=models.CASCADE, related_name='lesson_completions', verbose_name=_('Student') ) course_lesson = models.ForeignKey( CourseLesson, on_delete=models.CASCADE, related_name='completions', verbose_name=_('Course Lesson') ) completed_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at")) class Meta: unique_together = ('student', 'course_lesson') verbose_name = _("Lesson Completion") verbose_name_plural = _("Lesson Completions") indexes = [ models.Index(fields=['student']), models.Index(fields=['course_lesson']), models.Index(fields=['completed_at']), models.Index(fields=['student', 'course_lesson']), ] def __str__(self): return f"{self.student.fullname} - {self.course_lesson.title} - Completed"