From bbbb8bb6f2f87c677ffa4c9d19a6fae267ec1156 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 5 Mar 2018 12:41:00 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=9D=D0=B5=20=D0=B2=20=D0=BA=D0=BE=D0=B5?= =?UTF-8?q?=D0=BC=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D0=B5=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D0=BC=D1=91=D1=80=D0=B6=D0=B8=D1=82=D1=8C!!!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- access/serializers.py | 5 ++--- access/urls.py | 6 +---- lms/settings.py | 2 ++ progress/serializers.py | 24 ++++++++++++++++++-- progress/views.py | 50 ++++++++++++++++++----------------------- 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/access/serializers.py b/access/serializers.py index a9d5c6f..5d29a96 100644 --- a/access/serializers.py +++ b/access/serializers.py @@ -1,10 +1,9 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from rest_framework.generics import get_object_or_404 from access.models.other import Account from achievements.serialers import DiplomaSerializer, AchievementsSerializer -from progress.serializers import ProgressSerializer +from progress.serializers import SecureProgressSerializer class AccountSerializer(serializers.ModelSerializer): @@ -49,7 +48,7 @@ class UserSelfSerializer(serializers.ModelSerializer): @staticmethod def get_progress(self): - return [ProgressSerializer(i).data for i in self.progress_set.all()] + return [SecureProgressSerializer(i).data for i in self.progress_set.all()] class UserProfileSerializer(serializers.ModelSerializer): diff --git a/access/urls.py b/access/urls.py index 579905d..f2961b6 100644 --- a/access/urls.py +++ b/access/urls.py @@ -18,10 +18,6 @@ urlpatterns = [ url(r'logout/$', views.LogoutView.as_view()), url(r'reset/$', views.ResetPasswordView.as_view()), url(r'progress_detail/upload/(?P[0-9A-Fa-f-]+)/$', progress.views.UploadCourseProgressUserView.as_view()), - url( - r'management/password/$', - views.ManagementPassword.as_view(), - name='management-password' - ) + url(r'management/password/$', views.ManagementPassword.as_view(), name='management-password') ] diff --git a/lms/settings.py b/lms/settings.py index 92db76a..8dfbb82 100644 --- a/lms/settings.py +++ b/lms/settings.py @@ -68,6 +68,8 @@ DATABASES = { 'default': env.db(), } +COURSE_PROGRESS_SECRET_KEY = "!gf?s3@4Hr5#J#&%Kfr@56s" + SESSION_ENGINE = 'redis_sessions.session' CELERY_EMAIL_CHUNK_SIZE = 1 diff --git a/progress/serializers.py b/progress/serializers.py index ea27df1..a7a9e65 100644 --- a/progress/serializers.py +++ b/progress/serializers.py @@ -1,6 +1,8 @@ from rest_framework import serializers from progress.models import Progress, ProgressLesson +import jwt +from django.conf import settings class ProgressSerializer(serializers.ModelSerializer): @@ -15,6 +17,24 @@ class ProgressSerializer(serializers.ModelSerializer): return [ProgressLessonSerializer(i).data for i in self.progresslesson_set.all()] +class SecureProgressSerializer(serializers.ModelSerializer): + jwt_token = serializers.SerializerMethodField() + + class Meta: + model = Progress + fields = ('jwt_token', ) + + @staticmethod + def get_jwt_token(self): + payload = { + 'lessons': [ProgressLessonSerializer(i).data for i in self.progresslesson_set.all()], + 'course_token': str(self.course_token), + 'only_watch': self.only_watch, + 'is_finish': self.is_finish, + } + return jwt.encode(payload, settings.COURSE_PROGRESS_SECRET_KEY, algorithm='HS256') + + class ProgressAnalyticSerializer(serializers.ModelSerializer): name = serializers.SerializerMethodField() email = serializers.SerializerMethodField() @@ -47,8 +67,8 @@ class ProgressLessonSerializer(serializers.ModelSerializer): @staticmethod def get_student(self): - return {'name': self.progress.user.get_full_name(), "out_key": self.progress.user.out_key} + return {'name': self.progress.user.get_full_name(), "out_key": str(self.progress.user.out_key)} @staticmethod def get_course_token(self): - return self.progress.course_token \ No newline at end of file + return str(self.progress.course_token) diff --git a/progress/views.py b/progress/views.py index 2b43719..ed3db3f 100644 --- a/progress/views.py +++ b/progress/views.py @@ -175,47 +175,41 @@ class StudentUpdateProgress(APIView): @staticmethod def post(request): lesson_token = request.JSON.get('lesson_token', None) - course_token = request.JSON.get('course_token', None) comment = request.JSON.get('comment', None) - if lesson_token is None or course_token is None: - return Response('Не передан слаг курса или токен урока', status=400) - try: - student = request.user - - p = Progress.objects.get(user=student, course_token=course_token) - - try: - pv = ProgressLesson.objects.get( - progress=p, - lesson_token=lesson_token, - ) + if lesson_token is None: + return Response('Не передан токен урока', status=400) - if not pv.status == ProgressLesson.STATUSES.wait: - if pv.checker == p.teacher and not comment is None: - pv.status = ProgressLesson.STATUSES.wait - pv.comment_tokens.append(comment) + student = request.user - elif pv.checker == p.user: - pv.status = ProgressLesson.STATUSES.done - pv.finish_date = datetime.datetime.now() + try: + pv = ProgressLesson.objects.get( + progress__user=student, + lesson_token=lesson_token, + ) - else: - raise ValueError("Этого никогда не должно происходить, но я уверен, что произойдёт") + if not pv.status == ProgressLesson.STATUSES.wait: + if pv.checker == pv.progress.teacher and not comment is None: + pv.status = ProgressLesson.STATUSES.wait + pv.comment_tokens.append(comment) - pv.save() + elif pv.checker == pv.progress.user: + pv.status = ProgressLesson.STATUSES.done + pv.finish_date = datetime.datetime.now() else: - return Response("Ошибка прав доступа", status=403) + raise ValueError("Этого никогда не должно происходить, но я уверен, что произойдёт") - except ProgressLesson.DoesNotExist: - return Response('Урок не проходится этим пользователем', status=403) + pv.save() + + else: + return Response("Ошибка прав доступа", status=403) if pv.status == ProgressLesson.STATUSES.done: # TODO: Ассинхроннаязадача для celery - add_next_lesson(p) + add_next_lesson(pv.progress) - return Response(ProgressSerializer(p).data, status=200) + return Response(ProgressSerializer(pv.progress).data, status=200) except Progress.DoesNotExist: return Response('Не найден прогресс по заданным параметрам', status=404) From f2f0f8cc44d1f14fc5ff464345b42f396a63df5e Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 12 Mar 2018 16:33:43 +0300 Subject: [PATCH 2/3] Yandex_update --- access/serializers.py | 6 ++-- courses/models.py | 21 +++++++---- courses/serializers.py | 5 +++ courses/tasks.py | 12 +++++++ courses/urls.py | 2 +- courses/views.py | 77 ++++++++++++++++++++++++++--------------- progress/serializers.py | 1 - 7 files changed, 85 insertions(+), 39 deletions(-) create mode 100644 courses/tasks.py diff --git a/access/serializers.py b/access/serializers.py index 5d29a96..2aea54b 100644 --- a/access/serializers.py +++ b/access/serializers.py @@ -21,13 +21,13 @@ class AccountSerializer(serializers.ModelSerializer): class UserSelfSerializer(serializers.ModelSerializer): account = serializers.SerializerMethodField() groups = serializers.SerializerMethodField() - progress = serializers.SerializerMethodField() + progresses = serializers.SerializerMethodField() diplomas = serializers.SerializerMethodField() achievements = serializers.SerializerMethodField() class Meta: model = get_user_model() - fields = ('out_key', 'email', 'first_name', 'last_name', 'progress', 'achievements', + fields = ('out_key', 'email', 'first_name', 'last_name', 'progresses', 'achievements', 'account', 'groups', 'is_staff', 'is_superuser', 'diplomas', 'is_active') @staticmethod @@ -47,7 +47,7 @@ class UserSelfSerializer(serializers.ModelSerializer): return [group.name for group in self.groups.all()] @staticmethod - def get_progress(self): + def get_progresses(self): return [SecureProgressSerializer(i).data for i in self.progress_set.all()] diff --git a/courses/models.py b/courses/models.py index 7785b4c..934a43b 100755 --- a/courses/models.py +++ b/courses/models.py @@ -154,19 +154,26 @@ class Course(models.Model): lesson_list += list(topic.lesson_set.all()) return lesson_list - def get_next(self, lesson: Lesson) -> Lesson: + def get_next(self, lesson: Lesson, f=None) -> Lesson: lessons = self.get_lesson_list() try: - return lessons[lessons.index(lesson)] + n = lessons[lessons.index(lesson)+1] + if f is None or f(lesson): + return n + else: + return self.get_next(lesson, f) except IndexError: pass - def get_previous(self, lesson: Lesson): + def get_previous(self, lesson: Lesson, f=None): lessons = self.get_lesson_list() - try: - return lessons[lessons.index(lesson) - 2] - except IndexError: - pass + idx = lessons.index(lesson) - 1 + if idx > -1: + prev = lessons[idx] + if f is None or f(lesson): + return prev + else: + return self.get_previous(prev, f) def get_first_lesson(self) -> Lesson: lessons = self.get_lesson_list() diff --git a/courses/serializers.py b/courses/serializers.py index 70e4ba6..359386b 100644 --- a/courses/serializers.py +++ b/courses/serializers.py @@ -23,11 +23,16 @@ class MiniLessonSerializer(serializers.ModelSerializer): class LessonSerializer(MiniLessonSerializer): + course_slug = serializers.SerializerMethodField() class Meta: model = Lesson exclude = ('id', 'topic', 'key') + @staticmethod + def get_course_slug(self): + return self.topic.course.slug + class TeacherLessonSerializer(MiniLessonSerializer): topic_sort = serializers.SerializerMethodField() diff --git a/courses/tasks.py b/courses/tasks.py new file mode 100644 index 0000000..9d00b7f --- /dev/null +++ b/courses/tasks.py @@ -0,0 +1,12 @@ +from progress.models import ProgressLesson, Progress + + +def add_lesson(user_out_key: str, course_token: str, lesson_token: str, teacher_key: str, is_hm: bool): + + p = Progress.objects.get(course_token=course_token, user__out_key=user_out_key) + + ProgressLesson.objects.get_or_create( + progress=p, + lesson_token=lesson_token, + checker=user_out_key if is_hm else teacher_key, + ) \ No newline at end of file diff --git a/courses/urls.py b/courses/urls.py index d30f49b..819f814 100644 --- a/courses/urls.py +++ b/courses/urls.py @@ -3,8 +3,8 @@ from django.conf.urls import url from courses import views as views urlpatterns = [ - url(r'vertex/(?P.+)/$', views.LessonDetail.as_view()), url(r'lesson/teacher/(?P.+)/$', views.LessonInfoView.as_view()), + url(r'lesson/(?P.+)/$', views.LessonDetail.as_view()), url(r'tree/(?P.+)/$', views.TreeView.as_view()), url(r'detail/(?P.+)/$', views.CourseDetailView.as_view()), url(r'^$', views.CourseListView.as_view()), diff --git a/courses/views.py b/courses/views.py index ea9536f..90450b6 100644 --- a/courses/views.py +++ b/courses/views.py @@ -1,3 +1,5 @@ +from jwt import DecodeError + from courses.models import Course, Lesson from rest_framework.renderers import JSONRenderer from rest_framework.response import Response @@ -5,7 +7,10 @@ from rest_framework.views import APIView from django.contrib.auth import get_user_model from courses.serializers import CourseDetailSerializer, CourseTreeSerializer, LessonSerializer, TeacherLessonSerializer -from progress.models import ProgressLesson +import jwt + +from courses.tasks import add_lesson +from lms import settings class TreeView(APIView): @@ -93,33 +98,51 @@ class LessonDetail(APIView): renderer_classes = (JSONRenderer,) @staticmethod - def get(request, token): + def post(request, token): + jwt_token = request.JSON.get('jwt_token', None) try: lesson = Lesson.objects.get(token=token) except Lesson.DoesNotExist: - return Response("Lesson doesn't exist", status=404) - - if not lesson.free and not ProgressLesson.objects.filter(lesson_token=lesson.token).exists(): - previous_lesson = lesson.topic.course.get_previous(lesson) - - if not previous_lesson is None or not ProgressLesson.objects.filter( - lesson_token=previous_lesson.token, status=ProgressLesson.STATUSES.done).exists(): - return Response("Lesson doesn't access", status=403) - - # TODO: Доделать систему прав на курс - - res = LessonSerializer(lesson).data - # progress = vertex.course.progress_set.filter(user=request.user) - # try: - # if progress.exists(): - # next_vertex = vertex.get_next(progress[0].get_template()) - # if next_vertex: - # res['next'] = MiniVertexSerializer(next_vertex).data - # res['is_in_progress'] = vertex in progress[0].get_objects_in_progress() - # else: - # res['next'] = MiniVertexSerializer(vertex.get_next(vertex.course.route)).data - # except Thread.DoesNotExist or Vertex.DoesNotExist: - # res['next'] = MiniVertexSerializer(vertex.get_next(vertex.course.route)).data - - return Response(res, status=200) + return Response("Урока не существует", status=404) + + l = LessonSerializer(lesson).data + + if not lesson.free: + if jwt_token is None: + return Response("Ошибка доступа", status=403) + + try: + payload = jwt.decode(jwt_token, settings.COURSE_PROGRESS_SECRET_KEY, algorithms=['HS256']) + except DecodeError: + return Response("Bad token", status=400) + + try: + course = Course.objects.get(token=payload['course_token']) + except Course.DoesNotExist: + return Response("Course not found", status=404) + + prev_lesson = course.get_previous(lesson, (lambda x: not x.is_hm) if payload['only_watch'] else None) + next_lesson = course.get_next(lesson, (lambda x: not x.is_hm) if payload['only_watch'] else None) + + if not prev_lesson is None: + l['prev_token'] = prev_lesson.token + + if not next_lesson is None: + l['next_token'] = next_lesson.token + + new_lesson = False + for payload_lesson in payload['lessons']: + if payload_lesson['lesson_token'] == str(lesson.token): + return Response(l, status=200) + new_lesson = True if prev_lesson is None else \ + ((payload_lesson['status'] == "done" or payload_lesson['status'] == "wait") + if prev_lesson.token == payload_lesson['lesson_token'] else False) + + if not new_lesson: + return Response("Permission denied", status=403) + + #TODO Задача для селери + add_lesson(request.user.out_key, course.token, lesson.token, course.get_teacher(), lesson.is_hm) + + return Response(l, status=200) diff --git a/progress/serializers.py b/progress/serializers.py index a7a9e65..c2570e9 100644 --- a/progress/serializers.py +++ b/progress/serializers.py @@ -30,7 +30,6 @@ class SecureProgressSerializer(serializers.ModelSerializer): 'lessons': [ProgressLessonSerializer(i).data for i in self.progresslesson_set.all()], 'course_token': str(self.course_token), 'only_watch': self.only_watch, - 'is_finish': self.is_finish, } return jwt.encode(payload, settings.COURSE_PROGRESS_SECRET_KEY, algorithm='HS256') From ef3e1324314727b0e541d972af8a27e2e20709c5 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 13 Mar 2018 18:24:04 +0300 Subject: [PATCH 3/3] Yandex_update --- courses/tasks.py | 3 ++- courses/views.py | 54 ++++++++++++++++++++++++----------------------- progress/views.py | 11 ++++------ 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/courses/tasks.py b/courses/tasks.py index 9d00b7f..9384958 100644 --- a/courses/tasks.py +++ b/courses/tasks.py @@ -1,4 +1,5 @@ from progress.models import ProgressLesson, Progress +from django.contrib.auth import get_user_model def add_lesson(user_out_key: str, course_token: str, lesson_token: str, teacher_key: str, is_hm: bool): @@ -8,5 +9,5 @@ def add_lesson(user_out_key: str, course_token: str, lesson_token: str, teacher_ ProgressLesson.objects.get_or_create( progress=p, lesson_token=lesson_token, - checker=user_out_key if is_hm else teacher_key, + checker=get_user_model().objects.get(out_key=(teacher_key if is_hm else user_out_key)), ) \ No newline at end of file diff --git a/courses/views.py b/courses/views.py index 90450b6..1a6138b 100644 --- a/courses/views.py +++ b/courses/views.py @@ -108,41 +108,43 @@ class LessonDetail(APIView): l = LessonSerializer(lesson).data - if not lesson.free: - if jwt_token is None: - return Response("Ошибка доступа", status=403) + try: + payload = None if jwt_token is None\ + else jwt.decode(jwt_token, settings.COURSE_PROGRESS_SECRET_KEY, algorithms=['HS256']) + except DecodeError: + payload = None + + course = lesson.topic.course - try: - payload = jwt.decode(jwt_token, settings.COURSE_PROGRESS_SECRET_KEY, algorithms=['HS256']) - except DecodeError: + if payload is None: + if not lesson.free: return Response("Bad token", status=400) - try: - course = Course.objects.get(token=payload['course_token']) - except Course.DoesNotExist: - return Response("Course not found", status=404) + else: + return Response(l, status=200) + + prev_lesson = course.get_previous(lesson, (lambda x: not x.is_hm) if payload['only_watch'] else None) + next_lesson = course.get_next(lesson, (lambda x: not x.is_hm) if payload['only_watch'] else None) - prev_lesson = course.get_previous(lesson, (lambda x: not x.is_hm) if payload['only_watch'] else None) - next_lesson = course.get_next(lesson, (lambda x: not x.is_hm) if payload['only_watch'] else None) + if not prev_lesson is None: + l['prev_token'] = prev_lesson.token - if not prev_lesson is None: - l['prev_token'] = prev_lesson.token + if not next_lesson is None: + l['next_token'] = next_lesson.token - if not next_lesson is None: - l['next_token'] = next_lesson.token + new_lesson = False + for payload_lesson in payload['lessons']: + if payload_lesson['lesson_token'] == str(lesson.token): + return Response(l, status=200) - new_lesson = False - for payload_lesson in payload['lessons']: - if payload_lesson['lesson_token'] == str(lesson.token): - return Response(l, status=200) + if not prev_lesson is None and str(prev_lesson.token) == payload_lesson['lesson_token']: new_lesson = True if prev_lesson is None else \ - ((payload_lesson['status'] == "done" or payload_lesson['status'] == "wait") - if prev_lesson.token == payload_lesson['lesson_token'] else False) + (payload_lesson['status'] == "done" or payload_lesson['status'] == "wait") - if not new_lesson: - return Response("Permission denied", status=403) + if not new_lesson: + return Response("Permission denied", status=403) - #TODO Задача для селери - add_lesson(request.user.out_key, course.token, lesson.token, course.get_teacher(), lesson.is_hm) + #TODO Задача для селери + add_lesson(request.user.out_key, course.token, lesson.token, course.get_teacher(), lesson.is_hm) return Response(l, status=200) diff --git a/progress/views.py b/progress/views.py index ed3db3f..e0ae37d 100644 --- a/progress/views.py +++ b/progress/views.py @@ -14,7 +14,8 @@ from django.db.models import Q from courses.models import Course from progress.models import ProgressLesson, Progress -from progress.serializers import ProgressAnalyticSerializer, ProgressLessonSerializer, ProgressSerializer +from progress.serializers import ProgressAnalyticSerializer, ProgressLessonSerializer, ProgressSerializer, \ + SecureProgressSerializer from courses.api import CourseProgressApi, CourseParamsApi from progress.tasks import add_next_lesson @@ -203,13 +204,9 @@ class StudentUpdateProgress(APIView): pv.save() else: - return Response("Ошибка прав доступа", status=403) + pass - if pv.status == ProgressLesson.STATUSES.done: - # TODO: Ассинхроннаязадача для celery - add_next_lesson(pv.progress) - - return Response(ProgressSerializer(pv.progress).data, status=200) + return Response(SecureProgressSerializer(pv.progress).data, status=200) except Progress.DoesNotExist: return Response('Не найден прогресс по заданным параметрам', status=404)