diff --git a/README.md b/README.md index 3451111..a50f717 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,11 @@ #**SkillBox LMS** -Подробные шаги для проверки работоспособности CI +Нужно выполнить миграции и запустить скрипт update_db.py без параметров в модуле courses (скрипт на всякий случай пока не удалять) -1) Проверяем доступ к репозиторию кода https://gitlab.com/skillbox/go.skillbox.ru -2) Клонируем проект -3) Вносим изменения в код -4) Комитим изменения в новый branch (ОБЯЗАТЕЛЬНО в отдельный Branch) -4.1) Пушим изменения -5) Ждем выполнения создания тестового сайта https://gitlab.com/skillbox/go.skillbox.ru/pipelines -6) Сайт будет доступен по адресу .lms.test.spicycms.com +Фактически в структуре данных изменилось только то, что появилась новая табличка NormalMap. Скрипт собирает все treeview курсов и кладёт в эту табличку -**Правила для разработчика** +Появилась новая зависимость django_rest_framework. -* Разрабока ведется в отдельном брэнче -* После комита изменений можно проверить свою работу по следующему адресу: .lms.test.spicycms.com -* После проверки тестового сайта по имени брэнча, брэнч помечается к мерж реквесту -* Ответсвенный инженер производит код ревью и запускает/отклоняет мерж -* Если сайт запустился на демо сайте, код в ручом режиме можно обновить на продакшен сервере +Изменения никак не трогают старый функционал. -**Замечания** - -* Мерж реквест лучше производить в ветку develop(чтобы исключить случайного обновления кода на демо сайте) -* Внимание! Разработчик обязан проверить работоспособность тестового сайта по имени созданного брэнча .lms.test.spicycms.com -* Если необходимы тестовые данные разработчик их подготавливает дополнительно -* Если нет возможности добавить тестовые данные в авто режиме, разработчик добавляет инструкцию в README файл по запуску необходимых команд -(Под тестовыми данными подразумеваются фикстуры) +Имеет смысл пробежаться по middl diff --git a/access/middleware.py b/access/middleware.py index ed280f9..d749b62 100644 --- a/access/middleware.py +++ b/access/middleware.py @@ -1,11 +1,16 @@ from django.http import HttpResponseForbidden +from django.http import QueryDict +import json + class CheckPerm(object): @staticmethod def process_request(request): - if '/admin' in request.path or "/management" in request.path \ - or '/analytics' in request.path: + if len(request.path) > 6 and \ + ('/admin' == request.path[:6] + or'/analy' == request.path[:6]): + #or "/api/v" == request.path[:6]): if not request.user.is_authenticated(): return HttpResponseForbidden() @@ -14,3 +19,17 @@ class CheckPerm(object): or request.user.in_role == "A" or request.user.is_admin): return HttpResponseForbidden() + + +class RequestToApi(object): + @staticmethod + def process_request(request): + if len(request.path) > 4 and '/api' == request.path[:4]: + if request.method == 'POST': + data = json.loads(request.body.decode('utf-8')) + q_data = QueryDict('', mutable=True) + for value in data: + q_data.update({value: data[value]}) + request.JSON = q_data + if request.method == 'POST' or request.method == 'DELETE': + setattr(request, '_dont_enforce_csrf_checks', True) \ No newline at end of file diff --git a/access/models.py b/access/models.py index 9735915..6b82ed8 100755 --- a/access/models.py +++ b/access/models.py @@ -133,7 +133,7 @@ class User(AbstractBaseUser): deactivate = models.BooleanField(verbose_name=u'Не давать новых студентов', default=False) delay = models.BooleanField(verbose_name=u'Могут быть задержки', default=False, help_text=u'Если возможны задержки с проверками') delay_description = models.TextField(verbose_name=u'Повод просрочки', blank=True) - delay_date = models.DateTimeField(verbose_name=u'Задержка до. Не включая этот день.', blank=True, default=datetime.datetime.now(), null=True) + delay_date = models.DateTimeField(verbose_name=u'Задержка до. Не включая этот день.', blank=True, default=django.utils.timezone.now, null=True) email = models.EmailField(verbose_name='email', max_length=255, blank=True, db_index=True, unique=True, help_text=u'Будьте аккуратны. Менять почту можно. Но только если очень нужно.') changed_email = models.EmailField(verbose_name=u'Ящик на замену', blank=True, null=True) @@ -185,6 +185,9 @@ class User(AbstractBaseUser): def get_ip_len(self): UserRequest.objects.filter(user=self).count() + def full_name(self): + return str(self.id) + ": " + self.fname + " " + self.name + " " + self.oname + def set_request_data(self, request): ip = get_client_ip(request) try: @@ -345,10 +348,10 @@ class User(AbstractBaseUser): def check_phone(self, _type='actual'): # actual = self.phone, back = self.back_phone phone = self.phone if _type == 'actual' else self.back_phone - return bool(len(''.join([n for n in phone if n in [str(x) for x in xrange(0,10)]])) > 9) + return bool(len(''.join([n for n in phone if n in [str(x) for x in range(0,10)]])) > 9) def clean_phone(self, _type='actual'): - return ''.join([n for n in self.phone if n in [str(x) for x in xrange(0,10)]]) if self.check_phone() else '' + return ''.join([n for n in self.phone if n in [str(x) for x in range(0,10)]]) if self.check_phone() else '' class Meta: diff --git a/access/new_urls.py b/access/new_urls.py new file mode 100644 index 0000000..bf13a00 --- /dev/null +++ b/access/new_urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url +from access import new_view as views + +urlpatterns = [ + url(r'teachers/$', views.TeacherListView.as_view()), + url(r'check/$', views.CheckUserView.as_view()), +] \ No newline at end of file diff --git a/access/new_view.py b/access/new_view.py new file mode 100644 index 0000000..8dd0b67 --- /dev/null +++ b/access/new_view.py @@ -0,0 +1,48 @@ +from django.contrib.auth import get_user_model +from django.contrib import auth +from rest_framework.views import APIView +from rest_framework.renderers import JSONRenderer +from rest_framework.response import Response + +from access.serializers import UserInitSerializer + + +class TeacherListView(APIView): + renderer_classes = (JSONRenderer,) + status_code = 200 + + def get(self, request): + return Response([teacher.full_name() for teacher in get_user_model().objects.filter( + in_role='T', + is_active=True, + reg_status=4, + )], self.status_code) + + +class CheckUserView(APIView): + renderer_classes = (JSONRenderer,) + status_code = 200 + + def get(self, request): + if request.user.is_authenticated() and (request.user.in_role in ['M', 'S', 'S2', 'A'] or request.user.is_admin): + return Response(True, status=self.status_code) + return Response(False, status=self.status_code) + + +class LoginView(APIView): + renderer_classes = (JSONRenderer,) + status_code = 200 + + def get(self, request): + if not request.user.is_authenticated(): + user = auth.authenticate(email=request.JSON.get('email'), password=request.JSON.get('password')) + return Response(UserInitSerializer(user).data, status=self.status_code) + return Response(status=403) + + +class LogoutView(APIView): + renderer_classes = (JSONRenderer,) + status_code = 204 + + def get(self, request): + return Response(False, status=self.status_code) \ No newline at end of file diff --git a/access/serializers.py b/access/serializers.py new file mode 100644 index 0000000..72c81e7 --- /dev/null +++ b/access/serializers.py @@ -0,0 +1,9 @@ +from django.contrib.auth import get_user_model +from rest_framework import serializers + + +class UserInitSerializer(serializers.ModelSerializer): + + class Meta: + model = get_user_model() + fields = '__all__' \ No newline at end of file diff --git a/api_v1/urls.py b/api_v1/urls.py new file mode 100644 index 0000000..ab83d98 --- /dev/null +++ b/api_v1/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls import url, include + +urlpatterns = [ + url(r'courses/', include('courses.new_urls')), + url(r'users/', include('access.new_urls')), +] \ No newline at end of file diff --git a/courses/migrations/0048_normalmap.py b/courses/migrations/0048_normalmap.py new file mode 100644 index 0000000..20d3589 --- /dev/null +++ b/courses/migrations/0048_normalmap.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-04 13:56 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('courses', '0047_auto_20170904_1355'), + ] + + 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='')), + ('course', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='courses.Course')), + ], + ), + ] diff --git a/courses/migrations/0049_auto_20170907_1753.py b/courses/migrations/0049_auto_20170907_1753.py new file mode 100644 index 0000000..5e51c04 --- /dev/null +++ b/courses/migrations/0049_auto_20170907_1753.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.3 on 2017-09-07 17:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('courses', '0048_normalmap'), + ] + + operations = [ + migrations.AddField( + model_name='normalmap', + name='independent_elements', + field=models.TextField(default=''), + ), + migrations.AlterField( + model_name='course', + name='level', + field=models.CharField(choices=[('B', 'Базовый'), ('A', 'Продвинутый'), ('E', 'Экспертный'), ('B+A', 'Базовый + Продвинутый')], default='B', max_length=3, verbose_name='Уровень'), + ), + ] diff --git a/courses/models.py b/courses/models.py index fd25d8c..1c3c41a 100755 --- a/courses/models.py +++ b/courses/models.py @@ -33,7 +33,7 @@ class MaterialDirection(models.Model): title = models.CharField(verbose_name=u'Заголовок', max_length=255) color = models.CharField(verbose_name=u'Цвет', max_length=50) description = RedactorField(verbose_name=u'Описание', blank=True) - mentors = models.ManyToManyField(User, verbose_name=u'Кураторы', null=True) + mentors = models.ManyToManyField(User, verbose_name=u'Кураторы') def __unicode__(self): return u'%s' % self.title @@ -66,7 +66,7 @@ class MaterialDirection(models.Model): class Course(models.Model): COURSE_LEVEL = ( ('B', u'Базовый'), - ('A', u'Продвинутый '), + ('A', u'Продвинутый'), ('E', u'Экспертный'), ('B+A', u'Базовый + Продвинутый') ) @@ -75,7 +75,7 @@ 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(User, verbose_name=u'Кураторы', null=True, blank=True, related_name='course_mentors') + mentors = models.ManyToManyField(User, 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) @@ -732,7 +732,7 @@ class AchievementsMap(models.Model): class Diploma(models.Model): - key = models.IntegerField(verbose_name=u'Последний ключ', max_length=255) + key = models.IntegerField(verbose_name=u'Последний ключ') icon = models.ImageField(verbose_name=u'Мини картинка', upload_to='diploms', null=True) course = models.ForeignKey(Course, verbose_name=u'Курс') out_image = models.ImageField(verbose_name=u'Диплом без печати', upload_to='diploms') @@ -743,21 +743,21 @@ class Diploma(models.Model): date_color = models.CharField(verbose_name=u'Цвет даты', max_length=255, null=True, help_text=u'RGB. Пример: 0,0,0') date_font = models.FileField(verbose_name=u'Шрифт даты', upload_to='diploms', null=True) - date_size = models.IntegerField(verbose_name=u'Размер даты', max_length=255, null=True) + date_size = models.IntegerField(verbose_name=u'Размер даты', null=True) # key_place = models.CharField(verbose_name=u'Размещение ключа', max_length=255, null=True, help_text=u'Пикселей сверху:пикселей слева - 256:256') key_color = models.CharField(verbose_name=u'Цвет ключа', max_length=255, null=True, help_text=u'RGB. Пример: 0,0,0') key_font = models.FileField(verbose_name=u'Шрифт ключа', upload_to='diploms', null=True) - key_size = models.IntegerField(verbose_name=u'Размер ключа', max_length=255, null=True) + key_size = models.IntegerField(verbose_name=u'Размер ключа', null=True) # name_place = models.CharField(verbose_name=u'Размещение имени', max_length=255, null=True, help_text=u'Пикселей сверху:пикселей слева - 256:256') name_color = models.CharField(verbose_name=u'Цвет имени', max_length=255, null=True, help_text=u'RGB. Пример: 0,0,0') name_font = models.FileField(verbose_name=u'Шрифт имени', upload_to='diploms', null=True) - name_size = models.IntegerField(verbose_name=u'Размер имени', max_length=255, null=True) + name_size = models.IntegerField(verbose_name=u'Размер имени', null=True) def __str__(self): return '%s' % self.course @@ -973,3 +973,9 @@ class Flow(models.Model): verbose_name = u'Поток' verbose_name_plural = u'Потоки' ordering = ['-modified'] + + +class NormalMap(models.Model): + course = models.OneToOneField(to=Course) + json_tree = models.TextField(default='') + independent_elements = models.TextField(default='') diff --git a/courses/new_urls.py b/courses/new_urls.py new file mode 100644 index 0000000..f531528 --- /dev/null +++ b/courses/new_urls.py @@ -0,0 +1,11 @@ +from django.conf.urls import url +from courses import new_view as views + +urlpatterns = [ + url(r'theme/detail/([0-9]{1,99})/$', views.ThemeDetailView.as_view()), + url(r'lesson/detail/([0-9]{1,99})/$', views.LessonDetailView.as_view()), + url(r'detail/([0-9]{1,99})/$', views.CourseDetailView.as_view()), + url(r'tree/([0-9]{1,99})/$', 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 new file mode 100644 index 0000000..ba42044 --- /dev/null +++ b/courses/new_view.py @@ -0,0 +1,72 @@ +from rest_framework.views import APIView +from rest_framework.renderers import JSONRenderer +from rest_framework.response import Response +from courses.models import Course, MaterialDirection, CourseTheme, Lesson +from courses.serializers import CourseTreeSerializer, CourseDetailSerializer, CourseListSerializer,\ + ThemeSerializer, LessonSerializer +from finance.models import Bill + + +class DirectionListView(APIView): + renderer_classes = (JSONRenderer,) + status_code = 200 + + def get(self, request): + return Response([direction.title for direction in MaterialDirection.objects.all()], self.status_code) + + +class TreeView(APIView): + renderer_classes = (JSONRenderer,) + status_code = 200 + + @staticmethod + def post(request): + print(request) + return Response(status=204) + + def get(self, request, id): + return Response(CourseTreeSerializer(Course.objects.get(id=id)).data, self.status_code) + + +class ThemeDetailView(APIView): + renderer_classes = (JSONRenderer,) + status_code = 200 + + def get(self, request, id): + return Response(ThemeSerializer(CourseTheme.objects.get(id=id)).data, self.status_code) + + +class LessonDetailView(APIView): + renderer_classes = (JSONRenderer,) + status_code = 200 + + def get(self, request, id): + return Response(LessonSerializer(Lesson.objects.get(id=id)).data, self.status_code) + + +class CourseDetailView(APIView): + renderer_classes = (JSONRenderer,) + status_code = 200 + + def get(self, request, id): + return Response(CourseDetailSerializer(Course.objects.get(id=id)).data, self.status_code) + + +class CourseListView(APIView): + renderer_classes = (JSONRenderer,) + status_code = 200 + + def get(self, request): + if not request.GET.get('staff', 'true') == 'false': + return Response([CourseListSerializer(i).data for i in Course.objects.all()], self.status_code) + + res = [] + for course in Course.objects.all(): + if course.public: + course_serialize = CourseListSerializer(course).data + course_serialize['is_mine'] = False + if Bill.objects.filter(service__course=course, user=request.user, status='F').exists(): + course_serialize['is_mine'] = True + res.append(course_serialize) + + return Response(res, self.status_code) \ No newline at end of file diff --git a/courses/serializers.py b/courses/serializers.py new file mode 100644 index 0000000..96f467d --- /dev/null +++ b/courses/serializers.py @@ -0,0 +1,159 @@ +from rest_framework import serializers +import json +# from django.contrib.auth import get_user_model +# from django.core.exceptions import ObjectDoesNotExist + +from courses.models import Course, CourseTheme, Lesson, Homework, Exam + + +class LessonSerializer(serializers.ModelSerializer): + on_comment = serializers.SerializerMethodField() + + class Meta: + model = Lesson + fields = ( + 'id', 'title', 'on_comment', 'materials', + 'free', 'video', 'video_id', + ) + + @staticmethod + def get_on_comment(self): + return self.on_comment == 'N' or self.on_comment == 'T' and self.theme.on_comment + + +class ThemeSerializer(serializers.ModelSerializer): + + class Meta: + model = CourseTheme + exclude = ('price_type', '_type', 'sort', 'on_comment') + + +class CourseListSerializer(serializers.ModelSerializer): + length = serializers.SerializerMethodField() + level = serializers.SerializerMethodField() + direction = serializers.SerializerMethodField() + + class Meta: + model = Course + fields = ['id', 'title', 'length', + 'level', 'direction', 'image',] + + @staticmethod + def get_length(self): + summary = 0 + for theme_slim in json.loads(self.normalmap.json_tree): + for simple_object in theme_slim['body']: + if simple_object.split('_')[1] == 'L': + summary += 1 + return summary + + @staticmethod + def get_level(self): + return self.get_level_display() + + @staticmethod + def get_direction(self): + return self.direction.title + + +class CourseTreeSerializer(serializers.ModelSerializer): + children = serializers.SerializerMethodField() + + class Meta: + model = Course + fields = ['id', 'title', 'children'] + + @staticmethod + def get_children(self): + theme_list = json.loads(self.normalmap.json_tree) + map = [] + for theme_slim in theme_list: + theme = CourseTheme.objects.get(id=theme_slim['id']) + theme_obj = { + 'id': theme.id, + 'title': theme.title, + 'lessons': [], + 'tasks': [], + } + for simple_object in theme_slim['body']: + val = simple_object.split('_')[0] + if simple_object.split('_')[1] == 'L': + lesson = Lesson.objects.get(id=val) + lesson_obj = {'id': lesson.id, 'title': lesson.title} + theme_obj['lessons'].append(lesson_obj) + + if simple_object.split('_')[1] == 'H': + task = Homework.objects.get(id=val) + task_obj = { + 'id': task.id, + 'is_exam': False, + } + theme_obj['tasks'].append(task_obj) + + if simple_object.split('_')[1] == 'E': + task = Exam.objects.get(id=val) + task_obj = { + 'id': task.id, + 'is_exam': True, + } + theme_obj['tasks'].append(task_obj) + + map.append(theme_obj) + + return map + + +class CourseDetailSerializer(serializers.ModelSerializer): + level = serializers.SerializerMethodField() + direction = serializers.SerializerMethodField() + teachers = serializers.SerializerMethodField() + + class Meta: + model = Course + exclude = ( + 'slug', 'mentors', 'page', 'sort', + 'preview', 'use_fail', 'basic_len', + 'addition_len', 'min_price', 'buy_icon', + 'must_build', 'keywords', 'recommend', + ) + + @staticmethod + def get_level(self): + return self.get_level_display() + + @staticmethod + def get_direction(self): + return self.direction.title + + @staticmethod + def get_teachers(self): + return [teacher.full_name() for teacher in self.teachers.all()] + + +# class UserSerializer(serializers.ModelSerializer): +# statistics = serializers.SerializerMethodField('get_statistic') +# games = serializers.SerializerMethodField('get_my_games') +# is_anonymous = serializers.BooleanField() +# +# @staticmethod +# def get_my_games(self): +# res = {} +# try: +# res['active'] = GameSerializer(self.game_set.get(state__lte=1)).data +# except ObjectDoesNotExist: +# res['active'] = {} +# +# res['archive'] = [GameSerializer(i).data for i in self.game_set.all().filter(state=2)] +# return res +# +# @staticmethod +# def get_statistic(self): +# try: +# statistics = StatisticSerializer(Statistic.objects.get(user=self)).data +# except ObjectDoesNotExist: +# statistics = {} +# return statistics +# +# class Meta: +# model = get_user_model() +# fields = ['id', 'username', 'email', 'is_active', 'statistics', 'games', 'is_anonymous'] \ No newline at end of file diff --git a/courses/update_db.py b/courses/update_db.py new file mode 100644 index 0000000..c7075cc --- /dev/null +++ b/courses/update_db.py @@ -0,0 +1,22 @@ +import os, sys +import django, json + +sys.path.append("../") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.settings") +django.setup() + +from courses.models import Course, NormalMap, CourseTheme, Lesson, Homework, Exam + +if __name__ == '__main__': + for course in Course.objects.all(): + tree = [] + for theme in CourseTheme.objects.filter(course=course).order_by('sort'): + tree.append({'id': theme.id, 'body': + [str(i.id) + "_L" for i in Lesson.objects.filter(theme=theme).order_by('sort')] + + [str(i.id) + "_H" for i in Homework.objects.filter(theme=theme).order_by('sort')] + + [str(i.id) + "_E" for i in Exam.objects.filter(theme=theme).order_by('sort')] + }) + + obj, _is_create = NormalMap.objects.get_or_create(course=course) + obj.json_tree = json.dumps(tree) + obj.save() \ No newline at end of file diff --git a/fabfile.py b/fabfile.py index ff94072..2daf56a 100644 --- a/fabfile.py +++ b/fabfile.py @@ -296,7 +296,7 @@ def create(): with virtualenv(): #manage('migrate --run-syncdb --noinput') #manage('migrate --list') - manage("migrate --noinput") + #manage("migrate --noinput") manage("collectstatic -l --noinput") #if exists('%sgunicorn.pid'): diff --git a/finance/migrations/0071_bill_flow.py b/finance/migrations/0071_bill_flow.py deleted file mode 100644 index 0732ea7..0000000 --- a/finance/migrations/0071_bill_flow.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.3 on 2017-08-10 09:04 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('courses', '0045_auto_20170810_0904'), - ('finance', '0070_auto_20170424_1638'), - ] - - operations = [ - migrations.AddField( - model_name='bill', - name='flow', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='courses.Flow', verbose_name='Поток'), - ), - ] diff --git a/finance/models.py b/finance/models.py index 3ac1b01..e67ccb8 100755 --- a/finance/models.py +++ b/finance/models.py @@ -30,7 +30,7 @@ class Installment(models.Model): u'предоставлять доступ после ' u'просрочки') expired = models.BooleanField(verbose_name=u'Просрочено', default=False) - payments = models.ManyToManyField('Bill', verbose_name=u'Платежи', blank=True, null=True, related_name='bill_point') + payments = models.ManyToManyField('Bill', verbose_name=u'Платежи', blank=True, related_name='bill_point') def __str__(self): return '%s %s' % (self.user, self.price) @@ -54,7 +54,7 @@ class Price(models.Model): cost = models.IntegerField(verbose_name=u'Цена') course = models.ForeignKey(Course, verbose_name=u'Курс', null=True, blank=True) description = models.TextField(verbose_name=u'Описание', help_text=u'Будет показано менеджерам') - included = models.ManyToManyField(CourseMap, verbose_name=u'Включены', null=True, blank=True, + included = models.ManyToManyField(CourseMap, verbose_name=u'Включены', blank=True, help_text=u'Если задействовать эту функцию, стандартная схема подписок будет ' u'не активна') by_time = models.IntegerField(verbose_name=u'Дней доступа', default=0, blank=True, null=True, @@ -157,8 +157,8 @@ class Bill(models.Model): fire_date_sent = models.BooleanField(verbose_name=u'Письмо сгорания', default=False, editable=False) modals_show = models.BooleanField(verbose_name=u'Модалки показаны', default=False, editable=False) created_sent = models.BooleanField(verbose_name=u'Отправлено письмо сформированного счета', default=False, editable=False) - create_letters = models.ManyToManyField(MailBox, verbose_name=u'Письма при создании', null=True, blank=True, related_name='bill_create_letter', editable=False) - finish_letters = models.ManyToManyField(MailBox, verbose_name=u'Письма завершения', null=True, blank=True, related_name='bill_finish_letter', editable=False) + create_letters = models.ManyToManyField(MailBox, verbose_name=u'Письма при создании', blank=True, related_name='bill_create_letter', editable=False) + finish_letters = models.ManyToManyField(MailBox, verbose_name=u'Письма завершения', blank=True, related_name='bill_finish_letter', editable=False) def __unicode__(self): return u'%s:%s %s %s' % (self.id, self.get_status_display(), self.user, self.manager if self.manager else '') @@ -444,7 +444,7 @@ class ServiceRequest(models.Model): class RequestAlert(models.Model): title = models.CharField(verbose_name=u'Классификатор', max_length=255, editable=False) - requests = models.ManyToManyField(ServiceRequest, verbose_name=u'Количество заявок', blank=True, null=True, editable=False) + requests = models.ManyToManyField(ServiceRequest, verbose_name=u'Количество заявок', blank=True, editable=False) mails = models.TextField(verbose_name=u'Список почт', blank=True, help_text=u'Кому разослать дубли заявки. ЧЕРЕЗ ТОЧКУ С ЗАПЯТОЙ') date = models.DateTimeField(verbose_name=u'Начало заявок', default=datetime.datetime.now, editable=False) diff --git a/journals/models.py b/journals/models.py index d37b418..6616ccf 100755 --- a/journals/models.py +++ b/journals/models.py @@ -43,7 +43,7 @@ class TeacherJ(models.Model): start_date = models.DateTimeField(verbose_name=u'Дата начала', blank=True, null=True) opens = models.ManyToManyField(CourseMap, verbose_name=u'Насильные открытия', blank=True, editable=False, related_name='map_opens') - waiting = models.ManyToManyField(CourseMap, verbose_name=u'Доп изучения', blank=True, null=True, + waiting = models.ManyToManyField(CourseMap, verbose_name=u'Доп изучения', blank=True, related_name='map_waiting') def __str__(self): @@ -1398,7 +1398,7 @@ class DiplomaJ(models.Model): parent = models.ForeignKey(ExamJ, verbose_name=u'Журнал сдачи', blank=True, null=True) material = models.ForeignKey(Diploma, verbose_name=u'Шаблон диплома') date = models.DateField(verbose_name=u'Дата выдачи', default=datetime.date.today) - key = models.IntegerField(verbose_name=u'Ключ', max_length=255, blank=True, null=True) + key = models.IntegerField(verbose_name=u'Ключ', blank=True, null=True) student = models.ForeignKey(User, verbose_name=u'Студент', null=True) out_image = models.ImageField(verbose_name=u'Картинка без печати', upload_to='diploms', blank=True) in_image = models.ImageField(verbose_name=u'Картинка c печатью', upload_to='diploms', blank=True) diff --git a/library/models.py b/library/models.py index 94bad25..3dd0c20 100755 --- a/library/models.py +++ b/library/models.py @@ -36,12 +36,12 @@ class Article(models.Model): css = models.TextField(verbose_name=u'CSS', blank=True, default='', help_text=u'Можно добавлять все, но только исправить http на https') js = models.TextField(verbose_name=u'JS', blank=True, default='', help_text=u'Удалить bootstrap, jquery и заменить http на https') date = models.DateTimeField(verbose_name=u'Дата публикации', default=datetime.datetime.now, editable=False) - tags = models.ManyToManyField('Tags', verbose_name=u'Теги', blank=True, null=True) - likes = models.ManyToManyField(User, verbose_name=u'Лайки', max_length=255, null=True, blank=True, editable=False, related_name=u'acticle_likes') + tags = models.ManyToManyField('Tags', verbose_name=u'Теги', blank=True) + likes = models.ManyToManyField(User, verbose_name=u'Лайки', max_length=255, blank=True, editable=False, related_name=u'acticle_likes') views = models.ManyToManyField(User, verbose_name=u'Просмотры', max_length=255, default=0, related_name='article_views', editable=False) all_views = models.IntegerField(verbose_name=u'Всего просмотров', default=0, blank=True) - comments = models.ManyToManyField(Comment, verbose_name=u'Комментарии', null=True, blank=True, editable=False) - favorite = models.ManyToManyField(User, verbose_name=u'В фаворитах', blank=True, null=True, related_name='article_as_favorites', editable=False) + comments = models.ManyToManyField(Comment, verbose_name=u'Комментарии', blank=True, editable=False) + favorite = models.ManyToManyField(User, verbose_name=u'В фаворитах', blank=True, related_name='article_as_favorites', editable=False) def __str__(self): return '%s' % self.title diff --git a/lms/settings.py b/lms/settings.py index 930b62a..2813897 100644 --- a/lms/settings.py +++ b/lms/settings.py @@ -104,6 +104,7 @@ MIDDLEWARE_CLASSES = [ 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'access.middleware.CheckPerm', + 'access.middleware.RequestToApi', ] ROOT_URLCONF = 'lms.urls' diff --git a/lms/urls.py b/lms/urls.py index a389dde..8d6616d 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -24,6 +24,7 @@ urlpatterns = [ url(r'^management/', include('management.urls')), url(r'^teacher/', include('access.teach_urls')), url(r'^wallet/', include('finance.urls')), + url(r'^api/v1/', include('api_v1.urls')), url(r'^courses/', include('courses.urls')), url(r'^journals/', include('journals.urls')), url(r'^library/', include('library.urls')), diff --git a/management/models.py b/management/models.py index 3333cec..2594365 100755 --- a/management/models.py +++ b/management/models.py @@ -154,8 +154,8 @@ class Comment(models.Model): date = models.DateTimeField(verbose_name=u'Отправка', default=datetime.datetime.now) files = models.ManyToManyField(Storage, verbose_name=u'Прикрепленые файлы', blank=True, editable=False) send = models.BooleanField(verbose_name=u'Отправлено', default=False, editable=False) - replies = models.ManyToManyField('self', verbose_name=u'Ответы', blank=True, null=True, related_name='all_text_response', editable=False) - answers = models.ManyToManyField('self', verbose_name=u'Форумные ответы', blank=True, null=True, related_name='forum_answers', editable=False) + replies = models.ManyToManyField('self', verbose_name=u'Ответы', blank=True, related_name='all_text_response', editable=False) + answers = models.ManyToManyField('self', verbose_name=u'Форумные ответы', blank=True, related_name='forum_answers', editable=False) def __unicode__(self): return str(self.id) @@ -329,7 +329,7 @@ class Faq(models.Model): question = models.CharField(verbose_name=u'Вопрос', max_length=255) answer = RedactorField(verbose_name=u'Ответ') rating = models.IntegerField(verbose_name=u'Рейтинг статьи', default=0) - comments = models.ManyToManyField(Comment, verbose_name=u'Комментарии', blank=True, null=True, editable=False) + comments = models.ManyToManyField(Comment, verbose_name=u'Комментарии', blank=True, editable=False) date = models.DateTimeField(verbose_name=u'Дата создания', default=datetime.datetime.now, editable=False) def __str__(self): diff --git a/practice/models.py b/practice/models.py index d2fc2a1..1fcb7aa 100644 --- a/practice/models.py +++ b/practice/models.py @@ -53,7 +53,7 @@ class DataSheet(models.Model): data = models.TextField(verbose_name=u'Данные') split_char = models.CharField(verbose_name=u'Разделяющий символ', max_length=10, default=', ', blank=True) date = models.DateTimeField(verbose_name=u'Дата создания', default=datetime.datetime.now, editable=False) - users = models.ManyToManyField(User, verbose_name=u'Пользователи', blank=True, null=True, editable=False) + users = models.ManyToManyField(User, verbose_name=u'Пользователи', blank=True, editable=False) def __unicode__(self): return u'%s' % self.title @@ -117,13 +117,13 @@ class Workshop(models.Model): content = RedactorField(verbose_name=u'Описание методов', blank=True, default='', help_text=u'Добавление в /, после описаний и решений') variables = RedactorField(verbose_name=u'Описание переменных', blank=True, default='') - tools = models.ManyToManyField(WorkshopTools, verbose_name=u'Используемые инструменты', blank=True, null=True) + tools = models.ManyToManyField(WorkshopTools, verbose_name=u'Используемые инструменты', blank=True) open_resolve = models.BooleanField(verbose_name=u'Открыть ответ', default=False) resolve = RedactorField(verbose_name=u'Ответ', blank=True, default='') will_open_resolve = models.DateTimeField(verbose_name=u'Открыть ответ в дату', blank=True, null=True, help_text=u'Доступ к ответу открыть в указанную дату, если это вообще нужно') date = models.DateTimeField(verbose_name=u'Дата создания', default=datetime.datetime.now, editable=False) - users = models.ManyToManyField(User, verbose_name=u'Пользователи', blank=True, null=True, editable=False) + users = models.ManyToManyField(User, verbose_name=u'Пользователи', blank=True, editable=False) views = models.IntegerField(verbose_name=u'Просмотров комнаты', default=0) def __unicode__(self): diff --git a/requirements.txt b/requirements.txt index 08a729c..8acb06a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ contextlib2==0.5.4 cycler==0.10.0 decorator==4.0.10 diff-match-patch==20110725.1 +djangorestframework==3.6.4 django-admin-tools==0.8.0 django-celery==3.1.17 django-import-export==0.4.5