# -*- coding: utf-8 -*- from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models from django.core.exceptions import ObjectDoesNotExist import json import unidecode from django.template.defaultfilters import slugify from maps.models import CourseRoute, CourseMap from lms.tools import decode_base64, get_real_name from lms.global_decorators import transaction_decorator from library.models import Tags from storage.models import File import random COURSE_LEVEL = ( ('B', 'Базовый'), ('A', 'Продвинутый'), ('E', 'Экспертный'), ('B+A', 'Базовый + Продвинутый'), ) COURSE_DIRECTION = ( (3, 'Бизнес'), (2, 'Веб-дизайн'), (1, 'Разработка'), (4, 'Рисование'), ) class CourseManager(models.Manager): @transaction_decorator def update_or_create_course(self, image=None, big_image=None, id=0, route=None, big_mobile_image=None, mentors=None, slug=None, teachers=None, level=None, direction=None, **kwargs): slug = slug if slug else slugify(unidecode.unidecode(kwargs['title'])) if image: kwargs['image'] = decode_base64(image, 'course/image%s.png' % slug) if big_image: kwargs['big_image'] = decode_base64(big_image, 'course/big_image%s.png' % slug) if big_mobile_image: kwargs['big_mobile_image'] = decode_base64(big_mobile_image, 'course/big_mobile_image%s.png' % slug) if level: kwargs['level'] = get_real_name(COURSE_LEVEL, level) if direction: kwargs['direction'] = get_real_name(COURSE_DIRECTION, direction) if route: kwargs['route'] = CourseRoute.objects.get(id=route) try: course = self.get(id=id) for i in kwargs: if kwargs[i]: setattr(course, i, kwargs[i]) course.save() except ObjectDoesNotExist: kwargs['slug'] = slug kwargs['route'] = CourseRoute.objects.create(name="Основной шаблон") course = self.create(**kwargs) CourseMap.objects.create(course=course) if mentors: for email in mentors: course.mentors.add(get_user_model().objects.get(email=email)) if teachers: for email in teachers: course.teachers.add(get_user_model().objects.get(email=email)) return course class Course(models.Model): hidden = models.BooleanField(verbose_name='Видно только оплатившим', default=False) level = models.CharField(verbose_name='Уровень', choices=COURSE_LEVEL, default='B', max_length=3) slug = models.SlugField(max_length=255, blank=True, default='', unique=True, editable=False) direction = models.SmallIntegerField(choices=COURSE_DIRECTION, verbose_name='Направление', null=True) sort = models.SmallIntegerField(null=True, verbose_name="Порядок сортировки") public = models.BooleanField(verbose_name='Опубликовать', default=False) title = models.CharField(verbose_name="Заголовок", max_length=255) description = models.TextField(verbose_name='Описание', blank=True) image = models.URLField(verbose_name='Изображение', blank=True, max_length=255) big_image = models.URLField(verbose_name='Большое изображение', blank=True, max_length=255) big_mobile_image = models.URLField(verbose_name='Под мобилку', blank=True, null=True, help_text='Большая картинка для мобильной версии', max_length=255) teachers = models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name='Преподаватели', related_name='course_teachers') route = models.OneToOneField(to=CourseRoute, verbose_name="Порядок прохождения по умолчанию", blank=True, null=True) def __str__(self): return self.title def get_teacher(self): return random.choice(self.teachers.all()) def get_maps(self, user): return user.progress_set.get(course=self).get_template().get_maps() def get_vertexes(self, vertex_type=None): if vertex_type: return self.vertex_set.filter(content_type__model=vertex_type, course=self) return self.vertex_set.filter(course=self) def get_statistic(self): """ Минималистичная статистика по уроку, количество тем, уроков, домашек. """ return { "topic_count": self.get_vertexes('topic').count(), "tutorial_count": self.get_vertexes('tutorial').count(), "task_count": self.get_vertexes('task').count(), } def get_first(self, vertex_model_list=None): if vertex_model_list is None: vertex_model_list = ['topic', 'tutorial', 'task'] else: for i in vertex_model_list: if i not in ['topic', 'tutorial', 'task']: raise ValueError('undefined model: ' + i) vertex = Vertex.objects.get(id=self.coursemap_set.get(sort=0).get_first()) if vertex.content_type.model in vertex_model_list: return vertex return vertex.get_next(vertex_model_list) def get_last(self, vertex_model_list=None): if vertex_model_list is None: vertex_model_list = ['topic', 'tutorial', 'task'] else: for i in vertex_model_list: if i not in ['topic', 'tutorial', 'task']: raise ValueError('undefined model: ' + i) vertex = Vertex.objects.get(id=self.coursemap_set.get(sort=0).get_last()) if vertex.content_type.model in vertex_model_list: return vertex return vertex.get_previous(vertex_model_list) objects = CourseManager() class Meta: verbose_name = "Курс" verbose_name_plural = "Курсы" class Topic(models.Model): title = models.CharField(verbose_name='Название', max_length=255) icon = models.ImageField(verbose_name='Иконка темы', null=True, blank=True) course = models.ForeignKey(to=Course) class Meta: verbose_name = "Тема" verbose_name_plural = "Темы" class Vertex(models.Model): VALID_TYPE = ( (3, 'Автаматическая валидация'), (2, 'Полуавтаматическая валидация'), (1, 'Ручная валидация'), (0, 'Без валидации'), ) topic = models.ForeignKey(to=Topic) title = models.CharField(verbose_name='Название', max_length=255) free = models.BooleanField(default=True, verbose_name='Привилегии для узла не будут проверяться') description = models.TextField(verbose_name='Описание', default='', blank=True, null=True) video = models.TextField(verbose_name='Код видео', default='', blank=True, null=True) materials = models.ManyToManyField(File, verbose_name='Материалы урока', blank=True) valid_type = models.SmallIntegerField(choices=VALID_TYPE, default=0) def __str__(self): return self.title def get_next(self, route): return route.get_first().get_next(self) def get_previous(self, vertex_model_list=None): if vertex_model_list is None: vertex_model_list = ['topic', 'tutorial', 'task'] else: for i in vertex_model_list: if i not in ['topic', 'tutorial', 'task']: raise ValueError('undefined model: ' + i) vertex_id = CourseMap.objects.get(course=self.course).get_previous(self.id) vertex = Vertex.objects.get(id=int(vertex_id), ) if vertex.content_type.model in vertex_model_list: return vertex return vertex.get_previous(vertex_model_list) def is_more(self, vertex) -> bool: if not self.course == vertex.course: raise ValueError('Vertexes of different course') course_map = CourseMap.objects.get(course=self.course) return course_map.map_to_list().index(self.id) > course_map.map_to_list().index(vertex.id) def check_vertex(self, user) -> bool: if self.free: return True if not user.is_authenticated: return False try: progress = self.course.progress_set.get(user=user) except ObjectDoesNotExist: return False return progress.is_access(self) def get_number(self, vertex_type=None): """ Возврощает порядковый номер узла с определённым типом. Пример мы хотим определит какой по счёту теме принадлежит конкретно взятый урок. """ vertex_list = list(self.course.get_vertexes(vertex_type)) try: res = vertex_list.index(self) except ValueError: parents = self.vertex_set.all() if parents.count() == 1: res = vertex_list.index(parents[0]) else: res = None return res class Meta: verbose_name = "Урок" verbose_name_plural = "Уроки"