# -*- coding: utf-8 -*- from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models import json from library.models import Tags from management.models import Comment from storage.models import Storage class MaterialDirection(models.Model): title = models.CharField(verbose_name=u'Заголовок', max_length=255) color = models.CharField(verbose_name=u'Цвет', max_length=50) description = models.TextField(verbose_name=u'Описание', blank=True) mentors = models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name=u'Кураторы') def __str__(self): return self.title class Meta: verbose_name = u'Направление' verbose_name_plural = u'Направления' class Course(models.Model): COURSE_LEVEL = ( ('B', u'Базовый'), ('A', u'Продвинутый'), ('E', u'Экспертный'), ('B+A', u'Базовый + Продвинутый') ) hidden = models.BooleanField(verbose_name=u'Видно только оплатившим', default=False) level = models.CharField(verbose_name=u'Уровень', choices=COURSE_LEVEL, default='B', max_length=3) slug = models.SlugField(max_length=255, editable=False, blank=True, default='', unique=True) direction = models.ForeignKey(MaterialDirection, verbose_name=u'Направление', null=True) mentors = models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name=u'Кураторы', blank=True, related_name='course_mentors') public = models.BooleanField(verbose_name=u'Опубликовать', default=False) title = models.CharField(verbose_name=u"Заголовок", max_length=255) description = models.TextField(verbose_name=u'Описание', blank=True) image = models.ImageField(verbose_name=u'Изображение', upload_to='course', blank=True) big_image = models.ImageField(verbose_name=u'Большое изображение', upload_to='course', blank=True) big_mobile_image = models.ImageField(verbose_name=u'Под мобилку', upload_to='course', blank=True, null=True, help_text=u'Большая картинка для мобильной версии') teachers = models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name=u'Преподаватели', related_name='course_teachers') def __str__(self): return self.title def get_tree(self, serializer): """ Способ отображения дочерних элементов. Принимает на вход сериалайзер узла """ course_map = json.loads(CourseMap.objects.get(course=self).dependent_elements) def helper(tree_id): acc = [] for j, i in enumerate(tree_id): if type([]) == type(i): acc[-1]['children'] = helper(i) else: acc.append(serializer(Vertex.manager.get(id=i)).data) return acc return helper(course_map) def get_statistic(self): """ Минималистичная статистика по уроку, количество тем, уроков, домашек. """ topic_count = Vertex.objects.filter(course=self, content_type__model='topic').count() task_count = Vertex.objects.filter(course=self, content_type__model='task').count() tutorial_count = Vertex.objects.filter(course=self, content_type__model='tutorial').count() return {"topic_count": topic_count, "tutorial_count": tutorial_count, "task_count": task_count} class Meta: verbose_name = u"Курс" verbose_name_plural = u"Курсы" ordering = ['sort'] class Skills(models.Model): title = models.CharField(verbose_name=u'Наименование', max_length=255) color = models.CharField(verbose_name=u'Цвет', max_length=255) icon = models.ImageField(verbose_name=u'Большая картинка', upload_to='skills', null=True, help_text='65x65') description = models.TextField(verbose_name=u'Описание', blank=True) def __str__(self): return '%s' % self.title class Meta: verbose_name = u'Навык' verbose_name_plural = u'Навыки' class SkillJ(models.Model): skill = models.ForeignKey(to="Skills", verbose_name=u'Навык') lesson = models.ForeignKey(to="Vertex", verbose_name=u'Урок') size = models.IntegerField(verbose_name=u'Размер', default=0) def __str__(self): return '%s %s' % (self.skill, self.size) class Meta: verbose_name = u'Размер навыка' verbose_name_plural = u'Размеры навыков' ordering = ['id'] class Achievements(models.Model): course = models.ForeignKey(to="Course") icon = models.ImageField(verbose_name='Отображение достижения', upload_to='diplomas', blank=True, null=True) user = models.ForeignKey(to=settings.AUTH_USER_MODEL) def __str__(self): return 'Студенту %s за курс %s' % (self.user.username, self.course.title) class Meta: verbose_name = u'Достижение' verbose_name_plural = u'Достижения' class Diploma(models.Model): icon = models.ImageField(verbose_name=u'Иконка', upload_to='diplomas') course = models.ForeignKey(to=Course) user = models.ForeignKey(to=settings.AUTH_USER_MODEL) def __str__(self): return 'Студенту %s за курс %s' % (self.user.username, self.course.title) class Meta: verbose_name = 'Диплом' verbose_name_plural = 'Дипломы' class VertexManager(models.Manager): # Менеджер вершин графа. def create_with_dependencies(self, model, course, old_id, title, description, free=True, materials=None, **kwargs): content_type = ContentType.objects.get(app_label='courses', model=model) obj, _is_create = content_type.model_class().objects.update_or_create(**kwargs) [obj.materials.add(i) for i in materials] if materials else None res, _is_create = self.update_or_create( content_type=content_type, object_id=obj.id, course=course, title=title, description=description, free=free, old_id=old_id, ) return res class Vertex(models.Model): """ Основная структурная единица узел графа курса. Позволяет работать со структурой курса на более высоком уровне абстракции. """ course = models.ForeignKey(to=Course) title = models.CharField(verbose_name=u'Название', max_length=255) free = models.BooleanField(default=True, verbose_name=u'Привилегии для узла не будут проверяться') description = models.TextField(verbose_name=u'Описание', default='', blank=True, null=True) children = models.ManyToManyField(to='Vertex', blank=True) content_type = models.ForeignKey(to=ContentType) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') objects = VertexManager() def __str__(self): return self.title + ': ' + str(self.content_type.model) def get_next(self): vertex_id = CourseMap.objects.get(course=self.course).get_next(self.id) return Vertex.objects.get(id=int(vertex_id), ) 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) # Модели нового API со временем всё, что выше будет выпилено class Tutorial(Vertex): """ Модель урока. Урок может быть открыт для комментирования и закрыт, по дефолту открыт, вероятно закрывать нужно будет крайне редко. Видео к уроку фрейм который лежит прямо в базе, конечно же костыль и со временем мы уйдём от этого, все видео хостятся на двух онлайн сервисах на клиент нужно передовать id и сервис на котором, это дело хостится, а правило отображения оставить клиенту. Материалы для урока по сути FileField, нужна только для создания лишней связи в таблице и дублирования метазаголовков файла """ on_comment = models.BooleanField(verbose_name=u'Комментарии', default=True) video = models.TextField(verbose_name=u'Код видео', default='', blank=True) materials = models.ManyToManyField(Storage, verbose_name=u'Материалы урока', blank=True) class Task(Vertex): """ Модель таска. Исторически сложилось, что на сервере хостятся два типа тасков отличающихся лишь наименованием домашние работы и экзамены, не нужно быть гением, чтобы понять для чего нужно булево значение is_exam Материалы для урока по сути FileField, нужна только для создания лишней связи в таблице и дублирования метазаголовков файла """ materials = models.ManyToManyField(Storage, verbose_name=u'Материалы для домашней работы', blank=True) is_exam = models.BooleanField(default=False, verbose_name=u'Экзамен или домашка') class Topic(Vertex): """ Модель темы, нужно просто для объединения тасков и уроков. У некоторых тем есть иконка. Возможно поле icon перекачует в Vertex, а данная модель отвалится за ненадобностью """ icon = models.ImageField(verbose_name=u'Иконка темы', upload_to='CourseTheme', null=True, blank=True) class CourseMap(models.Model): """ Так как курс евляется связным графом мы можем отобразить его бесконечным количеством способов, а нам нужен один самый красивый, мы должный в явном виде указать способ отображения. """ course = models.OneToOneField(to=Course) dependent_elements = models.TextField(default='[]') independent_elements = models.TextField(default='[]') def map_to_list(self) -> list: def helper(root_list): res = [] for i in root_list: if type(i) == type([]): res += helper(i) else: res.append(i) return res return helper(json.loads(self.dependent_elements)) def get_next(self, vertex_id) -> str: res_list = self.map_to_list() try: return res_list[res_list.index(vertex_id)] except ValueError: raise ValueError("vertex_id " + str(vertex_id) + " not in list " + ",".join(res_list))