|
|
|
@ -6,6 +6,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey |
|
|
|
from django.contrib.contenttypes.models import ContentType |
|
|
|
from django.contrib.contenttypes.models import ContentType |
|
|
|
from django.db import models |
|
|
|
from django.db import models |
|
|
|
from redactor.fields import RedactorField |
|
|
|
from redactor.fields import RedactorField |
|
|
|
|
|
|
|
import json |
|
|
|
|
|
|
|
|
|
|
|
from courses.templates import search_in_collection, LEVELS_COLLECTION, MATERIAL_TYPE_COLLECTION, material_fabric, \ |
|
|
|
from courses.templates import search_in_collection, LEVELS_COLLECTION, MATERIAL_TYPE_COLLECTION, material_fabric, \ |
|
|
|
comment_fabric |
|
|
|
comment_fabric |
|
|
|
@ -78,7 +79,8 @@ class Course(models.Model): |
|
|
|
slug = models.SlugField(max_length=255, editable=False, blank=True, default='', unique=True) |
|
|
|
slug = models.SlugField(max_length=255, editable=False, blank=True, default='', unique=True) |
|
|
|
icon = models.ImageField(verbose_name=u'Иконка курса', blank=True, null=True, upload_to='course') |
|
|
|
icon = models.ImageField(verbose_name=u'Иконка курса', blank=True, null=True, upload_to='course') |
|
|
|
direction = models.ForeignKey(MaterialDirection, verbose_name=u'Направление', null=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') |
|
|
|
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) |
|
|
|
public = models.BooleanField(verbose_name=u'Опубликовать', default=False) |
|
|
|
title = models.CharField(verbose_name=u"Заголовок", max_length=255) |
|
|
|
title = models.CharField(verbose_name=u"Заголовок", max_length=255) |
|
|
|
description = RedactorField(verbose_name=u'Описание', blank=True) |
|
|
|
description = RedactorField(verbose_name=u'Описание', blank=True) |
|
|
|
@ -88,7 +90,8 @@ class Course(models.Model): |
|
|
|
help_text=u'Большая картинка для мобильной версии') |
|
|
|
help_text=u'Большая картинка для мобильной версии') |
|
|
|
page = models.URLField(verbose_name=u'Страничка описания', blank=True, default='') |
|
|
|
page = models.URLField(verbose_name=u'Страничка описания', blank=True, default='') |
|
|
|
preview = models.CharField(verbose_name=u'Трэйл', blank=True, default='', max_length=255) |
|
|
|
preview = models.CharField(verbose_name=u'Трэйл', blank=True, default='', max_length=255) |
|
|
|
teachers = models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name=u'Преподаватели', related_name='course_teachers') |
|
|
|
teachers = models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name=u'Преподаватели', |
|
|
|
|
|
|
|
related_name='course_teachers') |
|
|
|
sort = models.IntegerField(verbose_name=u'Порядок сортировки', default=0) |
|
|
|
sort = models.IntegerField(verbose_name=u'Порядок сортировки', default=0) |
|
|
|
use_fail = models.BooleanField(verbose_name=u'Использовать фейковую информацию', default=False) |
|
|
|
use_fail = models.BooleanField(verbose_name=u'Использовать фейковую информацию', default=False) |
|
|
|
basic_len = models.IntegerField(verbose_name=u'Основных модулей', default=0) |
|
|
|
basic_len = models.IntegerField(verbose_name=u'Основных модулей', default=0) |
|
|
|
@ -100,13 +103,42 @@ class Course(models.Model): |
|
|
|
recommend = models.ManyToManyField('self', verbose_name=u'Связанные курсы', blank=True, |
|
|
|
recommend = models.ManyToManyField('self', verbose_name=u'Связанные курсы', blank=True, |
|
|
|
help_text=u'Курсы, которые стоит порекомендовать вместе с этим') |
|
|
|
help_text=u'Курсы, которые стоит порекомендовать вместе с этим') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
def __str__(self): |
|
|
|
return self.title |
|
|
|
return self.title |
|
|
|
|
|
|
|
|
|
|
|
def __unicode__(self): |
|
|
|
def __unicode__(self): |
|
|
|
return u"%s" % self.title |
|
|
|
return u"%s" % self.title |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_tree(self, serializer): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
Способ отображения дочерних элементов. |
|
|
|
|
|
|
|
Принимает на вход сериалайзер узла |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
course_map = json.loads(NormalMap.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.manager.filter(course=self, content_type__model='topic').count() |
|
|
|
|
|
|
|
task_count = Vertex.manager.filter(course=self, content_type__model='task').count() |
|
|
|
|
|
|
|
tutorial_count = Vertex.manager.filter(course=self, content_type__model='tutorial').count() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {"topic_count": topic_count, "tutorial_count": tutorial_count, "task_count": task_count} |
|
|
|
|
|
|
|
|
|
|
|
def get_direction(self): |
|
|
|
def get_direction(self): |
|
|
|
return self.direction if self.direction else '' |
|
|
|
return self.direction if self.direction else '' |
|
|
|
|
|
|
|
|
|
|
|
@ -347,7 +379,8 @@ class Lesson(models.Model): |
|
|
|
('T', 'Подчиняется теме') |
|
|
|
('T', 'Подчиняется теме') |
|
|
|
) |
|
|
|
) |
|
|
|
free = models.BooleanField(verbose_name=u'Бесплатное видео', default=False) |
|
|
|
free = models.BooleanField(verbose_name=u'Бесплатное видео', default=False) |
|
|
|
on_comment = models.CharField(verbose_name=u'Блок комментариев', default='T', choices=COMMENT_SWITCH, help_text=u'{0}/management/faq/37'.format(DOMAIN), max_length=1) |
|
|
|
on_comment = models.CharField(verbose_name=u'Блок комментариев', default='T', choices=COMMENT_SWITCH, |
|
|
|
|
|
|
|
help_text=u'{0}/management/faq/37'.format(DOMAIN), max_length=1) |
|
|
|
token = models.CharField(verbose_name=u'Токен доступа', default='', blank=True, max_length=100, editable=False) |
|
|
|
token = models.CharField(verbose_name=u'Токен доступа', default='', blank=True, max_length=100, editable=False) |
|
|
|
title = models.TextField(verbose_name=u'Заголовок', blank=True) |
|
|
|
title = models.TextField(verbose_name=u'Заголовок', blank=True) |
|
|
|
sort = models.IntegerField(verbose_name=u'Текущая сортировка', default=1) |
|
|
|
sort = models.IntegerField(verbose_name=u'Текущая сортировка', default=1) |
|
|
|
@ -436,10 +469,12 @@ class Homework(models.Model): |
|
|
|
sort = models.IntegerField(verbose_name=u'Текущая сортировка', default=1) |
|
|
|
sort = models.IntegerField(verbose_name=u'Текущая сортировка', default=1) |
|
|
|
|
|
|
|
|
|
|
|
def __unicode__(self): |
|
|
|
def __unicode__(self): |
|
|
|
return u'%s ID: %s Тема: %s Порядок темы: %s' % (self.course.get_title(), self.id, self.theme.title, self.theme.sort) |
|
|
|
return u'%s ID: %s Тема: %s Порядок темы: %s' % ( |
|
|
|
|
|
|
|
self.course.get_title(), self.id, self.theme.title, self.theme.sort) |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
def __str__(self): |
|
|
|
return '%s ID: %s Тема: %s Порядок темы: %s' % (self.course.get_title(), self.id, self.theme.title, self.theme.sort) |
|
|
|
return '%s ID: %s Тема: %s Порядок темы: %s' % ( |
|
|
|
|
|
|
|
self.course.get_title(), self.id, self.theme.title, self.theme.sort) |
|
|
|
|
|
|
|
|
|
|
|
def get_skills(self): |
|
|
|
def get_skills(self): |
|
|
|
# Получить скилы темы |
|
|
|
# Получить скилы темы |
|
|
|
@ -500,7 +535,8 @@ class Exam(models.Model): |
|
|
|
course = models.ForeignKey(Course, verbose_name=u'Курс', null=True) |
|
|
|
course = models.ForeignKey(Course, verbose_name=u'Курс', null=True) |
|
|
|
theme = models.ForeignKey(CourseTheme, verbose_name=u'Тема курса', null=True) |
|
|
|
theme = models.ForeignKey(CourseTheme, verbose_name=u'Тема курса', null=True) |
|
|
|
description = RedactorField(verbose_name=u'Описание для студентов') |
|
|
|
description = RedactorField(verbose_name=u'Описание для студентов') |
|
|
|
materials = models.ManyToManyField(Storage, verbose_name=u'Материалы экзамена', related_name='exam_materials', blank=True) |
|
|
|
materials = models.ManyToManyField(Storage, verbose_name=u'Материалы экзамена', related_name='exam_materials', |
|
|
|
|
|
|
|
blank=True) |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
def __str__(self): |
|
|
|
return '%s' % self.course |
|
|
|
return '%s' % self.course |
|
|
|
@ -1021,18 +1057,52 @@ class Topic(models.Model): |
|
|
|
class VertexManager(models.Manager): |
|
|
|
class VertexManager(models.Manager): |
|
|
|
# Менеджер вершин графа. |
|
|
|
# Менеджер вершин графа. |
|
|
|
|
|
|
|
|
|
|
|
def create_with_dependencies(self, model, course, title, description, |
|
|
|
def create_with_dependencies(self, model, course, old_id, title, description, |
|
|
|
materials=None, **kwargs): |
|
|
|
free=True, materials=None, **kwargs): |
|
|
|
content_type = ContentType.objects.get(app_label='courses', model=model) |
|
|
|
content_type = ContentType.objects.get(app_label='courses', model=model) |
|
|
|
obj = content_type.model_class().objects.create(**kwargs) |
|
|
|
obj, _is_create = content_type.model_class().objects.update_or_create(**kwargs) |
|
|
|
[obj.materials.add(i) for i in materials] if materials else None |
|
|
|
[obj.materials.add(i) for i in materials] if materials else None |
|
|
|
return self.create( |
|
|
|
res, _is_create = self.update_or_create( |
|
|
|
content_type=content_type, |
|
|
|
content_type=content_type, |
|
|
|
object_id=obj.id, |
|
|
|
object_id=obj.id, |
|
|
|
course=course, |
|
|
|
course=course, |
|
|
|
title=title, |
|
|
|
title=title, |
|
|
|
description=description, |
|
|
|
description=description, |
|
|
|
|
|
|
|
free=free, |
|
|
|
|
|
|
|
old_id=old_id, |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
return res |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NormalMap(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)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Vertex(models.Model): |
|
|
|
class Vertex(models.Model): |
|
|
|
@ -1045,12 +1115,14 @@ class Vertex(models.Model): |
|
|
|
|
|
|
|
|
|
|
|
course = models.ForeignKey(to=Course) |
|
|
|
course = models.ForeignKey(to=Course) |
|
|
|
title = models.CharField(verbose_name=u'Название', max_length=255) |
|
|
|
title = models.CharField(verbose_name=u'Название', max_length=255) |
|
|
|
|
|
|
|
free = models.BooleanField(default=True, verbose_name=u'Привилегии для узла не будут проверяться') |
|
|
|
description = models.TextField( |
|
|
|
description = models.TextField( |
|
|
|
verbose_name=u'Описание', default='', blank=True, null=True) |
|
|
|
verbose_name=u'Описание', default='', blank=True, null=True) |
|
|
|
children = models.ManyToManyField(to='Vertex', blank=True) |
|
|
|
children = models.ManyToManyField(to='Vertex', blank=True) |
|
|
|
content_type = models.ForeignKey(to=ContentType) |
|
|
|
content_type = models.ForeignKey(to=ContentType) |
|
|
|
object_id = models.PositiveIntegerField() |
|
|
|
object_id = models.PositiveIntegerField() |
|
|
|
content_object = GenericForeignKey('content_type', 'object_id') |
|
|
|
content_object = GenericForeignKey('content_type', 'object_id') |
|
|
|
|
|
|
|
old_id = models.CharField(max_length=10, verbose_name='Поле создаётся на время миграции') |
|
|
|
|
|
|
|
|
|
|
|
manager = VertexManager() |
|
|
|
manager = VertexManager() |
|
|
|
|
|
|
|
|
|
|
|
@ -1060,12 +1132,12 @@ class Vertex(models.Model): |
|
|
|
def get_type(self): |
|
|
|
def get_type(self): |
|
|
|
return self.content_type.__str__() |
|
|
|
return self.content_type.__str__() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_next(self): |
|
|
|
class NormalMap(models.Model): |
|
|
|
vertex_id = NormalMap.objects.get(course=self.course).get_next(self.id) |
|
|
|
""" |
|
|
|
return Vertex.manager.get(id=int(vertex_id), ) |
|
|
|
Так как курс евляется связным графом мы можем отобразить его бесконечным количеством способов, |
|
|
|
|
|
|
|
а нам нужен один самый красивый, мы должный в явном виде указать способ отображения. |
|
|
|
def is_more(self, vertex) -> bool: |
|
|
|
""" |
|
|
|
if not self.course == vertex.course: |
|
|
|
course = models.OneToOneField(to=Course) |
|
|
|
raise ValueError('Vertexes of different course') |
|
|
|
dependent_elements = models.TextField(default='[]') |
|
|
|
course_map = NormalMap.objects.get(course=self.course) |
|
|
|
independent_elements = models.TextField(default='[]') |
|
|
|
return course_map.map_to_list().index(self.id) > course_map.map_to_list().index(vertex.id) |
|
|
|
|