Tree view refactor See merge request !54remotes/origin/pm_task_31703
commit
0a2cd35c15
23 changed files with 448 additions and 71 deletions
@ -1,27 +1,11 @@ |
|||||||
#**SkillBox LMS** |
#**SkillBox LMS** |
||||||
|
|
||||||
Подробные шаги для проверки работоспособности CI |
Нужно выполнить миграции и запустить скрипт update_db.py без параметров в модуле courses (скрипт на всякий случай пока не удалять) |
||||||
|
|
||||||
1) Проверяем доступ к репозиторию кода https://gitlab.com/skillbox/go.skillbox.ru |
Фактически в структуре данных изменилось только то, что появилась новая табличка NormalMap. Скрипт собирает все treeview курсов и кладёт в эту табличку |
||||||
2) Клонируем проект |
|
||||||
3) Вносим изменения в код |
|
||||||
4) Комитим изменения в новый branch (ОБЯЗАТЕЛЬНО в отдельный Branch) |
|
||||||
4.1) Пушим изменения |
|
||||||
5) Ждем выполнения создания тестового сайта https://gitlab.com/skillbox/go.skillbox.ru/pipelines |
|
||||||
6) Сайт будет доступен по адресу <branch_name>.lms.test.spicycms.com |
|
||||||
|
|
||||||
**Правила для разработчика** |
Появилась новая зависимость django_rest_framework. |
||||||
|
|
||||||
* Разрабока ведется в отдельном брэнче |
Изменения никак не трогают старый функционал. |
||||||
* После комита изменений можно проверить свою работу по следующему адресу: .lms.test.spicycms.com |
|
||||||
* После проверки тестового сайта по имени брэнча, брэнч помечается к мерж реквесту |
|
||||||
* Ответсвенный инженер производит код ревью и запускает/отклоняет мерж |
|
||||||
* Если сайт запустился на демо сайте, код в ручом режиме можно обновить на продакшен сервере |
|
||||||
|
|
||||||
**Замечания** |
Имеет смысл пробежаться по middl |
||||||
|
|
||||||
* Мерж реквест лучше производить в ветку develop(чтобы исключить случайного обновления кода на демо сайте) |
|
||||||
* Внимание! Разработчик обязан проверить работоспособность тестового сайта по имени созданного брэнча .lms.test.spicycms.com |
|
||||||
* Если необходимы тестовые данные разработчик их подготавливает дополнительно |
|
||||||
* Если нет возможности добавить тестовые данные в авто режиме, разработчик добавляет инструкцию в README файл по запуску необходимых команд |
|
||||||
(Под тестовыми данными подразумеваются фикстуры) |
|
||||||
|
|||||||
@ -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()), |
||||||
|
] |
||||||
@ -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) |
||||||
@ -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__' |
||||||
@ -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')), |
||||||
|
] |
||||||
@ -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')), |
||||||
|
], |
||||||
|
), |
||||||
|
] |
||||||
@ -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='Уровень'), |
||||||
|
), |
||||||
|
] |
||||||
@ -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()), |
||||||
|
] |
||||||
@ -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) |
||||||
@ -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'] |
||||||
@ -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() |
||||||
@ -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='Поток'), |
|
||||||
), |
|
||||||
] |
|
||||||
Loading…
Reference in new issue