# -*- coding: utf-8 -*- import random import string import unidecode from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.template.defaultfilters import slugify from course_service.maps.models import CourseRoute, CourseMap from lms.global_decorators import transaction_decorator from lms.tools import decode_base64, get_real_name COURSE_LEVEL = ( ('B', 'Базовый'), ('A', 'Продвинутый'), ('E', 'Экспертный'), ('B+A', 'Базовый + Продвинутый'), ) COURSE_DIRECTION = ( (3, 'Бизнес'), (2, 'Веб-дизайн'), (1, 'Разработка'), (4, 'Рисование'), ) class CourseManager(models.Manager): def update_or_create_course(self, image=None, big_image=None, id=0, big_mobile_image=None, slug=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) 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=kwargs['title'], is_template=True, out_key=''.join(random.choice(string.ascii_letters) for x in range(15)), ) course = self.create(**kwargs) course.save() return course def change_route(self, pk, route): route = CourseRoute.objects.get(id=route) course = self.get(id=pk) course.route = route course.save() 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) 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 = ArrayField( models.CharField(max_length=15, blank=True, verbose_name='Ссылки на преподов'), default=[]) route = models.OneToOneField(to=CourseRoute, verbose_name="Порядок прохождения по умолчанию", blank=True, null=True) def __str__(self): return self.title 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) description = models.TextField(verbose_name='Описание', blank=True, null=True) course = models.ForeignKey(to=Course, verbose_name='курс') class Meta: verbose_name = "Тема" verbose_name_plural = "Темы" class Vertex(models.Model): VALID_TYPE = ( (3, 'Автаматическая валидация'), (2, 'Полуавтаматическая валидация'), (1, 'Ручная валидация'), (0, 'Без валидации'), ) topic = models.ForeignKey(to=Topic, verbose_name='Тема') title = models.CharField(verbose_name='Название', max_length=255) free = models.BooleanField(default=True, verbose_name='Привилегии для узла не будут проверяться') description = models.TextField(verbose_name='Описание', blank=True, null=True) video = models.TextField(verbose_name='Код видео', default='', blank=True, null=True) materials = ArrayField(models.CharField(max_length=15, blank=True, verbose_name='Материалы урока')) valid_type = models.SmallIntegerField(choices=VALID_TYPE, default=0) token = models.CharField(max_length=15, verbose_name="Ключ доступа к узлу", unique=True) 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 = "Уроки"