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.
 
 
 
 
 
 

320 lines
10 KiB

import arrow
from random import shuffle
from uuid import uuid4
from django.db import models
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.utils.timezone import now
from django.contrib.auth import get_user_model
from django.urls import reverse_lazy
from django.conf import settings
from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey
from apps.school.models import LiveLesson
from project.mixins import BaseModel, DeactivatedMixin
from apps.content.models import ImageObject, Gallery, Video, ContestWork
User = get_user_model()
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ip = models.GenericIPAddressField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
def default_slug():
return str(uuid4())
def deferred_start_at_validator(value):
if value < now():
raise ValidationError(
'Дата и время начала курса не может быть меньше текущих.',
)
class Course(BaseModel, DeactivatedMixin):
DRAFT = 0
PENDING = 1
PUBLISHED = 2
ARCHIVED = 3
DENIED = 4
STATUS_CHOICES = (
(DRAFT, 'Draft'),
(PENDING, 'Pending'),
(PUBLISHED, 'Published'),
(ARCHIVED, 'Archived'),
(DENIED, 'Denied')
)
AGE_LT5 = 1
AGE_57 = 2
AGE_79 = 3
AGE_912 = 4
AGE_1215 = 5
AGE_1518 = 6
AGE_GT18 = 7
AGE_CHOICES = (
(0, 'Любой возраст'),
(AGE_LT5, 'до 5'),
(AGE_57, '5-7'),
(AGE_79, '7-9'),
(AGE_912, '9-12'),
(AGE_1215, '12-15'),
(AGE_1518, '15-18'),
(AGE_GT18, 'от 18'),
)
slug = models.SlugField(
allow_unicode=True, null=True, blank=True,
max_length=100, unique=True, db_index=True,
)
author = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, blank=True
)
title = models.CharField('Название курса', default='', max_length=100, db_index=True)
short_description = models.TextField(
'Краткое описание курса', default='', db_index=True
)
from_author = models.TextField(
'От автора', default='', null=True, blank=True
)
cover = models.ForeignKey(
ImageObject, related_name='course_covers',
verbose_name='Обложка курса', on_delete=models.SET_NULL,
null=True, blank=True,
)
price = models.DecimalField(
'Цена курса', help_text='Если цена не выставлена, то курс бесплатный',
max_digits=10, decimal_places=2, null=True, blank=True
)
age = models.SmallIntegerField(choices=AGE_CHOICES, default=0)
is_infinite = models.BooleanField(default=False)
deferred_start_at = models.DateTimeField(
'Отложенный запуск курса', help_text='Заполнить если курс отложенный',
null=True, blank=True, validators=[deferred_start_at_validator],
)
category = models.ForeignKey('Category', null=True, blank=True, on_delete=models.PROTECT, related_name='courses')
duration = models.IntegerField('Продолжительность курса', default=0)
is_featured = models.BooleanField(default=False)
status = models.PositiveSmallIntegerField(
'Статус', default=DRAFT, choices=STATUS_CHOICES
)
likes = models.ManyToManyField(Like, blank=True)
materials = models.ManyToManyField('Material', blank=True)
gallery = models.ForeignKey(
Gallery, verbose_name='Галерея работ',
on_delete=models.CASCADE, null=True, blank=True,
related_name='results_gallery',
)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'Курс'
verbose_name_plural = 'Курсы'
ordering = ['-is_featured', ]
def __str__(self):
return str(self.id) + ' ' + self.title
# def save(self, *args, **kwargs):
# if not self.slug:
# self.slug = slugify(
# self.title[:90],
# allow_unicode=True
# )
# if Course.objects.filter(slug=self.slug).exclude(id=self.id).exists():
# self.slug += '_' + str(uuid4())[:6]
# return super().save()
@property
def url(self):
return self.get_absolute_url()
def get_absolute_url(self):
return reverse_lazy('course', args=[self.id])
@property
def is_free(self):
if self.price:
return False
return True
@property
def deferred_start_at_humanize(self):
return arrow.get(self.deferred_start_at, settings.TIME_ZONE).humanize(locale='ru')
@property
def created_at_humanize(self):
return arrow.get(self.created_at, settings.TIME_ZONE).humanize(locale='ru')
@property
def is_deferred_start(self):
if not self.deferred_start_at:
return False
if timezone.now() < self.deferred_start_at:
return True
return False
@property
def count_videos_in_lessons(self):
return Video.objects.filter(lesson__in=self.lessons.all()).count()
@classmethod
def shuffle(cls, qs, order_is_featured = True):
if order_is_featured:
featured = []
other = []
for c in list(qs):
if c.is_featured:
featured.append(c)
else:
other.append(c)
shuffle(featured)
shuffle(other)
return featured + other
else:
all = list(qs)
shuffle(all)
return all
class Category(models.Model):
title = models.CharField('Название категории', max_length=100)
def __str__(self):
return self.title
class Meta:
verbose_name = 'Категория'
verbose_name_plural = 'Категории'
ordering = ['title']
class Lesson(BaseModel, DeactivatedMixin):
title = models.CharField('Название урока', max_length=100)
short_description = models.TextField('Краткое описание урока')
author = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, blank=True
)
course = models.ForeignKey(
Course, on_delete=models.CASCADE, related_name='lessons'
)
cover = models.ForeignKey(
ImageObject, related_name='lesson_covers',
verbose_name='Обложка урока', on_delete=models.SET_NULL,
null=True, blank=True,
)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
position = models.PositiveSmallIntegerField(
'Положение на странице',
default=1,
)
def __str__(self):
return self.title
def set_last_position(self):
if self.course:
self.position = self.course.lessons.count()
def save(self, *args, **kwargs):
if not self.author and self.course and self.course.author:
self.author = self.course.author
super().save(*args, **kwargs)
class Meta:
verbose_name = 'Урок'
verbose_name_plural = 'Уроки'
ordering = ('title',)
class Material(models.Model):
title = models.CharField('Название материала', max_length=100)
cover = models.ForeignKey(
ImageObject, related_name='material_covers',
verbose_name='Обложка материала', on_delete=models.SET_NULL,
null=True, blank=True,
)
short_description = models.TextField('Краткое описание материала')
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class Meta:
verbose_name = 'Материал'
verbose_name_plural = 'Материалы'
ordering = ('title',)
class Comment(PolymorphicMPTTModel, DeactivatedMixin):
OBJ_TYPE_COURSE = 'course'
OBJ_TYPE_LESSON = 'lesson'
OBJ_TYPE_LIVE_LESSON = 'live-lesson'
content = models.TextField('Текст комментария', default='')
author = models.ForeignKey(User, on_delete=models.CASCADE)
parent = PolymorphicTreeForeignKey(
'self', null=True, blank=True, related_name='children',
db_index=True, on_delete=models.PROTECT
)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
@property
def created_at_humanize(self):
return arrow.get(self.created_at, settings.TIME_ZONE).humanize(locale='ru')
def __str__(self):
return self.content
class Meta:
ordering = ('-created_at',)
class MPTTMeta:
order_insertion_by = ['-created_at']
class CourseComment(Comment):
course = models.ForeignKey(
Course, on_delete=models.CASCADE, related_name='comments'
)
class Meta(Comment.Meta):
verbose_name = 'Комментарий курса'
verbose_name_plural = 'Комментарии курсов'
class LessonComment(Comment):
lesson = models.ForeignKey(
Lesson, on_delete=models.CASCADE, related_name='comments'
)
class Meta(Comment.Meta):
verbose_name = 'Комментарий урока'
verbose_name_plural = 'Комментарии уроков'
class LiveLessonComment(Comment):
live_lesson = models.ForeignKey(
LiveLesson, on_delete=models.CASCADE, related_name='comments'
)
class Meta(Comment.Meta):
verbose_name = 'Комментарий урока школы'
verbose_name_plural = 'Комментарии уроков школы'
class ContestWorkComment(Comment):
contest_work = models.ForeignKey(ContestWork, on_delete=models.CASCADE, related_name='comments')