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.
 
 
 
 
 
 

417 lines
17 KiB

# -*- 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 lms.tools import decode_base64, get_real_name
from lms.global_decorators import transaction_decorator
from library.models import Tags
from storage.models import Storage
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,
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)
try:
course = self.get(id=id)
for i in kwargs:
setattr(course, i, kwargs[i])
course.save()
except ObjectDoesNotExist:
kwargs['slug'] = slug
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)
mentors = models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name='Кураторы', blank=True,
related_name='course_mentors')
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')
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}
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.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.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 = u"Курс"
verbose_name_plural = u"Курсы"
class Skills(models.Model):
title = models.CharField(verbose_name='Наименование', max_length=255)
color = models.CharField(verbose_name='Цвет', max_length=255)
icon = models.ImageField(verbose_name='Большая картинка', upload_to='skills', null=True, help_text='65x65')
description = models.TextField(verbose_name='Описание', 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='Навык')
lesson = models.ForeignKey(to="Vertex", verbose_name='Урок')
def __str__(self): return '%s' % self.skill
class Meta:
verbose_name = 'Размер навыка'
verbose_name_plural = 'Размеры навыков'
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 = 'Достижение'
verbose_name_plural = 'Достижения'
class DiplomaGen(models.Model):
course = models.ForeignKey(to=Course)
template = models.URLField(verbose_name="Путь до шаблона")
def __str__(self):
return 'Шаблон можно найти по адресу: %s, диплом выдаётся за курс %s' % (self.template, self.course.title)
class Meta:
verbose_name = 'Генератор дипломов'
verbose_name_plural = 'Генераторы дипловов'
class Diploma(models.Model):
icon = models.ImageField(verbose_name='Иконка', upload_to='diplomas')
template = models.ForeignKey(to=DiplomaGen, verbose_name='Использовать шаблон', blank=True, null=True)
user = models.ForeignKey(to=settings.AUTH_USER_MODEL)
def __str__(self):
return 'Студенту %s за курс %s' % (self.user.username, self.template.course.title)
class Meta:
verbose_name = 'Диплом'
verbose_name_plural = 'Дипломы'
class VertexManager(models.Manager):
# Менеджер вершин графа.
@transaction_decorator
def create_with_dependencies(self, model, course, title, description, extra_data=None,
free=True, materials=None, parents=None, children=None):
extra_data = json.loads(extra_data)
content_type = ContentType.objects.get(app_label='courses', model=model)
obj = content_type.model_class().objects.create(**extra_data)
[obj.materials.add(i) for i in materials] if materials else None
res = self.create(
content_type=content_type,
object_id=obj.id,
course=course,
title=title,
description=description,
free=free,
)
if children:
for child in children:
res.children.add(child)
if parents:
for parent in parents:
parent.children.add(res)
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_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_next(self.id)
vertex = Vertex.objects.get(id=int(vertex_id), )
if vertex.content_type.model in vertex_model_list:
return vertex
return vertex.get_next(vertex_model_list)
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
if self.extraprivilege_set.filter(user=user).exists():
return True
try:
progress = self.course.progress_set.get(user=user)
except ObjectDoesNotExist:
return False
return progress.is_access(self)
# Модели нового API со временем всё, что выше будет выпилено
class Tutorial(models.Model):
"""
Модель урока.
Урок может быть открыт для комментирования и закрыт, по дефолту открыт,
вероятно закрывать нужно будет крайне редко.
Видео к уроку фрейм который лежит прямо в базе, конечно же костыль и со временем
мы уйдём от этого, все видео хостятся на двух онлайн сервисах на клиент нужно передовать
id и сервис на котором, это дело хостится, а правило отображения оставить клиенту.
Материалы для урока по сути FileField, нужна только для создания лишней связи в таблице
и дублирования метазаголовков файла
"""
on_comment = models.BooleanField(verbose_name=u'Комментарии', default=False)
video = models.TextField(verbose_name=u'Код видео', default='', blank=True)
materials = models.ManyToManyField(Storage, verbose_name=u'Материалы урока', blank=True)
class Task(models.Model):
"""
Модель таска.
Исторически сложилось, что на сервере хостятся два типа тасков отличающихся лишь наименованием
домашние работы и экзамены, не нужно быть гением, чтобы понять для чего нужно булево значение
is_exam
Материалы для урока по сути FileField, нужна только для создания лишней связи в таблице
и дублирования метазаголовков файла
"""
materials = models.ManyToManyField(Storage, verbose_name=u'Материалы для домашней работы', blank=True)
is_exam = models.BooleanField(default=False, verbose_name=u'Экзамен или домашка')
class Topic(models.Model):
"""
Модель темы, нужно просто для объединения тасков и уроков.
У некоторых тем есть иконка.
Возможно поле icon перекачует в Vertex, а данная модель отвалится за ненадобностью
"""
icon = models.ImageField(verbose_name=u'Иконка темы', 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) -> int:
res_list = self.map_to_list()
if not res_list[-1] == vertex_id:
return res_list[res_list.index(vertex_id) + 1]
error = "vertex_id " + str(vertex_id) + " last object in list\n" + ",".join([str(v) for v in res_list])
raise ValueError(error)
def get_previous(self, vertex_id) -> int:
res_list = self.map_to_list()
if not res_list[0] == vertex_id:
return res_list[res_list.index(vertex_id) - 1]
error = "vertex_id " + str(vertex_id) + " first object in list\n" + ",".join([str(v) for v in res_list])
raise ValueError(error)
def get_first(self):
return self.map_to_list()[0]
def get_last(self):
return self.map_to_list()[-1]