import os from decimal import Decimal import math from django.db import models from django.db.models import TextChoices from django.utils.translation import gettext_lazy as _ from apps.account.models import ProfessorUser from utils.schema import default_timing from utils import generate_slug_for_model def course_file_upload_to(instance, filename): return os.path.join(f"courses/{instance.slug}/videos/{filename}") def attachment_file_upload_to(instance, filename): return os.path.join(f"courses/{instance.course.slug}/attachments/{filename}") class CourseCategory(models.Model): name = models.CharField(max_length=255, verbose_name='Category Name') slug = models.SlugField(unique=True, max_length=255) def __str__(self): return self.name def save(self, *args, **kwargs): if not self.slug: self.slug = generate_slug_for_model(CourseCategory, self.name) super().save(*args, **kwargs) @property def course_count(self): return self.courses.exclude(status="inactive").count() class Course(models.Model): class LevelChoices(TextChoices): BEGINNER = 'beginner', 'Beginner' MID = 'mid', 'Mid Level' ADVANCED = 'advanced', 'Advanced' class StatusChoices(TextChoices): INACTIVE = 'inactive', 'Inactive' # Not Active (does not show) UPCOMING = 'upcoming', 'Upcoming' # Upcoming (visible but registration not allowed)-Предстоящие REGISTERING = 'registering', 'Registering' # Registering (registration is open)-регистрация ONGOING = 'ongoing', 'Ongoing' # Ongoing (course has started, registration closed)-В процессе FINISHED = 'finished', 'Finished' # Finished (course has ended)-закончился class VedioTypeChoices(models.TextChoices): YOUTUBE_LINK = 'youtube_link', 'Youtube Link' VIDEO_FILE = 'video_file', 'Video File' title = models.CharField(max_length=255, verbose_name='Course Title') slug = models.SlugField(allow_unicode=True, unique=True) category = models.ForeignKey(CourseCategory, on_delete=models.CASCADE, related_name='courses', verbose_name='Category') professor = models.ForeignKey( ProfessorUser, on_delete=models.CASCADE, related_name="courses" ) thumbnail = models.ImageField(upload_to="courses/thumbnails/", null=True, blank=True, verbose_name=_('Thumbnail')) video_type = models.CharField( max_length=20, choices=VedioTypeChoices.choices, verbose_name='Preview Video Type (YouTube Link or File Upload)' ) video_file = models.FileField( upload_to=course_file_upload_to, null=True, blank=True ) video_link = models.CharField(max_length=500, null=True, blank=True) is_online = models.BooleanField(default=False, verbose_name='Is Online Course') online_link = models.CharField(max_length=500, null=True, blank=True, verbose_name='Online Class Link') level = models.CharField(max_length=10, choices=LevelChoices.choices, verbose_name='Course Level') duration = models.PositiveIntegerField(verbose_name='Duration (in hours)') lessons_count = models.PositiveIntegerField(verbose_name='Number of Lessons') description = models.TextField(verbose_name='Course Description') short_description = models.CharField(max_length=500, blank=True, null=True, verbose_name="Short Description") status = models.CharField(max_length=15, choices=StatusChoices.choices, default=StatusChoices.INACTIVE, verbose_name='Course Status') is_free = models.BooleanField(default=True, verbose_name='Is Free') price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='Course Price') discount_percentage = models.PositiveIntegerField(default=0, verbose_name='Discount Percentage') final_price = models.DecimalField( verbose_name=_('Course Final Price'), decimal_places=2, max_digits=10, default=0.00, blank=True, help_text=_('This field is automatically calculated based on the discount percentage.') ) timing = models.JSONField(blank=True, null=True, default=default_timing, verbose_name=_("Timing")) features = models.JSONField(verbose_name=_('Course features'), default=dict, blank=True, null=True) 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 def get_completed_lessons_count(self, student): return self.lessons.filter(completions__student=student).count() def is_student_participant(self, student): return self.participants.filter(student=student).exists() def save(self, *args, **kwargs): if not self.slug: self.slug = generate_slug_for_model(Course, self.title) if self.discount_percentage > 0: discount_amount = (self.price * self.discount_percentage) / 100 final_price = self.price - discount_amount self.final_price = Decimal(math.ceil(final_price)).quantize(Decimal('0.00')) else: self.final_price = Decimal(math.ceil(self.price)).quantize(Decimal('0.00')) super().save(*args, **kwargs) class Meta: verbose_name = "Course" verbose_name_plural = "Courses" class Glossary(models.Model): course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='glossaries', verbose_name='Course') title = models.CharField(max_length=555, verbose_name='Glossary Title') description = models.TextField(verbose_name='Description') def __str__(self): return f"{self.course.title} - {self.title}" class Meta: ordering = ("-id",) verbose_name = "Glossary" verbose_name_plural = "Glossary" class Attachment(models.Model): course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='attachments', verbose_name='Course') title = models.CharField(max_length=255, verbose_name='Attachment Title') file = models.FileField( upload_to=attachment_file_upload_to, verbose_name='Attachment File' ) file_size = models.PositiveIntegerField(verbose_name='File Size (in bytes)', null=True, blank=True) def save(self, *args, **kwargs): # Calculate the file size before saving if self.file and not self.file_size: self.file_size = self.file.size super().save(*args, **kwargs) def __str__(self): return f"{self.course.title} - {self.title}" class Meta: ordering = ("-id",) verbose_name = "Attachment" verbose_name_plural = "Attachments"