You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

529 lines
22 KiB

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
import jwt
from django.conf import settings
from access.serializers import UserProgressSearchSerializer
from courses.models import Course, Lesson
from progress.models import ProgressLesson, Progress
from progress.serializers import ProgressAnalyticSerializer, ProgressLessonSerializer, ProgressSerializer, \
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', None)
from_date = int(request.GET.get('from', '7'))
to_date = int(request.GET.get('to', '0'))
if request.user.is_authenticated() and request.user.is_staff:
progresses = ProgressLesson.objects.filter(status='done')
if not course_token is None:
try:
progresses = progresses.filter(progress__course_token=course_token)
except ProgressLesson.DoesNotExist:
return Response('incorrect course token', status=404)
progresses = progresses.filter(finish_date__gt=timezone.now() - relativedelta(days=from_date))
progresses = progresses.filter(finish_date__lt=timezone.now() - relativedelta(days=to_date))
progresses = progresses.order_by('finish_date')
res = {}
progresses = progresses.extra({'finish_date_day': "date(finish_date)"})\
.values('progress__course_token', 'finish_date_day')\
.annotate(total=Count('progress'))
#TODO Доделать
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("В настоящее время, мы уже проверяем одно из ваших домашних заданий. <br> Как "
"только оно будет успешно сдано - вы сможете продолжить.", 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.filter(status=ProgressLesson.STATUSES.wait).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('/')