Бекап перед переходом в принципиально новую систему

feature/fix_generate_pass
Andrey 8 years ago
parent 0dbf1c514e
commit ccac432200
  1. 3
      _utils/gen_diploms.py
  2. 71
      access/access_update.py
  3. 6
      access/admin.py
  4. 47
      access/migrations/0094_auto_20170929_1026.py
  5. 26
      access/migrations/0095_auto_20170929_1545.py
  6. 25
      access/models.py
  7. 7
      access/serializers.py
  8. 21
      courses/course_update.py
  9. 20
      courses/migrations/0048_auto_20170929_1026.py
  10. 21
      courses/migrations/0049_auto_20171002_1841.py
  11. 112
      courses/models.py
  12. 2
      courses/new_urls.py
  13. 59
      courses/new_view.py
  14. 42
      courses/serializers.py
  15. 2
      courses/signals.py
  16. 70
      journals/default_ations.py
  17. 342
      journals/default_threads.py
  18. 20
      journals/migrations/0077_journal_extra_data.py
  19. 26
      journals/migrations/0078_auto_20171009_0952.py
  20. 20
      journals/migrations/0079_thread_is_child.py
  21. 20
      journals/migrations/0080_auto_20171009_1736.py
  22. 24
      journals/migrations/0081_auto_20171009_1742.py
  23. 38
      journals/models.py
  24. 2
      journals/new_urls.py
  25. 27
      journals/new_view.py
  26. 36
      journals/serilizers.py
  27. 7
      lms/settings.py
  28. 29
      reactions/migrations/0002_like.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)

@ -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,
)

@ -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', )

@ -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',
),
]

@ -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='Активный объект'),
),
]

@ -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')
is_done = models.BooleanField(default=False, verbose_name=u'Выполнено?')
subject = models.ForeignKey(to=Vertex, verbose_name=u'Объект')

@ -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):
@ -13,3 +14,9 @@ class UserInitSerializer(serializers.ModelSerializer):
@staticmethod
def get_in_role(self):
return self.get_in_role_display()
class ExtraPrivilegeSerializer(serializers.ModelSerializer):
class Meta:
model = ExtraPrivilege
exclude = ('user',)

@ -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)

@ -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='Привилегии для узла не будут проверяться'),
),
]

@ -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,
),
]

@ -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,7 +79,8 @@ 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)
@ -88,7 +90,8 @@ class Course(models.Model):
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)
@ -100,13 +103,42 @@ class Course(models.Model):
recommend = models.ManyToManyField('self', verbose_name=u'Связанные курсы', blank=True,
help_text=u'Курсы, которые стоит порекомендовать вместе с этим')
def __str__(self):
return self.title
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 ''
@ -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
@ -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)

@ -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<slug>[-\w]+)/$', views.TreeView.as_view()),
url(r'directions/$', views.DirectionListView.as_view()),
url(r'^$', views.CourseListView.as_view()),
]

@ -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

@ -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):

@ -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()

@ -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='изминение чего-либо',
)

@ -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,20 +108,61 @@ 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,
)
@ -38,3 +170,205 @@ if __name__ == '__main__':
for i in get_user_model().objects.filter(email__in=support_list):
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 (инициализация является обязательной)
""")

@ -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=''),
),
]

@ -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='Подписчики'),
),
]

@ -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='Поле аптимизации поиска'),
),
]

@ -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',
),
]

@ -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='Поле аптимизации поиска'),
),
]

@ -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,6 +1701,34 @@ 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
@ -1705,3 +1736,6 @@ class Thread(models.Model):
class Action(models.Model):
name = models.CharField(max_length=255, verbose_name=u'Наименование действия (на английском)')
text = models.TextField(verbose_name=u'Описание действия')
def __str__(self):
return "%s (%s)" % (self.name, self.text)

@ -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<key>[-\w]+)/$', views.ThreadDetailView.as_view()),
]

@ -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,)
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)

@ -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']
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()]

@ -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'),
},
}

@ -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='Инициатор действия')),
],
),
]
Loading…
Cancel
Save