diff --git a/_utils/gen_diploms.py b/_utils/gen_diploms.py index 006070c..3e099c4 100644 --- a/_utils/gen_diploms.py +++ b/_utils/gen_diploms.py @@ -1,7 +1,6 @@ # coding=utf-8 import os from django.core.files import File -import datetime import django import sys @@ -10,7 +9,6 @@ from django.db.models import Q sys.path.append("/var/www/projects/lms/") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.settings") django.setup() -from courses.models import Diploma, User from journals.models import DiplomaJ for tm in DiplomaJ.objects.filter(Q(in_image=None)|Q(out_image=None)): @@ -20,7 +18,6 @@ for tm in DiplomaJ.objects.filter(Q(in_image=None)|Q(out_image=None)): _in = open(_in) tm.in_image.save(File(_in).name, File(_in), save=True) - if _out: _out = open(_out) tm.out_image.save(File(_out).name, File(_out), save=True) diff --git a/access/access_update.py b/access/access_update.py index 93babbe..8795f55 100644 --- a/access/access_update.py +++ b/access/access_update.py @@ -1,12 +1,77 @@ import os, sys -import django, json +import django sys.path.append("../") os.environ['PG_PORT_5432_TCP_ADDR'] = '127.0.0.1' os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.settings") django.setup() -from access.models import Privilege +from access.models import ActiveObject +from courses.models import Vertex, Lesson, Homework, Exam +from journals.models import TeacherJ, LessonJ, HomeworkJ, ExamJ +from finance.models import Bill +from django.contrib.auth import get_user_model + + +def get_first(course,): + l = Lesson.objects.filter(course=course).order_by('sort') + hw = Homework.objects.filter(course=course).order_by('sort') + e = Exam.objects.filter(course=course).order_by('sort') + try: + old_id = 'l_' + str(l[0].id) if l.exists() else ('h_' + str(hw[0].id) if hw.exists() else 'e_' + str(e[0].id)) + return Vertex.manager.get(old_id=old_id) + except IndexError: + return None if __name__ == '__main__': - Privilege + for user in get_user_model().objects.all(): + bills = Bill.objects.filter(user=user, status='F') + if bills.exists(): + for i in bills: + try: + tj = TeacherJ.objects.get(course=i.service.course, student=user) + if tj.status == 'F': + ActiveObject.objects.create(course=i.service.course, user=user, success=True) + else: + lesson = LessonJ.objects.filter(parent__parent=tj, success=True, student=tj.student) + homework = HomeworkJ.objects.filter(parent__parent=tj, success=True, student=tj.student) + exam = ExamJ.objects.filter(parent__parent=tj, success=True, student=tj.student) + + lesson = lesson.order_by('-material__sort', '-parent__material__sort')[0] if lesson.exists() else None + homework = homework.order_by('-material__sort', '-parent__material__sort')[0] if homework.exists() else None + exam = exam.order_by('-material__sort', '-parent__material__sort')[0] if exam.exists() else None + + lesson_p = lesson.parent.material.sort if lesson else 0 + homework_p = homework.parent.material.sort + 0.5 if homework else 0 + exam_p = exam.parent.material.sort + 0.75 if exam else 0 + + obj = (lesson_p > homework_p and lesson_p > exam_p and 'l_' + str(lesson.material.id)) or \ + (homework_p > exam_p and 'h_' + str(homework.material.id)) or \ + (exam and 'e_' + str(exam.material.id)) or\ + None + + if not obj: + active_obj = get_first(i.service.course) + if active_obj: + ActiveObject.objects.create( + course=i.service.course, + user=user, + active_obj=active_obj, + ) + + else: + vertex = Vertex.manager.get(old_id=obj) + ActiveObject.objects.create( + course=i.service.course, + user=user, + active_obj=vertex.get_next() + ) + + except TeacherJ.DoesNotExist: + active_obj = get_first(i.service.course) + if active_obj: + ActiveObject.objects.create( + course=i.service.course, + user=user, + active_obj=active_obj, + ) \ No newline at end of file diff --git a/access/admin.py b/access/admin.py index acc4fb8..7b86a76 100755 --- a/access/admin.py +++ b/access/admin.py @@ -1,9 +1,13 @@ from django.contrib import admin from access.models import User, Subscription, ActionJ, TrafSource, TrafHistory, TrafTokenHistory, Questionnaire, \ - UserRequest, UserRequestData, Document#, UserSync + UserRequest, UserRequestData, Document, ActiveObject, ExtraPrivilege from django.contrib.auth.admin import Group +admin.site.register(ExtraPrivilege) +admin.site.register(ActiveObject) + + class UserAdmin(admin.ModelAdmin): list_filter = ['in_role', 'refer', 'date_joined', 'last_time', 'status', 'customer'] list_display = ('id', 'email', 'is_active', 'fname', 'name', 'reg_status', 'phone', 'refer', 'date_joined', 'status', ) diff --git a/access/migrations/0094_auto_20170929_1026.py b/access/migrations/0094_auto_20170929_1026.py new file mode 100644 index 0000000..46edf82 --- /dev/null +++ b/access/migrations/0094_auto_20170929_1026.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-29 10:26 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('courses', '0048_auto_20170929_1026'), + ('access', '0093_auto_20170928_1625'), + ] + + operations = [ + migrations.CreateModel( + name='ActiveObject', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('active_obj', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Vertex', verbose_name='Активный объект')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Course', verbose_name='Курс')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Студент')), + ], + ), + migrations.CreateModel( + name='ExtraPrivilege', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_done', models.BooleanField(default=False, verbose_name='Выполнено?')), + ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Vertex', verbose_name='Объект')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Правообладатель')), + ], + ), + migrations.RemoveField( + model_name='privilege', + name='subject', + ), + migrations.RemoveField( + model_name='privilege', + name='user', + ), + migrations.DeleteModel( + name='Privilege', + ), + ] diff --git a/access/migrations/0095_auto_20170929_1545.py b/access/migrations/0095_auto_20170929_1545.py new file mode 100644 index 0000000..cb3f7a0 --- /dev/null +++ b/access/migrations/0095_auto_20170929_1545.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-29 15:45 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('access', '0094_auto_20170929_1026'), + ] + + operations = [ + migrations.AddField( + model_name='activeobject', + name='success', + field=models.BooleanField(default=False, verbose_name='Завершён ли курс'), + ), + migrations.AlterField( + model_name='activeobject', + name='active_obj', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='courses.Vertex', verbose_name='Активный объект'), + ), + ] diff --git a/access/models.py b/access/models.py index 79a9e04..5eab050 100755 --- a/access/models.py +++ b/access/models.py @@ -7,7 +7,7 @@ import datetime from django.db.models import SET_NULL -from courses.models import Vertex +from courses.models import Vertex, Course from lms.regex import check_email from lms.settings import STATIC_ROOT, DOMAIN from lms.tools import random_string, random_int, out_date_format, get_client_ip @@ -627,13 +627,24 @@ class Questionnaire(models.Model): # Новое API +class ActiveObject(models.Model): + user = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name=u'Студент') + course = models.ForeignKey(to=Course, verbose_name=u'Курс') + active_obj = models.ForeignKey(to=Vertex, verbose_name=u'Активный объект', blank=True, null=True) + success = models.BooleanField(default=False, verbose_name=u'Завершён ли курс') -class Privilege(models.Model): - TYPES = ( - ('r', 'Доступно для выполнению'), - ('w', 'Ждёт ответа'), - ('d', 'Выполнено'), - ) + def __str__(self): + return u'%s %s %s' % ( + self.user.email, + ('завершил курс' if self.success else 'в процессе изучения курса'), + self.course.title, + ) + + def is_access(self, vertex): + return not vertex.is_more(self.active_obj) + + +class ExtraPrivilege(models.Model): user = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name=u'Правообладатель') - value = models.CharField(verbose_name=u'Права', choices=TYPES, max_length=1, default='r') - subject = models.ForeignKey(to=Vertex, verbose_name=u'Объект') + is_done = models.BooleanField(default=False, verbose_name=u'Выполнено?') + subject = models.ForeignKey(to=Vertex, verbose_name=u'Объект') \ No newline at end of file diff --git a/access/serializers.py b/access/serializers.py index bcb2386..86df184 100644 --- a/access/serializers.py +++ b/access/serializers.py @@ -1,5 +1,6 @@ from django.contrib.auth import get_user_model from rest_framework import serializers +from access.models import ExtraPrivilege class UserInitSerializer(serializers.ModelSerializer): @@ -12,4 +13,10 @@ class UserInitSerializer(serializers.ModelSerializer): @staticmethod def get_in_role(self): - return self.get_in_role_display() \ No newline at end of file + return self.get_in_role_display() + + +class ExtraPrivilegeSerializer(serializers.ModelSerializer): + class Meta: + model = ExtraPrivilege + exclude = ('user',) \ No newline at end of file diff --git a/courses/course_update.py b/courses/course_update.py index e443b48..dc3cf36 100644 --- a/courses/course_update.py +++ b/courses/course_update.py @@ -8,6 +8,13 @@ django.setup() from courses.models import Course, NormalMap, CourseTheme, Lesson, Homework, Exam, Vertex + +def get_url(frame): + frame = frame[frame.find('src=')+5:] + url = frame[:frame.find('\"')] + return url + + if __name__ == '__main__': for course in Course.objects.all(): course_list = [] @@ -19,10 +26,12 @@ if __name__ == '__main__': description=theme.description, model='topic', icon=theme.icon, + old_id='t_' + str(theme.id), ) course_list.append(topic_vertex.id) topic_list = [] + pay_lesson_exist = False for i in Lesson.objects.filter(theme=theme).order_by('sort'): on_comment = i.on_comment == 'N' or i.on_comment == 'T' and i.theme.on_comment @@ -32,9 +41,12 @@ if __name__ == '__main__': description=i.description, model='tutorial', on_comment=on_comment, - video=i.video, + video=get_url(i.video), materials=i.materials.all(), + free=i.free, + old_id='l_' + str(i.id), ) + pay_lesson_exist = pay_lesson_exist or not i.free topic_vertex.children.add(vertex) topic_list.append(vertex.id) @@ -46,6 +58,8 @@ if __name__ == '__main__': model='task', is_exam=False, materials=i.materials.all(), + free=not pay_lesson_exist, + old_id='h_' + str(i.id), ) topic_vertex.children.add(vertex) topic_list.append(vertex.id) @@ -58,10 +72,15 @@ if __name__ == '__main__': model='task', is_exam=True, materials=i.materials.all(), + free=not pay_lesson_exist, + old_id='e_' + str(i.id), ) topic_vertex.children.add(vertex) topic_list.append(vertex.id) + topic_vertex.free = not pay_lesson_exist + topic_vertex.save() + course_list.append(topic_list) course_map, _is_create = NormalMap.objects.get_or_create(course=course) diff --git a/courses/migrations/0048_auto_20170929_1026.py b/courses/migrations/0048_auto_20170929_1026.py new file mode 100644 index 0000000..155ac4f --- /dev/null +++ b/courses/migrations/0048_auto_20170929_1026.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-29 10:26 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('courses', '0047_auto_20170928_1625'), + ] + + operations = [ + migrations.AlterField( + model_name='vertex', + name='free', + field=models.BooleanField(default=True, verbose_name='Привилегии для узла не будут проверяться'), + ), + ] diff --git a/courses/migrations/0049_auto_20171002_1841.py b/courses/migrations/0049_auto_20171002_1841.py new file mode 100644 index 0000000..b1ccc4e --- /dev/null +++ b/courses/migrations/0049_auto_20171002_1841.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-10-02 18:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('courses', '0048_auto_20170929_1026'), + ] + + operations = [ + migrations.AddField( + model_name='vertex', + name='old_id', + field=models.CharField(default='', max_length=10, verbose_name='Поле создаётся на время миграции'), + preserve_default=False, + ), + ] diff --git a/courses/models.py b/courses/models.py index 6903cd8..2078943 100755 --- a/courses/models.py +++ b/courses/models.py @@ -6,6 +6,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models from redactor.fields import RedactorField +import json from courses.templates import search_in_collection, LEVELS_COLLECTION, MATERIAL_TYPE_COLLECTION, material_fabric, \ comment_fabric @@ -78,17 +79,19 @@ class Course(models.Model): 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') 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) title = models.CharField(verbose_name=u"Заголовок", max_length=255) description = RedactorField(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'Большая картинка для мобильной версии') + big_mobile_image = models.ImageField(verbose_name=u'Под мобилку', upload_to='course', blank=True, null=True, + help_text=u'Большая картинка для мобильной версии') page = models.URLField(verbose_name=u'Страничка описания', blank=True, default='') 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) use_fail = models.BooleanField(verbose_name=u'Использовать фейковую информацию', default=False) basic_len = models.IntegerField(verbose_name=u'Основных модулей', default=0) @@ -97,9 +100,8 @@ class Course(models.Model): buy_icon = models.ImageField(verbose_name=u'Картинка покупки', upload_to='course', blank=True, null=True) must_build = models.BooleanField(verbose_name=u'На переформировку', default=False) keywords = models.ManyToManyField(Tags, verbose_name=u'Ключевые слова', blank=True) - recommend = models.ManyToManyField('self', verbose_name=u'Связанные курсы', blank=True, - help_text=u'Курсы, которые стоит порекомендовать вместе с этим') - + recommend = models.ManyToManyField('self', verbose_name=u'Связанные курсы', blank=True, + help_text=u'Курсы, которые стоит порекомендовать вместе с этим') def __str__(self): return self.title @@ -107,6 +109,36 @@ class Course(models.Model): def __unicode__(self): 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): return self.direction if self.direction else '' @@ -165,7 +197,7 @@ class Course(models.Model): def get_description(self): # Получить описание курса return self.description - + def get_skills(self): # Получить скилы темы result = [] @@ -200,8 +232,8 @@ class Course(models.Model): def to_build(self): # Отправить на переформирование картыx - #self.must_build = True - #self.save() + # self.must_build = True + # self.save() self.build_map() def get_absolute_url(self): @@ -347,7 +379,8 @@ class Lesson(models.Model): ('T', 'Подчиняется теме') ) 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) title = models.TextField(verbose_name=u'Заголовок', blank=True) sort = models.IntegerField(verbose_name=u'Текущая сортировка', default=1) @@ -436,10 +469,12 @@ class Homework(models.Model): sort = models.IntegerField(verbose_name=u'Текущая сортировка', default=1) 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): - 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): # Получить скилы темы @@ -500,7 +535,8 @@ class Exam(models.Model): course = models.ForeignKey(Course, verbose_name=u'Курс', null=True) theme = models.ForeignKey(CourseTheme, verbose_name=u'Тема курса', null=True) 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): return '%s' % self.course @@ -824,7 +860,7 @@ class CourseBuilder: def append_in_price(self, map): # Добавить карту в счета, куда включены все карты курса в типе, исключая текущий - #Price.objects.filter(included=map.get_befor) + # Price.objects.filter(included=map.get_befor) pass def rebuild_map(self): @@ -1021,18 +1057,52 @@ class Topic(models.Model): class VertexManager(models.Manager): # Менеджер вершин графа. - def create_with_dependencies(self, model, course, title, description, - materials=None, **kwargs): + 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 = 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 - return self.create( + 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 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): @@ -1045,12 +1115,14 @@ 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') + old_id = models.CharField(max_length=10, verbose_name='Поле создаётся на время миграции') manager = VertexManager() @@ -1060,12 +1132,12 @@ class Vertex(models.Model): def get_type(self): return self.content_type.__str__() - -class NormalMap(models.Model): - """ - Так как курс евляется связным графом мы можем отобразить его бесконечным количеством способов, - а нам нужен один самый красивый, мы должный в явном виде указать способ отображения. - """ - course = models.OneToOneField(to=Course) - dependent_elements = models.TextField(default='[]') - independent_elements = models.TextField(default='[]') + def get_next(self): + 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: + raise ValueError('Vertexes of different course') + course_map = NormalMap.objects.get(course=self.course) + return course_map.map_to_list().index(self.id) > course_map.map_to_list().index(vertex.id) diff --git a/courses/new_urls.py b/courses/new_urls.py index 8e2fbbf..45f741a 100644 --- a/courses/new_urls.py +++ b/courses/new_urls.py @@ -4,7 +4,7 @@ from courses import new_view as views urlpatterns = [ url(r'detail/([0-9]{1,99})/$', views.CourseDetailView.as_view()), url(r'vertex/([0-9]{1,99})/$', views.VertexDetail.as_view()), - url(r'tree/([0-9]{1,99})/$', views.TreeView.as_view()), + url(r'tree/(?P[-\w]+)/$', views.TreeView.as_view()), url(r'directions/$', views.DirectionListView.as_view()), url(r'^$', views.CourseListView.as_view()), ] \ No newline at end of file diff --git a/courses/new_view.py b/courses/new_view.py index c2a4dce..91ecf7a 100644 --- a/courses/new_view.py +++ b/courses/new_view.py @@ -1,7 +1,10 @@ from rest_framework.views import APIView from rest_framework.renderers import JSONRenderer from rest_framework.response import Response + +from access.serializers import ExtraPrivilegeSerializer from courses.models import Course, MaterialDirection, Vertex +from access.models import ActiveObject, ExtraPrivilege from courses.serializers import CourseDetailSerializer, CourseListSerializer, VertexSerializer, CourseTreeSerializer from finance.models import Bill @@ -10,7 +13,7 @@ class DirectionListView(APIView): renderer_classes = (JSONRenderer,) status_code = 200 - def get(self, request): + def get(self, _request): return Response([direction.title for direction in MaterialDirection.objects.all()], self.status_code) @@ -23,18 +26,35 @@ class TreeView(APIView): print(request) return Response(status=204) - def get(self, request, pk): + def get(self, request, slug): try: - return Response(CourseTreeSerializer(Course.objects.get(id=pk)).data, self.status_code) + course = Course.objects.get(slug=slug) except Course.DoesNotExist: return Response("Course doesn't exist", status=404) + res = CourseTreeSerializer(course).data + try: + res['active_id'] = ActiveObject.objects.get(course=course, user=request.user).active_obj.id + except ActiveObject.DoesNotExist: + res['active_id'] = False + + res['extra_privilege'] = [ + ExtraPrivilegeSerializer(i).data for i in ExtraPrivilege.objects.filter( + user=request.user, + subject__course=course, + ) + ] + return Response(res, self.status_code) + class CourseDetailView(APIView): renderer_classes = (JSONRenderer,) status_code = 200 def get(self, request, pk): + if not request.user.is_authenticated and request.user.in_role in ['A']: + return Response("Course detail access only for admin users", status=403) + try: return Response(CourseDetailSerializer(Course.objects.get(id=pk)).data, self.status_code) except Course.DoesNotExist: @@ -46,7 +66,9 @@ class CourseListView(APIView): status_code = 200 def get(self, request): - if not request.GET.get('staff', 'true') == 'false': + if (not (request.user.is_authenticated or request.user.in_role == 'U'))\ + or request.GET.get('staff') == 'true': + return Response([CourseListSerializer(i).data for i in Course.objects.all()], self.status_code) res = [] @@ -64,10 +86,31 @@ class CourseListView(APIView): class VertexDetail(APIView): renderer_classes = (JSONRenderer,) - def get(self, request, pk): - if not request.user.is_authenticated: - return Response("Access to detail of vertex, exist only for authenticated users", status=404) + @staticmethod + def get(request, pk): + status = int(request.GET.get('status', '200')) + try: - return Response(VertexSerializer(Vertex.manager.get(id=pk)).data, status=200) + vertex = Vertex.manager.get(id=pk) except Vertex.DoesNotExist: return Response("Vertex doesn't exist", status=404) + + res_a = Response(VertexSerializer(vertex).data, status=200) if status == 200 else Response(status=204) + + if vertex.free: + return res_a + + if not request.user.is_authenticated: + return Response("Access to detail of vertex, exist only for authenticated users", status=403) + + if ExtraPrivilege.objects.filter(user=request.user, subject=vertex).exists(): + return res_a + + try: + if not ActiveObject.objects.get(course=vertex.course, user=request.user).is_access(vertex): + return Response("permission denied", status=403) + + except ActiveObject.DoesNotExist: + return Response("permission denied", status=403) + + return res_a diff --git a/courses/serializers.py b/courses/serializers.py index e9e64ab..b89577a 100644 --- a/courses/serializers.py +++ b/courses/serializers.py @@ -3,7 +3,7 @@ import json # from django.contrib.auth import get_user_model # from django.core.exceptions import ObjectDoesNotExist -from courses.models import Course, Vertex, Tutorial, Topic, Task +from courses.models import Course, Vertex, Tutorial, Topic, Task, NormalMap class TutorialSerializer(serializers.ModelSerializer): @@ -27,56 +27,49 @@ class TopicSerializer(serializers.ModelSerializer): class MiniVertexSerializer(serializers.ModelSerializer): object = serializers.SerializerMethodField() type = serializers.SerializerMethodField() - children = serializers.SerializerMethodField() class Meta: model = Vertex - fields = ('id', 'title', 'type', 'object', 'children') + fields = ('id', 'title', 'type', 'object', 'free') @staticmethod def get_object(self): - if self.content_type.model == 'tutorial': - return TutorialSerializer(self.content_object).data - - elif self.content_type.model == 'topic': + if self.content_type.model == 'topic': return TopicSerializer(self.content_object).data - - elif self.content_type.model == 'task': - return TaskSerializer(self.content_object).data + return False @staticmethod def get_type(self): return self.content_type.model - @staticmethod - def get_children(self): - return json.loads(self.sort_map.dependent_elements) - class VertexSerializer(MiniVertexSerializer): class Meta: model = Vertex - fields = ('id', 'title', 'type', 'object', 'children', 'description') + fields = ('id', 'title', 'type', 'object', "description") @staticmethod - def get_children(self): - return [VertexSerializer(Vertex.manager.get(id=i)).data - for i in json.loads(self.sort_map.dependent_elements)] + def get_object(self): + if self.content_type.model == 'tutorial': + return TutorialSerializer(self.content_object).data + if self.content_type.model == 'task': + return TaskSerializer(self.content_object).data + return False class CourseListSerializer(serializers.ModelSerializer): - length = serializers.SerializerMethodField() + statistic = serializers.SerializerMethodField() level = serializers.SerializerMethodField() direction = serializers.SerializerMethodField() class Meta: model = Course - fields = ['id', 'title', 'length', - 'level', 'direction', 'image', ] + fields = ['id', 'title', 'statistic', + 'level', 'direction', 'image', 'slug'] @staticmethod - def get_length(self): - return 15 + def get_statistic(self): + return self.get_statistic() @staticmethod def get_level(self): @@ -96,8 +89,7 @@ class CourseTreeSerializer(serializers.ModelSerializer): @staticmethod def get_children(self): - return [MiniVertexSerializer(Vertex.manager.get(id=i)).data - for i in json.loads(self.sort_map.dependent_elements)] + return self.get_tree(MiniVertexSerializer) class CourseDetailSerializer(serializers.ModelSerializer): diff --git a/courses/signals.py b/courses/signals.py index fa8bf8a..086c944 100644 --- a/courses/signals.py +++ b/courses/signals.py @@ -9,5 +9,3 @@ def delete_dependencies(instance, **kwargs): """Удаляем зависимости вместе с узлом""" if instance.content_object: instance.content_object.delete() - if instance.sort_map: - instance.sort_map.delete() diff --git a/journals/default_ations.py b/journals/default_ations.py new file mode 100644 index 0000000..195a8b0 --- /dev/null +++ b/journals/default_ations.py @@ -0,0 +1,70 @@ +import os, sys, django + +sys.path.append("../") +os.environ['PG_PORT_5432_TCP_ADDR'] = '127.0.0.1' +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.settings") +django.setup() + +from journals.models import Action + +if __name__ == '__main__': + + Action.objects.update_or_create( + name='try', + text='попытка сдачи чего-либо', + ) + + Action.objects.update_or_create( + name='yes', + text='одобрение чего-либо', + ) + + Action.objects.update_or_create( + name='no', + text='отказ от чего-либо', + ) + + Action.objects.update_or_create( + name='like', + text='нравится что-либо', + ) + + Action.objects.update_or_create( + name='dislike', + text='не нравится что-либо', + ) + + Action.objects.update_or_create( + name='favorite', + text='добавить в избранное', + ) + + Action.objects.update_or_create( + name='watch', + text='просмотр', + ) + + Action.objects.update_or_create( + name='start', + text='начало чего-либо', + ) + + Action.objects.update_or_create( + name='end', + text='окончание чего-либо', + ) + + Action.objects.update_or_create( + name='create', + text='создание чего-либо', + ) + + Action.objects.update_or_create( + name='delete', + text='удаление чего-либо', + ) + + Action.objects.update_or_create( + name='update', + text='изминение чего-либо', + ) \ No newline at end of file diff --git a/journals/default_threads.py b/journals/default_threads.py index 12859f5..78e472a 100644 --- a/journals/default_threads.py +++ b/journals/default_threads.py @@ -5,10 +5,101 @@ os.environ['PG_PORT_5432_TCP_ADDR'] = '127.0.0.1' os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.settings") django.setup() -from journals.models import Thread +from management.models import Comment +from django.contrib.contenttypes.models import ContentType +from courses.models import Vertex, Lesson +from access.models import ActiveObject +from journals.models import Thread, HomeworkTry, ExamTry, Journal, Action, \ + LessonJ, CourseThemeJ, TeacherJ, HomeworkJ, ExamJ +from library.models import Article from django.contrib.auth import get_user_model +from django.db import transaction + + +def transaction_decorator(function_to_decorate): + def wrap(*args, **kwargs): + transaction.set_autocommit(False) + try: + result = function_to_decorate(*args, **kwargs) + except Exception as ex: + transaction.rollback() + raise ex + else: + transaction.commit() + return result + finally: + transaction.set_autocommit(True) + + return wrap + + +@transaction_decorator +def start_end_object(instance, pref, student, course, start_th_user, end_th_user): + for thj in instance.objects.filter(date__isnull=False, student=student, material__course__in=course): + obj_id = pref + str(thj.material.id) + vertex = Vertex.manager.get(old_id=obj_id) + + Journal.objects.get_or_create( + thread=start_th_user, + user=student, + content_type=vertex_type, + date=thj.date, + object_id=vertex.id, + action_type=start_action, + ) + + if thj.f_date: + Journal.objects.get_or_create( + thread=end_th_user, + user=student, + content_type=vertex_type, + date=thj.f_date, + object_id=vertex.id, + action_type=end_action, + ) + + +def task_obj_try(instance, pref, teacher, task_thread): + for obj_try in instance.objects.filter(teacher=teacher): + vt_id = Vertex.manager.get(old_id=pref + str(obj_try.material.id)).id + s_t_thread, _is_create = Thread.objects.get_or_create( + key='task_%d__student_%d__teacher_%d' % (vt_id, obj_try.student.id, obj_try.teacher.id), + is_staff=False, + ) + + s_t_thread.parent.add(task_thread) + s_t_thread.subscribers.add(obj_try.student) + s_t_thread.subscribers.add(obj_try.teacher) + + user = obj_try.teacher + action = Action.objects.get(name='no') + + if obj_try.success: + action = Action.objects.get(name='yes') + + if obj_try.f_date is None: + user = obj_try.student + action = Action.objects.get(name='try') + + Journal.objects.get_or_create( + thread=s_t_thread, + user=user, + content_type=vertex_type, + date=obj_try.date, + object_id=vt_id, + action_type=action + ) + +# +# Выше - хелперы, ниже - основная часть программы +# + + +@transaction_decorator +def main_threads(): + print("Инициализация основных тредов") + res = {} -if __name__ == '__main__': support_list = [ 'kate.gazukina@skillbox.ru', 'katerina.ragozina@skillbox.ru', @@ -17,24 +108,267 @@ if __name__ == '__main__': admin_list = [ 'andrey.korolev@skillbox.ru', 'pavel.matveev@skillbox.ru', + 'anton.kopylov@skillbox.ru', ] admin_thread, _is_create = Thread.objects.get_or_create( key='Admin', - text='Тред для админов сюда падают все журналируемые сообщения в системе', + text='Тред для админов, сюда падают все журналируемые сообщения в системе', is_staff=True, ) for i in get_user_model().objects.filter(email__in=admin_list): admin_thread.subscribers.add(i) + management_thread, _is_create = Thread.objects.get_or_create( + key='Project_management', + text='Тред для проджект-менеджеров, сюда падает статистика разного рода', + is_staff=True, + ) + + management_thread.parent.add(admin_thread) + + res['library_thread'], _is_create = Thread.objects.get_or_create( + key='Library', + text='Тред <<Библиотека>> сюда прилетает вся инфа по статьям', + is_staff=True, + ) + + res['library_thread'].parent.add(management_thread) + + start_and_end_thread, _is_create = Thread.objects.get_or_create( + key='Start_and_end', + text='Тред начала и завершения прохождения какого-либо этапа обучения', + is_staff=True, + ) + + start_and_end_thread.parent.add(management_thread) + + res['start_thread'], _is_create = Thread.objects.get_or_create( + key='Start', + text='Тред начала прохождения какого-либо этапа обучения', + is_staff=True, + ) + + res['start_thread'].parent.add(start_and_end_thread) + + res['end_thread'], _is_create = Thread.objects.get_or_create( + key='End', + text='Тред окончания прохождения какого-либо этапа обучения', + is_staff=True, + ) + + res['end_thread'].parent.add(start_and_end_thread) + support_thread, _is_create = Thread.objects.get_or_create( key='Support', - text='Тред сапортов занимаются поддержкой клиента', + text='Тред сапортов, занимаются поддержкой клиента', is_staff=True, ) support_thread.parent.add(admin_thread) for i in get_user_model().objects.filter(email__in=support_list): - support_thread.subscribers.add(i) \ No newline at end of file + support_thread.subscribers.add(i) + + res['task_thread'], _is_create = Thread.objects.get_or_create( + key='Tasks', + text='Сюда пободают все переписки студентов и преподов', + is_staff=True, + ) + + res['task_thread'].parent.add(support_thread) + + res['comment_thread'], _is_create = Thread.objects.get_or_create( + key='Comments', + text='Сюда пободают все лайки дизлайки и так далее по коментам', + is_staff=True, + ) + + res['comment_thread'].parent.add(support_thread) + + return res + + +@transaction_decorator +def create_article_journals(library_thread): + print("Создание журналов для статей") + for article in Article.objects.all(): + article_kwarg = { + 'content_type': article_type, + 'object_id': article.id, + 'thread': library_thread, + } + + for author in article.favorite.all(): + Journal.objects.get_or_create( + user=author, + action_type=Action.objects.get(name='favorite'), + **article_kwarg, + ) + + for author in article.likes.all(): + Journal.objects.get_or_create( + user=author, + action_type=Action.objects.get(name='like'), + **article_kwarg, + ) + + for author in article.views.all(): + Journal.objects.get_or_create( + user=author, + action_type=Action.objects.get(name='watch'), + **article_kwarg, + ) + + +def start_end_journals(start_thread, end_thread): + print("Создание журналов начала и завершения какого-либо этапа") + for ao in ActiveObject.objects.all(): + try: + start_th_user, _is_create = Thread.objects.get_or_create( + is_staff=False, + key='start_thread_course_%d__user_%d' % (ao.course.id, ao.user.id), + ) + + end_th_user, _is_create = Thread.objects.get_or_create( + is_staff=False, + key='end_thread_course_%d__user_%d' % (ao.course.id, ao.user.id), + ) + + tj = TeacherJ.objects.get(course=ao.course, student=ao.user) + Journal.objects.get_or_create( + thread=start_th_user, + user=ao.user, + content_type=course_type, + date=tj.start_date, + object_id=ao.course.id, + action_type=start_action, + ) + + start_th_user.parent.add(start_thread) + end_th_user.parent.add(end_thread) + + kwarg = { + 'student': ao.user, + 'course': ao.course, + 'start_th_user': start_th_user, + 'end_th_user': end_th_user, + } + + start_end_object( + instance=CourseThemeJ, + pref='t_', + **kwarg, + ) + + start_end_object( + instance=LessonJ, + pref='l_', + **kwarg, + ) + + start_end_object( + instance=HomeworkJ, + pref='h_', + **kwarg, + ) + + start_end_object( + instance=ExamJ, + pref='e_', + **kwarg, + ) + + except TeacherJ.DoesNotExist: + pass + + +@transaction_decorator +def task_try(task_tread): + print("Журналы попыток домашек") + for teacher in get_user_model().objects.filter(in_role='T'): + task_obj_try( + instance=HomeworkTry, + pref='h_', + teacher=teacher, + task_thread=task_tread + ) + + task_obj_try( + instance=ExamTry, + pref='e_', + teacher=teacher, + task_thread=task_tread + ) + + +@transaction_decorator +def init_comment(comment_thread): + comment_type = ContentType.objects.get(app_label='management', model='comment') + for comment in Comment.objects.all(): + comment_kwarg = { + "content_type": course_type, + "thread": comment_thread, + "object_id": comment.id, + } + + for user in comment.minus_rating.all(): + Journal.objects.get_or_create( + user=user, + action_type=Action.objects.get(name="dislike"), + **comment_kwarg, + ) + + for user in comment.plus_rating.all(): + Journal.objects.get_or_create( + user=user, + action_type=Action.objects.get(name="like"), + **comment_kwarg, + ) + + for user in comment.saw.all(): + Journal.objects.get_or_create( + user=user, + action_type=Action.objects.get(name="watch"), + **comment_kwarg, + ) + +if __name__ == '__main__': + + if not "help" in sys.argv: + vertex_type = ContentType.objects.get(app_label='courses', model='vertex') + article_type = ContentType.objects.get(app_label='library', model='article') + course_type = ContentType.objects.get(app_label='courses', model='course') + start_action = Action.objects.get(name='start') + end_action = Action.objects.get(name='end') + + boot_list = ['article', 'start_end', 'task_try', 'comment'] if len(sys.argv) == 1 else sys.argv[1:] + + main_threads_info = main_threads() + + if 'article' in boot_list: + create_article_journals(main_threads_info['library_thread']) + + if 'start_end' in boot_list: + start_end_journals(main_threads_info['start_thread'], main_threads_info['end_thread']) + + if 'task_try' in boot_list: + task_try(main_threads_info['task_thread']) + + if 'comment' in boot_list: + init_comment(main_threads_info['lesson_thread']) + + else: + print(""" + Это скрипт миграции в новую систему данных условно его + можно разделить на несколько частей:\n + 1 Инициализация основных тредов,\n + 2 Создание журналов для статей,\n + 3 Создание журналов начала и завершения какого-либо этапа,\n + 4 Журналы попыток домашек,\n + 5 Создание журналов для коментов,\n + + При запуске без флагов выполняются все этапы, но можно указать какой-то конкретный или несколько, + article, start_end, task_try, comment (инициализация является обязательной) + """) diff --git a/journals/migrations/0077_journal_extra_data.py b/journals/migrations/0077_journal_extra_data.py new file mode 100644 index 0000000..facfcd2 --- /dev/null +++ b/journals/migrations/0077_journal_extra_data.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-10-06 16:22 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0076_auto_20170928_1625'), + ] + + operations = [ + migrations.AddField( + model_name='journal', + name='extra_data', + field=models.TextField(default=''), + ), + ] diff --git a/journals/migrations/0078_auto_20171009_0952.py b/journals/migrations/0078_auto_20171009_0952.py new file mode 100644 index 0000000..963f6fe --- /dev/null +++ b/journals/migrations/0078_auto_20171009_0952.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-10-09 09:52 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0077_journal_extra_data'), + ] + + operations = [ + migrations.AddField( + model_name='thread', + name='check_subscribe', + field=models.BooleanField(default=True, verbose_name='Проверять ли подписки'), + ), + migrations.AlterField( + model_name='thread', + name='subscribers', + field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Подписчики'), + ), + ] diff --git a/journals/migrations/0079_thread_is_child.py b/journals/migrations/0079_thread_is_child.py new file mode 100644 index 0000000..f079b85 --- /dev/null +++ b/journals/migrations/0079_thread_is_child.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-10-09 17:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0078_auto_20171009_0952'), + ] + + operations = [ + migrations.AddField( + model_name='thread', + name='is_child', + field=models.BooleanField(default=False, verbose_name='Поле аптимизации поиска'), + ), + ] diff --git a/journals/migrations/0080_auto_20171009_1736.py b/journals/migrations/0080_auto_20171009_1736.py new file mode 100644 index 0000000..36c84e2 --- /dev/null +++ b/journals/migrations/0080_auto_20171009_1736.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-10-09 17:36 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0079_thread_is_child'), + ] + + operations = [ + migrations.RenameField( + model_name='thread', + old_name='is_child', + new_name='is_recursively', + ), + ] diff --git a/journals/migrations/0081_auto_20171009_1742.py b/journals/migrations/0081_auto_20171009_1742.py new file mode 100644 index 0000000..b15127e --- /dev/null +++ b/journals/migrations/0081_auto_20171009_1742.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-10-09 17:42 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0080_auto_20171009_1736'), + ] + + operations = [ + migrations.RemoveField( + model_name='thread', + name='is_recursively', + ), + migrations.AddField( + model_name='thread', + name='recurse_step', + field=models.SmallIntegerField(default=0, verbose_name='Поле аптимизации поиска'), + ), + ] diff --git a/journals/models.py b/journals/models.py index 8f749ca..20d62ea 100755 --- a/journals/models.py +++ b/journals/models.py @@ -1673,20 +1673,23 @@ class Journal(models.Model): thread = models.ForeignKey(to='Thread', verbose_name=u'Тред') user = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name=u'Инициатор действия') content_type = models.ForeignKey(to=ContentType) + extra_data = models.TextField(default='') object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') action_type = models.ForeignKey(to='Action') date = models.DateTimeField(auto_now=True) def __str__(self): - return 'Пользователь %s %s' % (self.user.email, self.action_type.name) + return '%d Пользователь %s %s %s' % (self.id, self.user.email, self.action_type.name, self.thread.key) class Thread(models.Model): key = models.CharField(max_length=200) text = models.TextField(default='', verbose_name=u'Описание треда') is_staff = models.BooleanField(default=False, verbose_name=u'Админская ли табличка') - subscribers = models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name=u'Подписчики') + recurse_step = models.SmallIntegerField(default=0, verbose_name=u'Поле аптимизации поиска') + subscribers = models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name=u'Подписчики', blank=True) + check_subscribe = models.BooleanField(default=True, verbose_name='Проверять ли подписки') parent = models.ManyToManyField(to='self', blank=True, symmetrical=False) x = models.SmallIntegerField(default=300) y = models.SmallIntegerField(default=300) @@ -1698,10 +1701,41 @@ class Thread(models.Model): def check_perm(self, user): return (user in self.subscribers.all()) or bool(sum([int(i.check_perm(user)) for i in self.parent.all()])) + def child_thread_count(self, step=None): + step = self.recurse_step if step is None else step + + if step == 0: + return self.thread_set.count() + + return sum([i.child_thread_count(step-1) for i in self.thread_set.all()]) + + def journals_count(self, step=None): + step = self.recurse_step if step is None else step + children = list(self.get_children(step)) + children.append(self) + return Journal.objects.filter(thread__in=children).count() + + def get_children(self, step): + children = self.thread_set.filter(is_staff=False) + + if step == 0: + list(children).append(self) + return children + + res = [self] + + for child in children: + res += child.get_children(step=step-1) + + return res + def __str__(self): return self.key class Action(models.Model): name = models.CharField(max_length=255, verbose_name=u'Наименование действия (на английском)') - text = models.TextField(verbose_name=u'Описание действия') \ No newline at end of file + text = models.TextField(verbose_name=u'Описание действия') + + def __str__(self): + return "%s (%s)" % (self.name, self.text) \ No newline at end of file diff --git a/journals/new_urls.py b/journals/new_urls.py index 398a4ef..3b2ff17 100644 --- a/journals/new_urls.py +++ b/journals/new_urls.py @@ -3,5 +3,5 @@ from journals import new_view as views urlpatterns = [ url(r'thread/$', views.ThreadListView.as_view()), - url(r'thread/([0-9]{1,99})/$', views.ThreadDetailView.as_view()), + url(r'thread/(?P[-\w]+)/$', views.ThreadDetailView.as_view()), ] \ No newline at end of file diff --git a/journals/new_view.py b/journals/new_view.py index cfcd9a7..26d5ceb 100644 --- a/journals/new_view.py +++ b/journals/new_view.py @@ -3,7 +3,7 @@ from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from journals.models import Thread -from journals.serilizers import ThreadSerializer +from journals.serilizers import ThreadUserSerializer, ThreadAdminSerializer class ThreadListView(APIView): @@ -12,7 +12,7 @@ class ThreadListView(APIView): def get(self, request): return Response( - [ThreadSerializer(thread).data for thread in Thread.objects.filter(is_staff=True)], + [ThreadAdminSerializer(thread).data for thread in Thread.objects.filter(is_staff=True)], self.status_code, ) @@ -21,14 +21,31 @@ class ThreadDetailView(APIView): renderer_classes = (JSONRenderer,) status_code = 200 - def post(self, request, pk): + def post(self, request, key): try: - thread = Thread.objects.get(id=pk) + thread = Thread.objects.get(key=key) thread.text = request.JSON.get('text', thread.text) thread.key = request.JSON.get('key', thread.key) thread.x = request.JSON.get('x', thread.x) thread.y = request.JSON.get('y', thread.y) thread.save() - return Response(ThreadSerializer(thread).data, self.status_code,) + return Response(ThreadAdminSerializer(thread).data, self.status_code,) except Thread.DoesNotExist: - return Response("Thread doesn't exist.", self.status_code,) \ No newline at end of file + return Response("Thread doesn't exist.", self.status_code,) + + def get(self, request, key): + try: + return Response(ThreadUserSerializer(Thread.objects.get(key=key)).data, self.status_code) + except Thread.DoesNotExist: + return Response("Thread not found", status=404) + + +# class FindThreadView(APIView): +# renderer_classes = (JSONRenderer,) +# status_code = 200 +# +# def get(self, request): +# is_full = request.GET.get('full', True) +# if is_full: +# key = request.GET['key'] +# return Response(ThreadSerializer(Thread.objects.get(key=key)).data, self.status_code) \ No newline at end of file diff --git a/journals/serilizers.py b/journals/serilizers.py index 0142cf7..439a278 100644 --- a/journals/serilizers.py +++ b/journals/serilizers.py @@ -1,10 +1,42 @@ from rest_framework import serializers -from journals.models import Thread +from journals.models import Thread, Journal + + +class JournalSerializer(serializers.ModelSerializer): + + class Meta: + model = Journal + exclude = ('content_type', 'object_id') class ThreadSerializer(serializers.ModelSerializer): class Meta: model = Thread - exclude = ['is_staff'] \ No newline at end of file + exclude = ('is_staff', 'recurse_step') + + +class ThreadAdminSerializer(ThreadSerializer): + count_children = serializers.SerializerMethodField() + count_journals = serializers.SerializerMethodField() + + @staticmethod + def get_count_children(self): + return self.child_thread_count() + + @staticmethod + def get_count_journals(self): + return self.journals_count() + + +class ThreadUserSerializer(serializers.ModelSerializer): + journals = serializers.SerializerMethodField() + + class Meta: + model = Thread + fields = ('journals', 'key') + + @staticmethod + def get_journals(self): + return [JournalSerializer(i).data for i in self.journal_set.all()] \ No newline at end of file diff --git a/lms/settings.py b/lms/settings.py index 87a9e36..c671a23 100644 --- a/lms/settings.py +++ b/lms/settings.py @@ -61,8 +61,8 @@ EMAIL_PORT = '587' EMAIL_USE_TLS = True DEFAULT_FROM_EMAIL = 'robo@skillbox.ru' YANDEX_MONEY_SHOP_PASSWORD = 'nu5Xefise' -YANDEX_SHOP_ID = '84348' -YANDEX_scid = '78309' +YANDEX_SHOP_ID = '157133' +YANDEX_scid = '149639' # Application definition # место куда сохраняем пользовательские файлы @@ -92,7 +92,6 @@ INSTALLED_APPS = [ 'library', 'practice', 'precise_bbcode', - 'reactions', ] MIDDLEWARE_CLASSES = [ @@ -139,7 +138,7 @@ DATABASES = { 'NAME': os.environ.get('DB_NAME', 'codemy'), 'USER': os.environ.get('PG_ENV_POSTGRES_USER', 'team'), 'PASSWORD': os.environ.get('PG_ENV_POSTGRES_PASSWORD', 'nu5Xefise'), - 'HOST': os.environ.get('PG_PORT_5432_TCP_ADDR', '192.168.0.6'), + 'HOST': os.environ.get('PG_PORT_5432_TCP_ADDR', '127.0.0.1'), 'PORT': os.environ.get('PG_PORT_5432_TCP_PORT', '5432'), }, } diff --git a/reactions/migrations/0002_like.py b/reactions/migrations/0002_like.py deleted file mode 100644 index 3d0edc2..0000000 --- a/reactions/migrations/0002_like.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.3 on 2017-09-21 17:58 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('contenttypes', '0002_remove_content_type_name'), - ('reactions', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Like', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('is_positive', models.BooleanField(default=True)), - ('object_id', models.PositiveIntegerField()), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Инициатор действия')), - ], - ), - ]