From af4261f409ee141aca84469b0a8b953fe3c10872 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 22 Sep 2017 17:50:38 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6=D0=B5?= =?UTF-8?q?=D1=80=20=D1=83=D0=B7=D0=BB=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- access/access_update.py | 0 access/migrations/0091_privilege.py | 22 ++++++ access/migrations/0092_auto_20170921_1707.py | 28 +++++++ access/models.py | 6 +- courses/admin.py | 26 +++--- courses/apps.py | 5 +- courses/course_update.py | 75 ++++++++++++++++++ courses/migrations/0047_auto_20170921_1746.py | 29 +++++++ courses/models.py | 79 ++++++++++++------- courses/signals.py | 9 +++ finance/models.py | 6 +- journals/admin.py | 9 ++- journals/default_threads.py | 40 ++++++++++ .../migrations/0076_auto_20170922_1116.py | 25 ++++++ .../migrations/0077_auto_20170922_1117.py | 20 +++++ .../migrations/0078_auto_20170922_1118.py | 20 +++++ .../migrations/0079_auto_20170922_1123.py | 24 ++++++ journals/models.py | 23 +++--- journals/new_urls.py | 0 journals/new_view.py | 0 lms/settings.py | 1 + reactions/migrations/0002_like.py | 29 +++++++ 22 files changed, 416 insertions(+), 60 deletions(-) create mode 100644 access/access_update.py create mode 100644 access/migrations/0091_privilege.py create mode 100644 access/migrations/0092_auto_20170921_1707.py create mode 100644 courses/course_update.py create mode 100644 courses/migrations/0047_auto_20170921_1746.py create mode 100644 courses/signals.py create mode 100644 journals/default_threads.py create mode 100644 journals/migrations/0076_auto_20170922_1116.py create mode 100644 journals/migrations/0077_auto_20170922_1117.py create mode 100644 journals/migrations/0078_auto_20170922_1118.py create mode 100644 journals/migrations/0079_auto_20170922_1123.py create mode 100644 journals/new_urls.py create mode 100644 journals/new_view.py create mode 100644 reactions/migrations/0002_like.py diff --git a/access/access_update.py b/access/access_update.py new file mode 100644 index 0000000..e69de29 diff --git a/access/migrations/0091_privilege.py b/access/migrations/0091_privilege.py new file mode 100644 index 0000000..450510e --- /dev/null +++ b/access/migrations/0091_privilege.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-21 17:07 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('access', '0090_auto_20170918_0811'), + ] + + operations = [ + migrations.CreateModel( + name='Privilege', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('value', models.CharField(choices=[('r', 'Отображение'), ('u', 'Использование'), ('w', 'Изменение')], default='r', max_length=1, verbose_name='Права')), + ], + ), + ] diff --git a/access/migrations/0092_auto_20170921_1707.py b/access/migrations/0092_auto_20170921_1707.py new file mode 100644 index 0000000..fbdf0c3 --- /dev/null +++ b/access/migrations/0092_auto_20170921_1707.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-21 17:07 +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 = [ + ('access', '0091_privilege'), + ('courses', '0046_auto_20170921_1707'), + ] + + operations = [ + migrations.AddField( + model_name='privilege', + name='subject', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Vertex', verbose_name='Объект'), + ), + migrations.AddField( + model_name='privilege', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Правообладатель'), + ), + ] diff --git a/access/models.py b/access/models.py index aa7b96b..79a9e04 100755 --- a/access/models.py +++ b/access/models.py @@ -630,9 +630,9 @@ class Questionnaire(models.Model): class Privilege(models.Model): TYPES = ( - ('r', 'Отображение'), - ('u', 'Использование'), - ('w', 'Изменение'), + ('r', 'Доступно для выполнению'), + ('w', 'Ждёт ответа'), + ('d', 'Выполнено'), ) user = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name=u'Правообладатель') value = models.CharField(verbose_name=u'Права', choices=TYPES, max_length=1, default='r') diff --git a/courses/admin.py b/courses/admin.py index 619c731..2a10b8a 100755 --- a/courses/admin.py +++ b/courses/admin.py @@ -2,8 +2,14 @@ from django.contrib import admin from access.models import User from courses.models import Lesson, Course, CourseTheme, Homework, Exam, \ - Skills, Achievements, SkillJ, CourseMap, \ - AchievementsMap, Diploma, MaterialDirection, Flow + Skills, Achievements, SkillJ, CourseMap, NormalMap, Topic, Task, Vertex,\ + AchievementsMap, Diploma, MaterialDirection, Tutorial + +admin.site.register(NormalMap) +admin.site.register(Topic) +admin.site.register(Task) +admin.site.register(Vertex) +admin.site.register(Tutorial) class LessonAdmin(admin.ModelAdmin): @@ -122,12 +128,12 @@ class DiplomaAdmin(admin.ModelAdmin): admin.site.register(Diploma, DiplomaAdmin) -class FlowAdmin(admin.ModelAdmin): - save_on_top = True - list_display = ('title', 'course', 'start_flow') - raw_id_fields = ['course'] - list_filter = ('start_flow','course') - search_fields = ['title', 'description'] - -admin.site.register(Flow, FlowAdmin) +# class FlowAdmin(admin.ModelAdmin): +# save_on_top = True +# list_display = ('title', 'course', 'start_flow') +# raw_id_fields = ['course'] +# list_filter = ('start_flow','course') +# search_fields = ['title', 'description'] +# +# admin.site.register(Flow, FlowAdmin) diff --git a/courses/apps.py b/courses/apps.py index d537555..879596c 100644 --- a/courses/apps.py +++ b/courses/apps.py @@ -4,4 +4,7 @@ from django.apps import AppConfig class CoursesAppConfig(AppConfig): name = "courses" - verbose_name = "Курсы" \ No newline at end of file + verbose_name = "Курсы" + + def ready(self): + import courses.signals \ No newline at end of file diff --git a/courses/course_update.py b/courses/course_update.py new file mode 100644 index 0000000..867b29d --- /dev/null +++ b/courses/course_update.py @@ -0,0 +1,75 @@ +import os, sys +import django, json + +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 django.contrib.contenttypes.models import ContentType +from courses.models import Course, NormalMap, CourseTheme, Lesson, Homework, Exam, \ + Tutorial, Task, Topic, Vertex + +if __name__ == '__main__': + for course in Course.objects.all(): + tree = [] + for theme in CourseTheme.objects.filter(course=course).order_by('sort'): + + topic_vertex = Vertex.manager.create_with_dependencies( + course=course, + title=theme.title, + description=theme.description, + model='topic', + icon=theme.icon, + ) + + tree.append(topic_vertex.id) + children = [] + 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 + tutor = Tutorial.objects.create(video=i.video, on_comment=on_comment) + [tutor.materials.add(j) for j in i.materials.all()] + lesson_type = ContentType.objects.get(app_label="courses", model="tutorial") + vertex = Vertex.objects.create( + course=course, + title=i.title, + description=i.description, + content_type=lesson_type, + object_id=tutor.id, + ) + topic_vertex.children.add(vertex) + children.append(vertex.id) + + for i in Homework.objects.filter(theme=theme).order_by('sort'): + task = Task.objects.create(is_exam=False,) + [task.materials.add(j) for j in i.materials.all()] + task_type = ContentType.objects.get(app_label="courses", model="task") + vertex = Vertex.objects.create( + course=course, + title='Домашняя работа', + description=i.description, + content_type=task_type, + object_id=task.id, + ) + topic_vertex.children.add(vertex) + children.append(vertex.id) + + for i in Exam.objects.filter(theme=theme).order_by('sort'): + task=Task.objects.create(is_exam=True,) + [task.materials.add(j) for j in i.materials.all()] + task_type = ContentType.objects.get(app_label="courses", model="task") + vertex = Vertex.objects.create( + course=course, + title='Экзамен', + description=i.description, + content_type=task_type, + object_id=task.id, + ) + topic_vertex.children.add(vertex) + children.append(vertex.id) + + tree.append(children) + + course_map, _is_create = NormalMap.objects.get_or_create(course=course) + course_map.json_tree=json.dumps(tree) + course_map.save() \ No newline at end of file diff --git a/courses/migrations/0047_auto_20170921_1746.py b/courses/migrations/0047_auto_20170921_1746.py new file mode 100644 index 0000000..ae9025a --- /dev/null +++ b/courses/migrations/0047_auto_20170921_1746.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-21 17:46 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('courses', '0046_auto_20170921_1707'), + ] + + operations = [ + migrations.CreateModel( + name='NormalMap', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('json_tree', models.TextField(default='')), + ('independent_elements', models.TextField(default='')), + ], + ), + migrations.AddField( + model_name='normalmap', + name='course', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='courses.Course'), + ), + ] diff --git a/courses/models.py b/courses/models.py index ef1ba6d..a1d2edb 100755 --- a/courses/models.py +++ b/courses/models.py @@ -948,34 +948,34 @@ class CourseBuilder: return self.token if self.material else None # Вернуть токен для материала если он есть -class Flow(models.Model): - title = models.CharField(verbose_name=u'Название', max_length=255) - description = models.TextField( - verbose_name=u'Описание', default='', blank=True, null=True) - course = models.ForeignKey(Course, verbose_name=u'Курс') - start_flow = models.DateTimeField( - verbose_name=u'Дата начала потока', default=datetime.datetime.now) - created = models.DateTimeField( - default=datetime.datetime.now, verbose_name=u"Создан", editable=False) - modified = models.DateTimeField( - auto_now=True, verbose_name=u"Изменен") - - def __str__(self): - return '%s / %s' % (self.title, self.course) - - def __unicode__(self): - return '%s / %s' % (self.title, self.course) - - def get_status(self): - if self.start_flow <= datetime.datetime.now(): - return True - else: - return False - - class Meta: - verbose_name = u'Поток' - verbose_name_plural = u'Потоки' - ordering = ['-modified'] +# class Flow(models.Model): +# title = models.CharField(verbose_name=u'Название', max_length=255) +# description = models.TextField( +# verbose_name=u'Описание', default='', blank=True, null=True) +# course = models.ForeignKey(Course, verbose_name=u'Курс') +# start_flow = models.DateTimeField( +# verbose_name=u'Дата начала потока', default=datetime.datetime.now) +# created = models.DateTimeField( +# default=datetime.datetime.now, verbose_name=u"Создан", editable=False) +# modified = models.DateTimeField( +# auto_now=True, verbose_name=u"Изменен") +# +# def __str__(self): +# return '%s / %s' % (self.title, self.course) +# +# def __unicode__(self): +# return '%s / %s' % (self.title, self.course) +# +# def get_status(self): +# if self.start_flow <= datetime.datetime.now(): +# return True +# else: +# return False +# +# class Meta: +# verbose_name = u'Поток' +# verbose_name_plural = u'Потоки' +# ordering = ['-modified'] # Модели нового API со временем всё, что выше будет выпилено @@ -994,6 +994,21 @@ class Topic(models.Model): icon = models.ImageField(verbose_name=u'Иконка темы', upload_to='CourseTheme', null=True, blank=True) +class VertexManager(models.Manager): + # Менеджер вершин графа. На самом деле + + def create_with_dependencies(self, model, course, title, description, **kwargs): + content_type = ContentType.objects.get(app_label='courses', model=model) + obj = content_type.model_class().objects.create(**kwargs) + return self.create( + content_type=content_type, + object_id=obj.id, + course=course, + title=title, + description=description, + ) + + class Vertex(models.Model): course = models.ForeignKey(to=Course) title = models.CharField(verbose_name=u'Название', max_length=255) @@ -1004,6 +1019,14 @@ class Vertex(models.Model): object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') + manager = VertexManager() + + def __str__(self): + return self.title + ': ' + self.get_type() + + def get_type(self): + return self.content_type.__str__() + class NormalMap(models.Model): course = models.OneToOneField(to=Course) diff --git a/courses/signals.py b/courses/signals.py new file mode 100644 index 0000000..6d41363 --- /dev/null +++ b/courses/signals.py @@ -0,0 +1,9 @@ +from django.db.models.signals import pre_delete +from django.dispatch import receiver + +from courses.models import Vertex + + +@receiver(pre_delete, sender=Vertex) +def delete_dependencies(instance, **kwargs): + instance.content_object.delete() diff --git a/finance/models.py b/finance/models.py index e67ccb8..b54a7d3 100755 --- a/finance/models.py +++ b/finance/models.py @@ -14,7 +14,7 @@ from management.letters import sent_created_my_self_bill, sent_new_my_self_bill, pay_no_public_course, sent_new_service_request_to_out from service.models import MailBox -from courses.models import Flow +#from courses.models import Flow class Installment(models.Model): @@ -129,8 +129,8 @@ class Bill(models.Model): help_text=u'Сумма, минус комиссия') traf_source = models.ForeignKey(TrafHistory, verbose_name=u'Обращение', blank=True, null=True, editable=False) service = models.ForeignKey(Price, verbose_name=u'Оплачиваемая услуга') - flow = models.ForeignKey( - Flow, verbose_name=u'Поток', blank=True, null=True) + # flow = models.ForeignKey( + # Flow, verbose_name=u'Поток', blank=True, null=True) inside_data = models.TextField(verbose_name=u'Данные проверки', default='', blank=True, editable=False) # user = models.ForeignKey(User, verbose_name=u'Плательщик', related_name=u'bill_user') diff --git a/journals/admin.py b/journals/admin.py index f18027a..8168ece 100755 --- a/journals/admin.py +++ b/journals/admin.py @@ -7,8 +7,13 @@ from django.http import HttpResponse from access.models import User from journals.models import TeacherJ, LessonJ, HomeworkJ, \ ExamJ, AchievementJ, HomeworkTry, \ - ExamTry, CourseThemeJ, DiplomaJ, ReportDepth -from import_export.admin import ImportExportModelAdmin + ExamTry, CourseThemeJ, DiplomaJ, ReportDepth,\ + Thread, Journal, Action + + +admin.site.register(Thread) +admin.site.register(Journal) +admin.site.register(Action) def export_xls(modeladmin, request, queryset): diff --git a/journals/default_threads.py b/journals/default_threads.py new file mode 100644 index 0000000..12859f5 --- /dev/null +++ b/journals/default_threads.py @@ -0,0 +1,40 @@ +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 Thread +from django.contrib.auth import get_user_model + +if __name__ == '__main__': + support_list = [ + 'kate.gazukina@skillbox.ru', + 'katerina.ragozina@skillbox.ru', + ] + + admin_list = [ + 'andrey.korolev@skillbox.ru', + 'pavel.matveev@skillbox.ru', + ] + + admin_thread, _is_create = Thread.objects.get_or_create( + key='Admin', + text='Тред для админов сюда падают все журналируемые сообщения в системе', + is_staff=True, + ) + + for i in get_user_model().objects.filter(email__in=admin_list): + admin_thread.subscribers.add(i) + + support_thread, _is_create = Thread.objects.get_or_create( + key='Support', + 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 diff --git a/journals/migrations/0076_auto_20170922_1116.py b/journals/migrations/0076_auto_20170922_1116.py new file mode 100644 index 0000000..1aeccbb --- /dev/null +++ b/journals/migrations/0076_auto_20170922_1116.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-22 11:16 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0075_remove_journal_body'), + ] + + operations = [ + migrations.AddField( + model_name='thread', + name='is_staff', + field=models.BooleanField(default=True, verbose_name='Видно ли в админке'), + ), + migrations.AlterField( + model_name='thread', + name='text', + field=models.TextField(default='', verbose_name='Описание треда'), + ), + ] diff --git a/journals/migrations/0077_auto_20170922_1117.py b/journals/migrations/0077_auto_20170922_1117.py new file mode 100644 index 0000000..c4cccbb --- /dev/null +++ b/journals/migrations/0077_auto_20170922_1117.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-22 11:17 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0076_auto_20170922_1116'), + ] + + operations = [ + migrations.AlterField( + model_name='thread', + name='is_staff', + field=models.BooleanField(default=False, verbose_name='Видно ли в админке'), + ), + ] diff --git a/journals/migrations/0078_auto_20170922_1118.py b/journals/migrations/0078_auto_20170922_1118.py new file mode 100644 index 0000000..505fa53 --- /dev/null +++ b/journals/migrations/0078_auto_20170922_1118.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-22 11:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0077_auto_20170922_1117'), + ] + + operations = [ + migrations.AlterField( + model_name='thread', + name='is_staff', + field=models.BooleanField(default=False, verbose_name='Админская ли табличка'), + ), + ] diff --git a/journals/migrations/0079_auto_20170922_1123.py b/journals/migrations/0079_auto_20170922_1123.py new file mode 100644 index 0000000..57085c9 --- /dev/null +++ b/journals/migrations/0079_auto_20170922_1123.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-22 11:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0078_auto_20170922_1118'), + ] + + operations = [ + migrations.RemoveField( + model_name='thread', + name='children', + ), + migrations.AddField( + model_name='thread', + name='parent', + field=models.ManyToManyField(blank=True, related_name='_thread_parent_+', to='journals.Thread'), + ), + ] diff --git a/journals/models.py b/journals/models.py index 360c601..acac6af 100755 --- a/journals/models.py +++ b/journals/models.py @@ -118,12 +118,6 @@ class TeacherJ(models.Model): def get_head_face(self): if self.start_date and self.student: by = Bill.objects.filter(service__course=self.course, status='F', user=self.student) - flow_status = None - start_flow = None - if by.exists(): - if by[0].flow: - flow_status = by[0].flow.get_status() - start_flow = by[0].flow.start_flow return { 'title': self.course.get_title(), 'teacher': self.teacher, @@ -141,9 +135,6 @@ class TeacherJ(models.Model): 'page': self.course.page, 'course_id': self.course.id, 'by': by.exists(), - 'by_type': '', - 'by_flow': flow_status, - 'by_start_flow': start_flow } else: return self.get_empty_head_face() @@ -1682,7 +1673,6 @@ 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) - body = models.TextField(default='') object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') action_type = models.ForeignKey(to='Action') @@ -1694,14 +1684,21 @@ class Journal(models.Model): class Thread(models.Model): key = models.CharField(max_length=200) - text = models.TextField(verbose_name=u'Описание треда') + 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'Подписчики') - children = models.ManyToManyField(to='self', blank=True) + parent = models.ManyToManyField(to='self', blank=True) def get_journals(self, **filter_extra): - threads = [i for i in self.children.all()].append(self) + threads = [i for i in self.thread_set.all()].append(self) return Journal.objects.filter(thread_in=threads, **filter_extra).order_by('-date') + 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 __str__(self): + return self.key + class Action(models.Model): name = models.CharField(max_length=255, verbose_name=u'Наименование действия (на английском)') diff --git a/journals/new_urls.py b/journals/new_urls.py new file mode 100644 index 0000000..e69de29 diff --git a/journals/new_view.py b/journals/new_view.py new file mode 100644 index 0000000..e69de29 diff --git a/lms/settings.py b/lms/settings.py index 2813897..87a9e36 100644 --- a/lms/settings.py +++ b/lms/settings.py @@ -92,6 +92,7 @@ INSTALLED_APPS = [ 'library', 'practice', 'precise_bbcode', + 'reactions', ] MIDDLEWARE_CLASSES = [ diff --git a/reactions/migrations/0002_like.py b/reactions/migrations/0002_like.py new file mode 100644 index 0000000..3d0edc2 --- /dev/null +++ b/reactions/migrations/0002_like.py @@ -0,0 +1,29 @@ +# -*- 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='Инициатор действия')), + ], + ), + ]