diff --git a/access/admin.py b/access/admin.py index 278435d..e99a407 100755 --- a/access/admin.py +++ b/access/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from access.models.other import Invite, Account, ResetPassword, UserActivity -from access.models.progress import ProgressLesson, Progress +from progress.models import ProgressLesson, Progress from access.models.user import User admin.site.register(User) diff --git a/access/migrations/0001_initial.py b/access/migrations/0001_initial.py index a80247f..ed55134 100644 --- a/access/migrations/0001_initial.py +++ b/access/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2018-01-29 16:48 +# Generated by Django 1.11.6 on 2018-01-31 18:55 from __future__ import unicode_literals import access.models.user @@ -75,37 +75,6 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Приглошения в систему', }, ), - migrations.CreateModel( - name='Progress', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('hidden_lessons', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(editable=False, unique=True, verbose_name='Токен урока'), default=[], size=None, verbose_name='Список скрытых уроков')), - ('course_token', models.UUIDField(editable=False, verbose_name='Токен курса')), - ('active_lesson', models.UUIDField(blank=True, null=True, verbose_name='Токен активного урока')), - ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teacher_progress', to=settings.AUTH_USER_MODEL, verbose_name='Преподователь по умолчанию')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Студент')), - ], - options={ - 'verbose_name': 'Прогресс пользователя', - 'verbose_name_plural': 'Прогресс пользователя', - }, - ), - migrations.CreateModel( - name='ProgressLesson', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('lesson_token', models.UUIDField(editable=False, verbose_name='Токен урока')), - ('date', models.DateTimeField(blank=True, null=True, verbose_name='Дата зачтения задания')), - ('status', models.CharField(choices=[('done', 'done'), ('wait', 'wait'), ('fail', 'fail')], default='wait', max_length=20)), - ('comment_tokens', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(editable=False, verbose_name='Токен комента'), default=[], size=None)), - ('progress', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.Progress')), - ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Преподователь')), - ], - options={ - 'verbose_name': 'Прохождение уроков', - 'verbose_name_plural': 'Прохождение урока', - }, - ), migrations.CreateModel( name='UserActivity', fields=[ @@ -136,12 +105,4 @@ class Migration(migrations.Migration): name='owner', field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Кому приглошение'), ), - migrations.AlterUniqueTogether( - name='progresslesson', - unique_together=set([('progress', 'lesson_token')]), - ), - migrations.AlterUniqueTogether( - name='progress', - unique_together=set([('user', 'course_token')]), - ), ] diff --git a/access/models/__init__.py b/access/models/__init__.py index 666ece4..73200f3 100755 --- a/access/models/__init__.py +++ b/access/models/__init__.py @@ -1,7 +1,7 @@ # encoding=utf-8 from .other import Account, Invite -from access.models.progress import Progress +from progress.models import Progress from .user import User diff --git a/access/serializers.py b/access/serializers.py index 54d8690..425ffd5 100644 --- a/access/serializers.py +++ b/access/serializers.py @@ -2,52 +2,8 @@ from django.contrib.auth import get_user_model from rest_framework import serializers from access.models.other import Account -from access.models.progress import ProgressLesson -from access.models import Progress from achievements.serialers import DiplomaSerializer, AchievementsSerializer -from courses.api import CourseProgressApi - - -class ProgressLessonSerializer(serializers.ModelSerializer): - teacher = serializers.SerializerMethodField() - - class Meta: - model = ProgressLesson - exclude = ('id', 'progress') - - @staticmethod - def get_teacher(self): - return self.teacher.get_full_name() - - -class ProgressAnalyticSerializer(serializers.ModelSerializer): - name = serializers.SerializerMethodField() - email = serializers.SerializerMethodField() - - class Meta: - model = Progress - fields = ('name', 'email',) - - @staticmethod - def get_name(self): - return self.user.get_full_name() - - @staticmethod - def get_email(self): - return self.user.email - - - -class ProgressSerializer(serializers.ModelSerializer): - lessons = serializers.SerializerMethodField() - - class Meta: - model = Progress - fields = ('lessons', 'course_token') - - @staticmethod - def get_lessons(self): - return [ProgressLessonSerializer(i).data for i in self.progresslesson_set.all()] +from progress.serializers import ProgressSerializer class AccountSerializer(serializers.ModelSerializer): diff --git a/access/urls.py b/access/urls.py index 59946d8..c4cdb3d 100644 --- a/access/urls.py +++ b/access/urls.py @@ -1,4 +1,6 @@ from django.conf.urls import url + +import progress.views from access import views urlpatterns = [ @@ -6,15 +8,15 @@ urlpatterns = [ url(r'detail/$', views.DetailUserView.as_view()), url(r'detail/([0-9]{1,99})/$', views.DetailUserView.as_view()), url(r'info/(?P[0-9A-Fa-f-]+)/$', views.MinUserView.as_view()), - url(r'guard/(?P[0-9]{1,99})/(?P.+)/$', views.UserGuardView.as_view()), + url(r'guard/(?P[0-9]{1,99})/(?P.+)/$', progress.views.UserGuardView.as_view()), url(r'find/$', views.FindUserView.as_view()), url(r'registration/$', views.RegistrationView.as_view()), url(r'change_password/$', views.ChangePasswordView.as_view()), url(r'login/$', views.LoginView.as_view()), url(r'logout/$', views.LogoutView.as_view()), url(r'reset/$', views.ResetPasswordView.as_view()), - url(r'progress/$', views.UpdateProgress.as_view()), - url(r'progress_detail/upload/(?P[0-9A-Fa-f-]+)/$', views.UploadCourseProgressUserView.as_view()), - url(r'progress_detail/(?P[0-9A-Fa-f-]+)/$', views.CourseProgressUserView.as_view()), - url(r'progress_dynamic/(?P[0-9A-Fa-f-]+)/$', views.CourseProgressDynamicView.as_view()), + url(r'progress/$', progress.views.UpdateProgress.as_view()), + url(r'progress_detail/upload/(?P[0-9A-Fa-f-]+)/$', progress.views.UploadCourseProgressUserView.as_view()), + url(r'progress_detail/(?P[0-9A-Fa-f-]+)/$', progress.views.CourseProgressUserView.as_view()), + url(r'progress_dynamic/(?P[0-9A-Fa-f-]+)/$', progress.views.CourseProgressDynamicView.as_view()), ] \ No newline at end of file diff --git a/access/views.py b/access/views.py index af4c4be..171c260 100644 --- a/access/views.py +++ b/access/views.py @@ -1,27 +1,18 @@ -import csv import datetime import random import string from django.contrib import auth from django.contrib.auth import get_user_model -from django.core.exceptions import ValidationError from django.core.mail import send_mail from django.db.models import Q -from django.http import HttpResponse from django.shortcuts import redirect -from rest_framework.permissions import IsAuthenticated from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.views import APIView from access.models.other import Invite, ResetPassword -from access.models.progress import ProgressLesson -from access.models import Progress -from access.serializers import UserSelfSerializer, UserSearchSerializer, ProgressLessonSerializer, \ - ProgressAnalyticSerializer -from access.tasks import add_next_lesson -from courses.api import CourseProgressApi +from access.serializers import UserSelfSerializer, UserSearchSerializer class TeacherListView(APIView): @@ -237,175 +228,6 @@ class LogoutView(APIView): return Response(status=204) -class UpdateProgress(APIView): - renderer_classes = (JSONRenderer,) - - @staticmethod - def post(request): - """ - На вход обязательно передаётся параметр id (id узла). - """ - lesson_token = request.JSON.get('lesson_token', None) - course_token = request.JSON.get('course_token', None) - student_out_key = request.JSON.get('student_out_key', None) - action = request.JSON.get('action', 'wait') - comment = request.JSON.get('comment', None) - - if lesson_token is None or course_token is None: - return Response('Не передан слаг курса или токен урока', status=400) - try: - is_student = student_out_key is None - student = request.user if is_student else get_user_model().objects.get(out_key=student_out_key) - - if is_student: - p = Progress.objects.get(user=student, course_token=course_token) - else: - p = Progress.objects.get( - user=student, - teacher=request.user, - course_token=course_token, - ) - - if p.active_lesson == lesson_token: - return Response("Ошибка доступа", status=403) - - try: - pv = ProgressLesson.objects.get( - progress=p, - lesson_token=lesson_token, - ) - - if is_student and pv.status == ProgressLesson.STATUSES.fail and action == "wait": - pv.status = ProgressLesson.STATUSES.wait - - elif not is_student and pv.status == ProgressLesson.STATUSES.wait: - pv.status = action - - else: - return Response("Ошибка прав доступа", status=403) - - pv.comment_tokens.append(comment) - - except ProgressLesson.DoesNotExist: - pv = ProgressLesson.objects.create( - date=datetime.datetime.now(), - teacher=p.teacher, - progress=p, - lesson_token=lesson_token, - status=ProgressLesson.STATUSES.done - ) - - pv.save() - - if pv.status == ProgressLesson.STATUSES.done: - #TODO: Ассинхроннаязадача для celery - add_next_lesson(p) - - return Response(ProgressLessonSerializer(pv).data, status=200) - - except Progress.DoesNotExist: - return Response('Не найден прогресс по заданным параметрам', status=404) - - -class UserGuardView(APIView): - renderer_classes = (JSONRenderer,) - permission_classes = (IsAuthenticated,) - - @staticmethod - def get(request, pk, page): - try: - user = get_user_model().objects.get(out_key=pk) - except get_user_model().DoesNotExist: - return Response("User doesn't exist", status=404) - - is_i = request.user == user - res_403 = Response('Permission denied', status=403) - res_204 = Response(status=204) - - if is_i and not request.user.groups.filter(name='teachers').exists() and page == 'homeworks': - return res_403 - - if is_i and not \ - request.user.groups.filter(name__in=['students', 'managers', 'lead_managers']).exists() \ - and page == 'payment': - return res_403 - - if is_i: - return res_204 - - if page == 'profile' and (request.user.is_superuser or request.user.is_staff): - return res_204 - - return res_403 - - -class CourseProgressDynamicView(APIView): - renderer_classes = (JSONRenderer,) - - @staticmethod - def get(request, token): - if request.user.is_authenticated() and request.user.is_staff: - try: - progresses = Progress.objects.filter(course_token=token) - res = {} - for i in progresses: - key = i.progresslesson_set.filter(status="done").count() - res[key] = 1 if not key in res.keys() else res[key] + 1 - return Response(res, status=200) - except ValidationError: - return Response("Bad request", status=400) - - return Response(status=403) - - -class CourseProgressUserView(APIView): - renderer_classes = (JSONRenderer,) - - @staticmethod - def get(request, token): - if request.user.is_authenticated() and request.user.is_staff: - try: - res = [] - sorted_token_list = CourseProgressApi.get_topic_lesson(token) - for p in Progress.objects.filter(course_token=token): - progress = ProgressAnalyticSerializer(p).data - progress['progress_course'] = p.progress_status(sorted_token_list) - res.append(progress) - return Response(res, status=200) - except ValidationError: - return Response("Bad request", status=400) - - return Response(status=403) - - -class UploadCourseProgressUserView(APIView): - renderer_classes = (JSONRenderer,) - - @staticmethod - def get(request, token): - if request.user.is_authenticated() and request.user.is_staff: - try: - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="%s.csv"' % token - sorted_token_list = CourseProgressApi.get_topic_lesson(token) - writer = csv.writer(response) - writer.writerow(['Имя', 'Почта', 'Последняя тема', 'Последний урок']) - - for p in Progress.objects.filter(course_token=token): - progress = ProgressAnalyticSerializer(p).data - progress['progress_course'] = p.progress_status(sorted_token_list) - writer.writerow([ - progress['name'], - progress['email'], - progress['progress_course'] and progress['progress_course'][0], - progress['progress_course'] and progress['progress_course'][1], - ]) - return response - except ValidationError: - return Response("Bad request", status=400) - return Response(status=403) - - class MinUserView(APIView): renderer_classes = (JSONRenderer,) diff --git a/achievements/migrations/0001_initial.py b/achievements/migrations/0001_initial.py index 6036241..a84484f 100644 --- a/achievements/migrations/0001_initial.py +++ b/achievements/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2018-01-29 16:48 +# Generated by Django 1.11.6 on 2018-01-31 18:55 from __future__ import unicode_literals from django.conf import settings diff --git a/courses/migrations/0001_initial.py b/courses/migrations/0001_initial.py index 07fcae5..8ead8fa 100644 --- a/courses/migrations/0001_initial.py +++ b/courses/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2018-01-29 16:48 +# Generated by Django 1.11.6 on 2018-01-31 18:55 from __future__ import unicode_literals import django.contrib.postgres.fields diff --git a/csv/load_bills.py b/csv/load_bills.py index 01ffb2c..75fa3eb 100644 --- a/csv/load_bills.py +++ b/csv/load_bills.py @@ -15,7 +15,7 @@ django.setup() from yandex_money.models import Payment from finance.models import Bill, Invoice -from access.models import Progress +from progress.models import Progress from courses.models import Course if __name__ == '__main__': diff --git a/csv/load_comments.py b/csv/load_comments.py index 150e2a6..a96afdb 100644 --- a/csv/load_comments.py +++ b/csv/load_comments.py @@ -15,7 +15,7 @@ django.setup() from storage.models import Comment, File from courses.models import Lesson -from access.models.progress import ProgressLesson, Progress +from progress.models import ProgressLesson, Progress if __name__ == '__main__': csv.field_size_limit(500 * 1024 * 1024) diff --git a/csv/load_perm.py b/csv/load_perm.py index 08db52d..37780db 100644 --- a/csv/load_perm.py +++ b/csv/load_perm.py @@ -15,8 +15,7 @@ django.setup() from courses.api import CourseProgressApi from django.contrib.auth.models import Group from django.contrib.auth import get_user_model -from access.models.progress import ProgressLesson -from access.models import Progress +from progress.models import ProgressLesson, Progress from courses.models import Lesson, Course @@ -55,7 +54,7 @@ if __name__ == '__main__': ) if pivot['date'] and not pivot['date'] == 'None': - pv.date = datetime.strptime(pivot['date'].split('.')[0], '%Y-%m-%d %H:%M:%S') + pv.start_date = datetime.strptime(pivot['date'].split('.')[0], '%Y-%m-%d %H:%M:%S') pv.status = ProgressLesson.STATUSES.done pv.save() @@ -68,4 +67,5 @@ if __name__ == '__main__': last_progress_lesson = p.progresslesson_set.filter(status=ProgressLesson.STATUSES.done).last() - is_next = True if last_progress_lesson is None else last_progress_lesson.lesson_token == lesson_token + is_next = True if last_progress_lesson is None else \ + last_progress_lesson.lesson_token == lesson_token diff --git a/finance/migrations/0001_initial.py b/finance/migrations/0001_initial.py index 7c9b6ff..207e228 100644 --- a/finance/migrations/0001_initial.py +++ b/finance/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2018-01-29 16:48 +# Generated by Django 1.11.6 on 2018-01-31 18:55 from __future__ import unicode_literals from django.conf import settings diff --git a/library/migrations/0001_initial.py b/library/migrations/0001_initial.py index 7b1c6d4..79ad5d6 100644 --- a/library/migrations/0001_initial.py +++ b/library/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2018-01-29 16:48 +# Generated by Django 1.11.6 on 2018-01-31 11:24 from __future__ import unicode_literals import datetime diff --git a/lms/settings.py b/lms/settings.py index 52935f1..e588ab5 100644 --- a/lms/settings.py +++ b/lms/settings.py @@ -107,6 +107,7 @@ libs = ( apps = ( 'access', + 'progress', 'courses', 'storage', 'finance', diff --git a/progress/__init__.py b/progress/__init__.py new file mode 100644 index 0000000..f1740f4 --- /dev/null +++ b/progress/__init__.py @@ -0,0 +1 @@ +default_app_config = "progress.apps.ProgressAppConfig" \ No newline at end of file diff --git a/progress/apps.py b/progress/apps.py new file mode 100644 index 0000000..815e0e3 --- /dev/null +++ b/progress/apps.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from django.apps import AppConfig + + +class ProgressAppConfig(AppConfig): + name = "progress" + verbose_name = "Прогрес" \ No newline at end of file diff --git a/progress/migrations/0001_initial.py b/progress/migrations/0001_initial.py new file mode 100644 index 0000000..dfc51bb --- /dev/null +++ b/progress/migrations/0001_initial.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2018-01-31 18:55 +from __future__ import unicode_literals + +from django.conf import settings +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Progress', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('hidden_lessons', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(editable=False, unique=True, verbose_name='Токен урока'), default=[], size=None, verbose_name='Список скрытых уроков')), + ('course_token', models.UUIDField(editable=False, verbose_name='Токен курса')), + ('active_lesson', models.UUIDField(blank=True, null=True, verbose_name='Токен активного урока')), + ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teacher_progress', to=settings.AUTH_USER_MODEL, verbose_name='Преподователь по умолчанию')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Студент')), + ], + options={ + 'verbose_name': 'Прогресс пользователя', + 'verbose_name_plural': 'Прогресс пользователя', + }, + ), + migrations.CreateModel( + name='ProgressLesson', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('lesson_token', models.UUIDField(editable=False, verbose_name='Токен урока')), + ('finish_date', models.DateTimeField(blank=True, null=True, verbose_name='Дата зачтения задания')), + ('start_date', models.DateTimeField(auto_now_add=True, verbose_name='Дата начала прохождения задания')), + ('status', models.CharField(choices=[('done', 'done'), ('wait', 'wait'), ('fail', 'fail')], default='wait', max_length=20)), + ('comment_tokens', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(editable=False, verbose_name='Токен комента'), default=[], size=None)), + ('checker', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Преподователь')), + ('progress', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='progress.Progress')), + ], + options={ + 'verbose_name': 'Прохождение уроков', + 'verbose_name_plural': 'Прохождение урока', + }, + ), + migrations.AlterUniqueTogether( + name='progresslesson', + unique_together=set([('progress', 'lesson_token')]), + ), + migrations.AlterUniqueTogether( + name='progress', + unique_together=set([('user', 'course_token')]), + ), + ] diff --git a/progress/migrations/__init__.py b/progress/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/access/models/progress.py b/progress/models.py similarity index 64% rename from access/models/progress.py rename to progress/models.py index abd9fae..5bedf06 100644 --- a/access/models/progress.py +++ b/progress/models.py @@ -2,7 +2,6 @@ from django.conf import settings from django.contrib.postgres.fields import ArrayField from django.db import models from model_utils import Choices -from courses.api import CourseProgressApi class Progress(models.Model): @@ -50,8 +49,9 @@ class Progress(models.Model): class ProgressLesson(models.Model): progress = models.ForeignKey(to=Progress) lesson_token = models.UUIDField(verbose_name="Токен урока", editable=False) - teacher = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name="Преподователь",) - date = models.DateTimeField(verbose_name='Дата зачтения задания', blank=True, null=True) + checker = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name="Преподователь", ) + finish_date = models.DateTimeField(verbose_name='Дата зачтения задания', blank=True, null=True) + start_date = models.DateTimeField(verbose_name='Дата начала прохождения задания', auto_now_add=True) STATUSES = Choices('done', 'wait', 'fail') status = models.CharField(choices=STATUSES, default=STATUSES.wait, max_length=20) comment_tokens = ArrayField(models.UUIDField(verbose_name="Токен комента", editable=False), default=[]) @@ -63,27 +63,3 @@ class ProgressLesson(models.Model): verbose_name = 'Прохождение уроков' verbose_name_plural = 'Прохождение урока' unique_together = ('progress', 'lesson_token') - - -# class UserLessonAnswer(models.Model): -# progress_lesson = models.ForeignKey(to=ProgressLesson) -# date = models.DateTimeField(verbose_name='Дата сдачи', auto_now_add=True) -# reviewer = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name="Проверяющий",) -# -# class Meta: -# verbose_name = 'Блок ответов пользователя' -# verbose_name_plural = 'Блоки ответов пользователя' -# -# -# class AnswerItem(models.Model): -# STATUSES = Choices('done', 'wait', 'fail') -# -# lesson_answer = models.ForeignKey(to=UserLessonAnswer) -# question = models.TextField(verbose_name='Вопрос') # TODO подумать над хранением токена вопроса -# value = models.TextField(verbose_name='Ответ') -# comment = models.TextField(verbose_name='Комент', blank=True, null=True) -# status = models.CharField(choices=STATUSES, default=STATUSES.wait, max_length=20) -# -# class Meta: -# verbose_name = 'Ответ пользователя' -# verbose_name_plural = 'Ответы пользователя' diff --git a/progress/serializers.py b/progress/serializers.py new file mode 100644 index 0000000..09d4abd --- /dev/null +++ b/progress/serializers.py @@ -0,0 +1,44 @@ +from rest_framework import serializers + +from progress.models import Progress, ProgressLesson + + +class ProgressSerializer(serializers.ModelSerializer): + lessons = serializers.SerializerMethodField() + + class Meta: + model = Progress + fields = ('lessons', 'course_token') + + @staticmethod + def get_lessons(self): + return [ProgressLessonSerializer(i).data for i in self.progresslesson_set.all()] + + +class ProgressAnalyticSerializer(serializers.ModelSerializer): + name = serializers.SerializerMethodField() + email = serializers.SerializerMethodField() + + class Meta: + model = Progress + fields = ('name', 'email',) + + @staticmethod + def get_name(self): + return self.user.get_full_name() + + @staticmethod + def get_email(self): + return self.user.email + + +class ProgressLessonSerializer(serializers.ModelSerializer): + teacher = serializers.SerializerMethodField() + + class Meta: + model = ProgressLesson + exclude = ('id', 'progress') + + @staticmethod + def get_teacher(self): + return self.teacher.get_full_name() \ No newline at end of file diff --git a/access/tasks.py b/progress/tasks.py similarity index 100% rename from access/tasks.py rename to progress/tasks.py diff --git a/progress/urls.py b/progress/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/progress/views.py b/progress/views.py new file mode 100644 index 0000000..ec624dc --- /dev/null +++ b/progress/views.py @@ -0,0 +1,184 @@ +import csv +import datetime + +from django.contrib.auth import get_user_model +from django.core.exceptions import ValidationError +from django.http import HttpResponse +from rest_framework.permissions import IsAuthenticated +from rest_framework.renderers import JSONRenderer +from rest_framework.response import Response +from rest_framework.views import APIView + +from progress.models import ProgressLesson, Progress +from progress.serializers import ProgressAnalyticSerializer, ProgressLessonSerializer +from courses.api import CourseProgressApi +from progress.tasks import add_next_lesson + + +class CourseProgressDynamicView(APIView): + renderer_classes = (JSONRenderer,) + + @staticmethod + def get(request, token): + if request.user.is_authenticated() and request.user.is_staff: + try: + progresses = Progress.objects.filter(course_token=token) + res = {} + for i in progresses: + key = i.progresslesson_set.filter(status="done").count() + res[key] = 1 if not key in res.keys() else res[key] + 1 + return Response(res, status=200) + except ValidationError: + return Response("Bad request", status=400) + + return Response(status=403) + + +class CourseProgressUserView(APIView): + renderer_classes = (JSONRenderer,) + + @staticmethod + def get(request, token): + if request.user.is_authenticated() and request.user.is_staff: + try: + res = [] + sorted_token_list = CourseProgressApi.get_topic_lesson(token) + for p in Progress.objects.filter(course_token=token): + progress = ProgressAnalyticSerializer(p).data + progress['progress_course'] = p.progress_status(sorted_token_list) + res.append(progress) + return Response(res, status=200) + except ValidationError: + return Response("Bad request", status=400) + + return Response(status=403) + + +class UpdateProgress(APIView): + renderer_classes = (JSONRenderer,) + + @staticmethod + def post(request): + """ + На вход обязательно передаётся параметр id (id узла). + """ + lesson_token = request.JSON.get('lesson_token', None) + course_token = request.JSON.get('course_token', None) + student_out_key = request.JSON.get('student_out_key', None) + action = request.JSON.get('action', 'wait') + comment = request.JSON.get('comment', None) + + if lesson_token is None or course_token is None: + return Response('Не передан слаг курса или токен урока', status=400) + try: + is_student = student_out_key is None + student = request.user if is_student else get_user_model().objects.get(out_key=student_out_key) + + if is_student: + p = Progress.objects.get(user=student, course_token=course_token) + else: + p = Progress.objects.get( + user=student, + teacher=request.user, + course_token=course_token, + ) + + if p.active_lesson == lesson_token: + return Response("Ошибка доступа", status=403) + + try: + pv = ProgressLesson.objects.get( + progress=p, + lesson_token=p.active_lesson, + ) + + if is_student and pv.status == ProgressLesson.STATUSES.fail and action == "wait": + pv.status = ProgressLesson.STATUSES.wait + + elif not is_student and pv.status == ProgressLesson.STATUSES.wait: + pv.status = action + + else: + return Response("Ошибка прав доступа", status=403) + + pv.comment_tokens.append(comment) + + except ProgressLesson.DoesNotExist: + pv = ProgressLesson.objects.create( + date=datetime.datetime.now(), + teacher=p.teacher, + progress=p, + lesson_token=p.active_lesson, + status=ProgressLesson.STATUSES.done + ) + + pv.save() + + if pv.status == ProgressLesson.STATUSES.done: + #TODO: Ассинхроннаязадача для celery + add_next_lesson(p) + + return Response(ProgressLessonSerializer(pv).data, status=200) + + except Progress.DoesNotExist: + return Response('Не найден прогресс по заданным параметрам', status=404) + + +class UploadCourseProgressUserView(APIView): + renderer_classes = (JSONRenderer,) + + @staticmethod + def get(request, token): + if request.user.is_authenticated() and request.user.is_staff: + try: + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="%s.csv"' % token + sorted_token_list = CourseProgressApi.get_topic_lesson(token) + writer = csv.writer(response) + writer.writerow(['Имя', 'Почта', 'Последняя тема', 'Последний урок']) + + for p in Progress.objects.filter(course_token=token): + progress = ProgressAnalyticSerializer(p).data + progress['progress_course'] = p.progress_status(sorted_token_list) + writer.writerow([ + progress['name'], + progress['email'], + progress['progress_course'] and progress['progress_course'][0], + progress['progress_course'] and progress['progress_course'][1], + ]) + return response + except ValidationError: + return Response("Bad request", status=400) + return Response(status=403) + + +class UserGuardView(APIView): + renderer_classes = (JSONRenderer,) + permission_classes = (IsAuthenticated,) + + @staticmethod + def get(request, pk, page): + try: + user = get_user_model().objects.get(out_key=pk) + except get_user_model().DoesNotExist: + return Response("User doesn't exist", status=404) + + is_i = request.user == user + res_403 = Response('Permission denied', status=403) + res_204 = Response(status=204) + + if is_i and not request.user.groups.filter(name='teachers').exists() and page == 'homeworks': + return res_403 + + if is_i and not \ + request.user.groups.filter(name__in=['students', 'managers', 'lead_managers']).exists() \ + and page == 'payment': + return res_403 + + if is_i: + return res_204 + + if page == 'profile' and (request.user.is_superuser or request.user.is_staff): + return res_204 + + return res_403 \ No newline at end of file diff --git a/storage/migrations/0001_initial.py b/storage/migrations/0001_initial.py index 4e36cc4..bf5edd1 100644 --- a/storage/migrations/0001_initial.py +++ b/storage/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2018-01-29 16:48 +# Generated by Django 1.11.6 on 2018-01-31 18:55 from __future__ import unicode_literals from django.db import migrations, models