import csv import datetime from django.db import IntegrityError from django.shortcuts import redirect from dateutil.relativedelta import relativedelta from django.contrib import auth from django.utils import timezone from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError from django.core.mail import EmailMessage from django.http import HttpResponse, HttpResponseForbidden from rest_framework import permissions 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 django.db.models import Q, Count, F import jwt from django.conf import settings from django.db.models.functions import Cast from django.db.models.fields import DateField from access.serializers import UserProgressSearchSerializer from courses.models import Course, Lesson from progress.models import ProgressLesson, Progress from progress.serializers import ProgressAnalyticSerializer, ProgressLessonSerializer, \ SecureProgressSerializer from courses.api import CourseProgressApi, CourseParamsApi from progress.tasks import add_next_lesson class StudentWorkView(APIView): renderer_classes = (JSONRenderer,) @staticmethod def get(request, teacher_token): client_status = request.GET.get('status', 'in_progress') if request.user.is_authenticated() and request.user.groups.filter(name__in=['teachers', 'admin']).exists(): try: if client_status == 'done': date_from = datetime.datetime.now() - datetime.timedelta(days=7) progress_lessons = ProgressLesson.objects.filter( ~Q(progress__user__out_key=teacher_token), ~Q(comment_tokens__len=0), status='done', checker__out_key=teacher_token, finish_date__gte=date_from, ).order_by('finish_date') else: server_status = Q(status='fail') if client_status == 'not_done' else Q(status='wait') progress_lessons = ProgressLesson.objects.filter( ~Q(progress__user__out_key=teacher_token), server_status, checker__out_key=teacher_token, ).order_by('-last_update') return Response([ProgressLessonSerializer(i).data for i in progress_lessons], status=200) except ValidationError: return Response("Bad request", status=400) return Response(status=403) class CourseProgressDynamicView(APIView): renderer_classes = (JSONRenderer,) @staticmethod def get(request): course_token = request.GET.get('course_token', '') teacher_email = request.GET.get('teacher_email', '').lower() from_date = int(request.GET.get('from', '7')) to_date = int(request.GET.get('to', '0')) only_hw = request.GET.get('only_hw', 'yes') == 'yes' if request.user.is_authenticated() and request.user.is_staff: progresses = ProgressLesson.objects.all() waiters = ProgressLesson.objects.filter(status='wait') if only_hw: progresses = progresses.filter(checker=F('progress__teacher')) if not course_token is '': progresses = progresses.filter(progress__course_token=course_token) if not teacher_email is '': waiters = waiters.filter(checker__email=teacher_email) progresses = progresses.filter(checker__email=teacher_email) res = {} progresses_finish = progresses.filter(finish_date__gt=timezone.now() - relativedelta(days=from_date)) progresses_finish = progresses_finish.filter(finish_date__lt=timezone.now() - relativedelta(days=to_date)) progresses_start = progresses.filter(start_date__gt=timezone.now() - relativedelta(days=from_date)) progresses_start = progresses_start.filter(start_date__lt=timezone.now() - relativedelta(days=to_date)) progresses_finish = progresses_finish.annotate(name=Cast('finish_date', DateField())) \ .values('name') \ .annotate(value=Count('id')) progresses_start = progresses_start.annotate(name=Cast('start_date', DateField())) \ .values('name') \ .annotate(value=Count('id')) progresses_finish = progresses_finish.order_by('name') progresses_start = progresses_start.order_by('name') res['finish'] = progresses_finish res['start'] = progresses_start res['in_progress'] = waiters.count() return Response(res, status=200) 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 TeacherUpdateProgress(APIView): permission_classes = (permissions.IsAuthenticated, ) renderer_classes = (JSONRenderer,) @staticmethod def post(request): 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', None) comment = request.JSON.get('comment', None) if lesson_token is None or course_token is None: return Response('Не передан слаг курса или токен урока', status=400) if student_out_key is None: return Response('Не передан student_out_key', status=400) try: student = get_user_model().objects.get(out_key=student_out_key) p = Progress.objects.get( user=student, course_token=course_token, ) try: pv = ProgressLesson.objects.get( progress=p, lesson_token=lesson_token, ) if pv.status == ProgressLesson.STATUSES.wait: if action == "no": pv.status = ProgressLesson.STATUSES.fail msg = EmailMessage( 'Ваша работа отправлена на доработку', '''Преподаватель "%s" отклонил вашу работу''' % request.user.get_full_name(), 'robo@skillbox.ru', [student.email], ) msg.send() elif action == "yes": pv.status = ProgressLesson.STATUSES.done pv.finish_date = datetime.datetime.now() msg = EmailMessage( 'Ваша работа принята', '''Преподователь "%s" принял вашу работу''' % request.user.get_full_name(), 'robo@skillbox.ru', [student.email], ) msg.send() else: Response("Свойство action должно иметь значение либо done, либо fail", status=400) else: return Response("Ошибка прав доступа", status=403) if not comment is None: pv.comment_tokens.append(comment) except ProgressLesson.DoesNotExist: return Response('Урок не проходится этим пользователем', status=403) pv.save() res = {"current": ProgressLessonSerializer(pv).data} if pv.status == ProgressLesson.STATUSES.done: # TODO: Ассинхронная задача для celery res['next'] = ProgressLessonSerializer(add_next_lesson(p)).data return Response(res, status=200) except Progress.DoesNotExist: return Response('Не найден прогресс по заданным параметрам', status=404) class StudentUpdateProgress(APIView): permission_classes = (permissions.IsAuthenticated,) renderer_classes = (JSONRenderer,) @staticmethod def post(request): lesson_token = request.JSON.get('lesson_token', None) comment = request.JSON.get('comment', None) if lesson_token is None: return Response('Не передан токен урока', status=400) student = request.user try: pv = ProgressLesson.objects.get( progress__user=student, lesson_token=lesson_token, ) if pv.status == ProgressLesson.STATUSES.done: return Response(SecureProgressSerializer(pv.progress).data, status=200) if not pv.status == ProgressLesson.STATUSES.wait: if pv.checker == pv.progress.user: pv.status = ProgressLesson.STATUSES.done pv.finish_date = datetime.datetime.now() elif not comment is None and\ not pv.progress.progresslesson_set.filter(status=ProgressLesson.STATUSES.wait).exists(): pv.status = ProgressLesson.STATUSES.wait pv.comment_tokens.append(comment) msg = EmailMessage( 'Студент оставил комментарий', '''Студент "%s" оставил вам комментарий.''' % request.user.get_full_name(), 'robo@skillbox.ru', [pv.checker.email], ) msg.send() elif comment is None: return Response("Не преложен комментарий", status=400) else: return Response("В настоящее время, мы уже проверяем одно из ваших домашних заданий.
Как " "только оно будет успешно сдано - вы сможете продолжить.", status=403) elif not comment is None: pv.comment_tokens.append(comment) pv.save() return Response(SecureProgressSerializer(pv.progress).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 FindProgressView(APIView): renderer_classes = (JSONRenderer,) permission_classes = (IsAuthenticated,) @staticmethod def get(request): if not request.user.is_staff: return Response("Только сотрудники персонала могут запрашивать инфо по прогрессу", status=403) key = request.GET.get('key', None) count = int(request.GET.get('count', '10')) if key: res = get_user_model().objects.filter( Q(id__contains=key) | Q(email__contains=key.lower()) | Q(first_name__contains=key) | Q(last_name__contains=key) | Q(account__phone__contains=key) ) else: res = get_user_model().objects.all() res = res[:(count if len(res) > count else len(res))] return Response([UserProgressSearchSerializer(i).data for i in res], status=200) class FreezeProgressView(APIView): renderer_classes = (JSONRenderer,) permission_classes = (IsAuthenticated,) @staticmethod def post(request): if not request.user.is_staff: return Response("Только сотрудники персонала могут вносить изменение в прогресс", status=403) key = request.JSON.get('id', None) is_freeze = request.JSON.get('is_freeze', False) try: p = Progress.objects.get(id=key) p.is_freeze = is_freeze p.save() except Progress.DoesNotExist: return Response("не найден прогресс по заданному id", status=404) return Response(status=204) class ChangeTeacherView(APIView): renderer_classes = (JSONRenderer,) permission_classes = (IsAuthenticated,) @staticmethod def post(request): if not request.user.is_staff: return Response("Только сотрудники персонала могут вносить изменение в прогресс", status=403) key = request.JSON.get('id', None) teacher_email = request.JSON.get('teacher_email', False) try: p = Progress.objects.get(id=key) try: teacher = get_user_model().objects.get(email=teacher_email.lower()) except get_user_model().DoesNotExist: return Response("Нет пользователя c таким email", status=404) p.teacher = teacher p.progresslesson_set.exclude(status=ProgressLesson.STATUSES.done).update(checker=teacher) p.save() except Progress.DoesNotExist: return Response("не найден прогресс по заданному id", status=404) return Response(status=204) class SetProgress(APIView): renderer_classes = (JSONRenderer,) permission_classes = (IsAuthenticated,) @staticmethod def post(request): if request.user.is_staff: email = request.JSON.get('email', None) course_slug = request.JSON.get('course_slug', None) topic_sort = int(request.JSON.get('topic', 1)) lesson_sort = int(request.JSON.get('lesson', 1)) only_watch = request.JSON.get('only_watch', False) force = request.JSON.get('force', False) if course_slug is None: return Response('course_slug не передан', status=400) if email is None: return Response('email не передан', status=400) try: student = get_user_model().objects.get(email=email.lower()) except get_user_model().DoesNotExist: return Response("User doesn't exist", status=404) try: course = Course.objects.get(slug=course_slug) except get_user_model().DoesNotExist: return Response("Course doesn't exist", status=404) try: progress = Progress.objects.get(course_token=course.token, user=student) except Progress.DoesNotExist: if not force: return Response("Студент не проходит этот курс", status=403) teacher = get_user_model().objects.get(out_key=course.get_teacher()) progress = Progress.objects.create(course_token=course.token, user=student, teacher=teacher) progress.only_watch = only_watch progress.save() token_list = [] lesson_list = [] for topic_idx, topic in enumerate(course.topic_set.all()): topic_find = topic_idx == (topic_sort - 1) for lesson_idx, lesson in enumerate(topic.lesson_set.all()): token_list.append(lesson.token) lesson_list.append(lesson) if lesson_idx == (lesson_sort - 1) and topic_find: break if topic_find: break if progress.progresslesson_set.filter( ~Q(lesson_token__in=token_list)).exists() and not force: return Response("Пользователь, прошёл дальше по курсу", status=403) progress.progresslesson_set.filter(~Q(lesson_token__in=token_list)).delete() for lesson_idx, lesson_token in enumerate(token_list[:-1]): try: pl = ProgressLesson.objects.get(progress__user=student, lesson_token=lesson_token) if pl.finish_date is None: pl.finish_date = datetime.datetime.now() pl.status = 'done' pl.save() except ProgressLesson.DoesNotExist: ProgressLesson.objects.create( progress=progress, lesson_token=lesson_token, checker=progress.teacher if lesson_list[lesson_idx].is_hm else student, status="done", finish_date=datetime.datetime.now() ) try: pl = ProgressLesson.objects.get(progress=progress, lesson_token=token_list[-1:][0]) if pl.status == "done": pl.status = "start" pl.finish_date = None pl.save() except ProgressLesson.DoesNotExist: ProgressLesson.objects.create( progress=progress, lesson_token=token_list[-1:][0], checker=progress.teacher if lesson_list[-1:][0].is_hm else student ) return Response(status=204) else: return Response("Эта функция доступна только сотрудникам персонала", status=403) def get_teachers_pay(request): if not request.user.is_authenticated and (request.user.groups.filter(name="support") or request.user.is_superuser): return HttpResponseForbidden() date_from = request.GET.get('from', None) date_to = request.GET.get('to', None) email = request.GET.get('email', None) file_name = "teacher_pay_%s" % email file_name = file_name + "__from_%s" % date_from if date_from else file_name file_name = file_name + "__to_%s" % date_to if date_to else file_name progress_lessons = ProgressLesson.objects.filter( ~Q(comment_tokens__len=0), checker__email=email, status='done', ) progress_lessons = progress_lessons.filter(finish_date__lt=date_to) if date_to else progress_lessons progress_lessons = progress_lessons.filter(finish_date__gte=date_from) if date_from else progress_lessons response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="%s.csv"' % file_name writer = csv.writer(response) writer.writerow(['Почта студента', 'Имя студента', 'Тема', 'Курс', 'Дата', 'Время',]) for i in progress_lessons.order_by('-finish_date'): course_api = CourseParamsApi(i.progress.course_token) writer.writerow([ i.progress.user.email, i.progress.user.get_full_name(), #TODO очередной костыль Lesson.objects.get(token=i.lesson_token).topic.title, course_api.get_slug_and_title()['title'], i.finish_date.date(), i.finish_date.time(), ]) return response class ProgressToken(APIView): renderer_classes = (JSONRenderer,) @staticmethod def get(request): token = request.GET.get('token', None) if token is None: return Response("bad request", 400) payload = jwt.decode(token, settings.COURSE_PROGRESS_SECRET_KEY, algorithms=['HS256']) try: user = get_user_model().objects.get(email=payload['email'].lower()) except get_user_model().DoesNotExist: user = get_user_model().objects.create_student(email=payload['email'].lower()) user.is_active = True user.save() try: course = Course.objects.get(token=payload['course_token']) except get_user_model().DoesNotExist: return Response("Course doesn't exist", status=404) teacher = get_user_model().objects.get(out_key=course.get_teacher()) try: p = Progress.objects.create( user=user, course_token=payload['course_token'], teacher=teacher, exp_date=timezone.now() + relativedelta(days=int(payload['period'])), ) ProgressLesson.objects.create( progress=p, lesson_token=course.get_first_lesson().token, checker=p.user, ) except IntegrityError: pass auth.login(request, user) return redirect('/')