You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

174 lines
6.7 KiB

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"