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.
249 lines
11 KiB
249 lines
11 KiB
# -*- 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 Course(models.Model):
|
|
COURSE_LEVEL = (
|
|
('B', u'Базовый'),
|
|
('A', u'Продвинутый'),
|
|
('E', u'Экспертный'),
|
|
('B+A', u'Базовый + Продвинутый')
|
|
)
|
|
COURSE_DIRECTION = (
|
|
(3, 'Бизнес'),
|
|
(2, 'Веб-дизайн'),
|
|
(1, 'Разработка'),
|
|
(4, 'Рисование')
|
|
)
|
|
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.SmallIntegerField(choices=COURSE_DIRECTION, 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.objects.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"Курсы"
|
|
|
|
|
|
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'Урок')
|
|
|
|
def __str__(self): return '%s %s' % (self.skill, self.size)
|
|
|
|
class Meta:
|
|
verbose_name = u'Размер навыка'
|
|
verbose_name_plural = u'Размеры навыков'
|
|
|
|
|
|
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))
|
|
|