From 7fd5d662eb347014ce428a1051fafa5f8ca64898 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 6 Sep 2017 18:05:44 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 26 +---- access/middleware.py | 24 ++++- access/models.py | 3 + access/new_urls.py | 6 ++ access/new_view.py | 16 +++ api_v1/urls.py | 6 ++ courses/migrations/0048_normalmap.py | 24 +++++ courses/models.py | 2 +- courses/new_urls.py | 10 ++ courses/new_view.py | 50 ++++++++++ courses/serializers.py | 144 +++++++++++++++++++++++++++ courses/update_db.py | 22 ++++ finance/migrations/0071_bill_flow.py | 22 ---- lms/settings.py | 3 +- lms/urls.py | 1 + requirements.txt | 1 + 16 files changed, 313 insertions(+), 47 deletions(-) create mode 100644 access/new_urls.py create mode 100644 access/new_view.py create mode 100644 api_v1/urls.py create mode 100644 courses/migrations/0048_normalmap.py create mode 100644 courses/new_urls.py create mode 100644 courses/new_view.py create mode 100644 courses/serializers.py create mode 100644 courses/update_db.py delete mode 100644 finance/migrations/0071_bill_flow.py 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..c29b245 100644 --- a/access/middleware.py +++ b/access/middleware.py @@ -1,11 +1,17 @@ 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 "/manag" == request.path[:6] + or'/analy' == request.path[:6]): + #or "/api/v" == request.path[:6]): if not request.user.is_authenticated(): return HttpResponseForbidden() @@ -14,3 +20,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 33568bc..6b82ed8 100755 --- a/access/models.py +++ b/access/models.py @@ -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: diff --git a/access/new_urls.py b/access/new_urls.py new file mode 100644 index 0000000..0c78478 --- /dev/null +++ b/access/new_urls.py @@ -0,0 +1,6 @@ +from django.conf.urls import url +from access import new_view as views + +urlpatterns = [ + url(r'teachers/$', views.TeacherListView.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..34cf62d --- /dev/null +++ b/access/new_view.py @@ -0,0 +1,16 @@ +from django.contrib.auth import get_user_model +from rest_framework.views import APIView +from rest_framework.renderers import JSONRenderer +from rest_framework.response import Response + + +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) \ 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/models.py b/courses/models.py index 72c1a6e..bb4163c 100755 --- a/courses/models.py +++ b/courses/models.py @@ -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'Базовый + Продвинутый') ) diff --git a/courses/new_urls.py b/courses/new_urls.py new file mode 100644 index 0000000..86ec6b2 --- /dev/null +++ b/courses/new_urls.py @@ -0,0 +1,10 @@ +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'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..fd92c5f --- /dev/null +++ b/courses/new_view.py @@ -0,0 +1,50 @@ +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 +from courses.serializers import CourseTreeSerializer, CourseDetailSerializer, CourseListSerializer, ThemeSerializer + + +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 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): + return Response([CourseListSerializer(i).data for i in Course.objects.all()], 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..33f4d7b --- /dev/null +++ b/courses/serializers.py @@ -0,0 +1,144 @@ +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): + + class Meta: + model = Lesson + fields = '__all__' + + +class HomeworkSerializer(serializers.ModelSerializer): + + class Meta: + model = Homework + fields = '__all__' + + +class ExamSerializer(serializers.ModelSerializer): + + class Meta: + model = Exam + fields = '__all__' + + +class ThemeSerializer(serializers.ModelSerializer): + + class Meta: + model = CourseTheme + fields = '__all__' + + +class CourseListSerializer(serializers.ModelSerializer): + + class Meta: + model = Course + fields = ['id', '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/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/lms/settings.py b/lms/settings.py index 930b62a..0e0b485 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' @@ -137,7 +138,7 @@ DATABASES = { 'NAME': os.environ.get('DB_NAME', 'codemy'), 'USER': os.environ.get('PG_ENV_POSTGRES_USER', 'team'), 'PASSWORD': os.environ.get('PG_ENV_POSTGRES_PASSWORD', 'nu5Xefise'), - 'HOST': os.environ.get('PG_PORT_5432_TCP_ADDR', '192.168.0.6'), + 'HOST': os.environ.get('PG_PORT_5432_TCP_ADDR', '127.0.0.1'), 'PORT': os.environ.get('PG_PORT_5432_TCP_PORT', '5432'), }, } diff --git a/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/requirements.txt b/requirements.txt index 08a729c..6e14adf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +djangorestframework Babel==2.3.4 Django==1.9.3 Jinja2==2.8