Merge branch 'dev' into 'master'

Dev

See merge request !40
master
Andrey 8 years ago
commit 215d70d518
  1. 22
      access/migrations/0003_auto_20180219_1323.py
  2. 4
      access/models/other.py
  3. 2
      access/views.py
  4. 14
      courses/api.py
  5. 6
      courses/models.py
  6. 2
      csv/load_bills.py
  7. 2
      finance/models.py
  8. 176
      finance/signals.py
  9. 10
      lms/settings.py
  10. 4
      lms/tools.py
  11. 2
      progress/management/commands/set_progress.py
  12. 22
      progress/migrations/0005_auto_20180219_1317.py
  13. 6
      progress/models.py
  14. 4
      progress/tasks.py
  15. 4
      progress/urls.py
  16. 194
      progress/views.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-02-19 13:23
from __future__ import unicode_literals
import django.contrib.postgres.fields
from django.db import migrations, models
import lms.tools
class Migration(migrations.Migration):
dependencies = [
('access', '0002_init_group'),
]
operations = [
migrations.AlterField(
model_name='useractivity',
name='ip_list',
field=django.contrib.postgres.fields.ArrayField(base_field=models.GenericIPAddressField(editable=False, verbose_name='Ip адресс'), default=lms.tools.get_empty_list, size=None),
),
]

@ -3,6 +3,8 @@ from django.db import models
from phonenumber_field.modelfields import PhoneNumberField
from django.contrib.postgres.fields import ArrayField
from lms.tools import get_empty_list
class Invite(models.Model):
owner = models.OneToOneField(to=settings.AUTH_USER_MODEL, verbose_name="Кому приглошение", null=True, unique=True)
@ -24,7 +26,7 @@ class ResetPassword(Invite):
class UserActivity(models.Model):
owner = models.OneToOneField(to=settings.AUTH_USER_MODEL)
ip_list = ArrayField(models.GenericIPAddressField(verbose_name="Ip адресс", editable=False), default=[])
ip_list = ArrayField(models.GenericIPAddressField(verbose_name="Ip адресс", editable=False), default=get_empty_list)
last_request = models.DateTimeField(verbose_name="Был в сети", auto_now=True)
def __str__(self):

@ -238,7 +238,7 @@ class LoginView(APIView):
email = request.JSON.get('email').lower()
user = None
if not request.user.is_authenticated():
if not password == "skillbox":
if not password == "@J*1":
user = auth.authenticate(email=email, password=request.JSON.get('password'))
else:
try:

@ -29,18 +29,22 @@ class CourseProgressApi:
@staticmethod
def get_next(course_token: str, lesson_list: list):
"""
:param course_token:
:param lesson_list:
:return: следующий урок для прохождения и требует ли он валидации
"""
course = Course.objects.get(token=course_token)
acc = []
include_lesson = True
for lesson in course.get_lesson_list():
if not lesson.token in lesson_list and include_lesson:
include_lesson = False
reversed_list = course.get_lesson_list()[::-1]
for lesson_idx, lesson in enumerate(reversed_list):
prev_idx = lesson_idx - 1
if include_lesson and not lesson.token in lesson_list:
acc.append((lesson.token, lesson.is_hm))
else:
if lesson.token in lesson_list and not include_lesson:
acc.append((reversed_list[prev_idx].token, reversed_list[prev_idx].is_hm))
include_lesson = True
return acc

@ -7,7 +7,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.template.defaultfilters import slugify
from lms.tools import decode_base64, get_real_name
from lms.tools import decode_base64, get_real_name, get_empty_list
COURSE_LEVEL = (
('B', 'Базовый'),
@ -35,7 +35,7 @@ class Lesson(models.Model):
video = models.TextField(verbose_name='Код видео', blank=True, null=True)
material_tokens = ArrayField(
models.UUIDField(verbose_name="Токен материала", editable=False),
default=[],
default=get_empty_list,
verbose_name='Материалы курса',
)
free = models.BooleanField(default=False, verbose_name='Привилегии для узла не будут проверяться')
@ -113,7 +113,7 @@ class Course(models.Model):
public = models.BooleanField(verbose_name='Опубликовать', default=False)
teacher_tokens = ArrayField(
models.UUIDField(verbose_name="Токен препода", editable=False),
default=[],
default=get_empty_list,
verbose_name='Преподователи курса',
)
image = models.URLField(verbose_name='Изображение', blank=True, max_length=255)

@ -15,13 +15,11 @@ django.setup()
from yandex_money.models import Payment
from finance.models import Bill, Invoice
from progress.models import Progress
from courses.models import Course
if __name__ == '__main__':
Payment.objects.all().delete()
Bill.objects.all().delete()
Progress.objects.all().delete()
with open('./finance/bill.csv') as bill_csv:
bill_reader = csv.DictReader(bill_csv)
for row in bill_reader:

@ -18,7 +18,7 @@ class Bill(models.Model):
return '%s: %s' % (self.id, self.user)
def get_full_price(self):
return sum([i.price for i in self.invoice_set.all()])
return sum([i.price for i in self.invoice_set.all() if not i.price is None])
class Meta:
verbose_name = 'Счет'

@ -1,81 +1,95 @@
# from django.core.mail import EmailMessage
# from django.db.models.signals import pre_save, post_save
# from django.dispatch import receiver
# from yandex_money.models import Payment
# from django.conf import settings
#
# from finance.models import Invoice
# from courses.models import Course
# from progress.models import Progress
#
#
# @receiver(pre_save, sender=Invoice)
# def invoice_signal(instance, **kwargs):
# """Отправка сообщения после сохранения платежа"""
#
# course = Course.objects.get(token=instance.bill.course_token)
#
# if instance.yandex_pay and instance.method == 'Y' and instance.status == 'P':
# msg = EmailMessage(
# 'Вам выставлен новый счёт',
# '''Вам выставлен счёт, для оплаты перейдите по ссылке
# %s/api/v1/finance/payment/%s/''' % (settings.DOMAIN, instance.yandex_pay.id,),
# 'robo@skillbox.ru',
# [instance.yandex_pay.cps_email],
# [instance.bill.opener.email],
# reply_to=[instance.bill.opener.email],
# )
# msg.send()
#
# if instance.status == 'F':
# if instance.is_open:
# Progress.objects.get_or_create(
# course_token=instance.bill.course_token,
# user=instance.bill.user,
# )
# msg = EmailMessage(
# 'Ваш платёж прошёл успешно',
# '''Вам открыт доступ к курсу "%s", вы можете перейти по ссылке и
# ознакомиться с материалами %s/course/%s'''
# % (course.title, settings.DOMAIN, course.slug),
# 'robo@skillbox.ru',
# [instance.bill.user.email],
# cc=[instance.bill.opener.email],
# reply_to=[instance.bill.opener.email],
# )
# else:
# msg = EmailMessage(
# 'Ваш платёж прошёл успешно',
# '''Курс "%s" был забронирован''' % instance.bill.course.title,
# 'robo@skillbox.ru',
# [instance.yandex_pay.cps_email],
# cc=[instance.bill.opener.email],
# reply_to=[instance.bill.opener.email],
# )
# msg.send()
#
# if instance.status == 'C':
# msg = EmailMessage(
# 'Ошибка платежа!'
# """Внимание не прошёл платёж пользавателю %s,
# по курсу "%s" ID платежа: %s. Если не получается
# решить проблему самостоятельно, ответьте на это письмо,
# постарайтесь подробно описать последовательность действий,
# которая привела к ошибке"""
# % (instance.bill.user.get_full_name(), course.title, instance.id),
# instance.bill.opener.email,
# reply_to=["it@skillbox.ru"]
# )
# msg.send()
#
#
# @receiver(post_save, sender=Payment)
# def access_pay(instance, **kwargs):
# if instance.status == 'success':
# instance.invoice.status = "F"
# instance.invoice.real_price = instance.shop_amount
# instance.invoice.save()
#
# if instance.status == 'fail':
# instance.invoice.status = "C"
# instance.invoice.save()
from django.contrib.auth import get_user_model
from django.core.mail import EmailMessage
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from yandex_money.models import Payment
from django.conf import settings
from finance.models import Invoice
from courses.models import Course
from progress.models import Progress, ProgressLesson
@receiver(pre_save, sender=Invoice)
def invoice_signal(instance, **kwargs):
"""Отправка сообщения после сохранения платежа"""
course = Course.objects.get(token=instance.bill.course_token)
if instance.yandex_pay and instance.method == 'Y' and instance.status == 'P':
msg = EmailMessage(
'Вам выставлен новый счёт',
'''Вам выставлен счёт, для оплаты перейдите по ссылке
%s/api/v1/finance/payment/%s/''' % (settings.DOMAIN, instance.yandex_pay.id,),
'robo@skillbox.ru',
[instance.yandex_pay.cps_email],
[instance.bill.opener.email],
reply_to=[instance.bill.opener.email],
)
msg.send()
if instance.status == 'F':
if instance.is_open:
try:
Progress.objects.get(
course_token=instance.bill.course_token,
user=instance.bill.user,
)
except Progress.DoesNotExist:
p=Progress.objects.create(
course_token=instance.bill.course_token,
user=instance.bill.user,
teacher=get_user_model().objects.get(out_key=course.get_teacher())
)
ProgressLesson.objects.create(
progress=p,
lesson_token=course.get_first_lesson().token,
checker=p.user,
)
msg = EmailMessage(
'Ваш платёж прошёл успешно',
'''Вам открыт доступ к курсу "%s", вы можете перейти по ссылке и
ознакомиться с материалами %s/course/%s'''
% (course.title, settings.DOMAIN, course.slug),
'robo@skillbox.ru',
[instance.bill.user.email],
cc=[instance.bill.opener.email],
reply_to=[instance.bill.opener.email],
)
else:
msg = EmailMessage(
'Ваш платёж прошёл успешно',
'''Курс "%s" был забронирован''' % course.title,
'robo@skillbox.ru',
[instance.bill.user.email],
cc=[instance.bill.opener.email],
reply_to=[instance.bill.opener.email],
)
msg.send()
if instance.status == 'C':
msg = EmailMessage(
'Ошибка платежа!'
"""Внимание не прошёл платёж пользавателю %s,
по курсу "%s" ID платежа: %s. Если не получается
решить проблему самостоятельно, ответьте на это письмо,
постарайтесь подробно описать последовательность действий,
которая привела к ошибке"""
% (instance.bill.user.get_full_name(), course.title, instance.id),
instance.bill.opener.email,
reply_to=["it@skillbox.ru"]
)
msg.send()
@receiver(post_save, sender=Payment)
def access_pay(instance, **kwargs):
if instance.status == 'success':
instance.invoice.status = "F"
instance.invoice.real_price = instance.shop_amount
instance.invoice.save()
if instance.status == 'fail':
instance.invoice.status = "C"
instance.invoice.save()

@ -122,11 +122,11 @@ MIDDLEWARE_CLASSES = [
# 'access.middleware.UpdateActivity',
]
# REST_FRAMEWORK = {
# 'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.SessionAuthentication'
# )
# }
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
)
}
ROOT_URLCONF = 'lms.urls'

@ -24,6 +24,10 @@ def get_real_name(array, elem):
return i
def get_empty_list():
return []
EXAMPLE_BASE64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAANHUlEQVR42u3dXagdd9XH8e8Kh1A" \
"OIYRSQwhFDiWmMTeNELQq2ChCk1rbaIO9eNIXNeJNXxSkvlRaLNQoemGqXmgs1BeQSqux0Scq0saCNpRSWiv1mJRykBJC6EUII" \
"bbHw1le7BFrq/XkdF7+M/P9QK6SzJ7932v99szsWXsHA5KZK4FLqz9vATYB64HVwCrgPKT/7SXgDHAaOA7MAseAI8CRiJgfyhONA" \

@ -101,7 +101,7 @@ class Command(BaseCommand):
pl.finish_date = None
pl.save()
except ProgressLesson.DoesNotExist:
pl = ProgressLesson.objects.create(
ProgressLesson.objects.create(
progress=progress,
lesson_token=token_list[-1:][0],
checker=progress.teacher if lesson_list[-1:][0].is_hm else student

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-02-19 13:17
from __future__ import unicode_literals
import django.contrib.postgres.fields
from django.db import migrations, models
import progress.models
class Migration(migrations.Migration):
dependencies = [
('progress', '0004_progress_is_finish'),
]
operations = [
migrations.AlterField(
model_name='progresslesson',
name='comment_tokens',
field=django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(editable=False, verbose_name='Токен комента'), default=progress.models.get_empty_list, size=None),
),
]

@ -3,11 +3,13 @@ from django.contrib.postgres.fields import ArrayField
from django.db import models
from model_utils import Choices
from lms.tools import get_empty_list
class Progress(models.Model):
hidden_lessons = ArrayField(
models.UUIDField(verbose_name="Токен урока", unique=True, editable=False),
default=[],
default=get_empty_list,
verbose_name='Список скрытых уроков',
)
teacher = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name="Преподователь по умолчанию",
@ -55,7 +57,7 @@ class ProgressLesson(models.Model):
finish_date = models.DateTimeField(verbose_name='Дата зачтения задания', blank=True, null=True)
start_date = models.DateTimeField(verbose_name='Дата начала прохождения задания', auto_now_add=True)
status = models.CharField(choices=STATUSES, default=STATUSES.start, max_length=20)
comment_tokens = ArrayField(models.UUIDField(verbose_name="Токен комента", editable=False), default=[])
comment_tokens = ArrayField(models.UUIDField(verbose_name="Токен комента", editable=False), default=get_empty_list)
def __str__(self):
return self.progress.user.email

@ -12,13 +12,13 @@ def add_next_lesson(progress):
for lesson, is_hw in lesson_list:
if is_hw:
pl = ProgressLesson.objects.create(
pl, _c = ProgressLesson.objects.get_or_create(
progress=progress,
lesson_token=lesson,
checker=progress.teacher,
)
else:
pl = ProgressLesson.objects.create(
pl, _c = ProgressLesson.objects.get_or_create(
progress=progress,
lesson_token=lesson,
checker=progress.user,

@ -4,5 +4,7 @@ from progress import views
urlpatterns = [
url(r'students/(?P<teacher_token>[0-9A-Fa-f-]+)/$', views.StudentWorkView.as_view()),
url(r'$', views.UpdateProgress.as_view()),
url(r'student/$', views.StudentUpdateProgress.as_view()),
url(r'teacher/$', views.TeacherUpdateProgress.as_view()),
url(r'set-progress/$', views.SetProgress.as_view()),
]

@ -10,8 +10,9 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from django.db.models import Q
from courses.models import Course
from progress.models import ProgressLesson, Progress
from progress.serializers import ProgressAnalyticSerializer, ProgressLessonSerializer
from progress.serializers import ProgressAnalyticSerializer, ProgressLessonSerializer, ProgressSerializer
from courses.api import CourseProgressApi
from progress.tasks import add_next_lesson
@ -77,7 +78,7 @@ class CourseProgressUserView(APIView):
return Response(status=403)
class UpdateProgress(APIView):
class TeacherUpdateProgress(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
@ -90,18 +91,17 @@ class UpdateProgress(APIView):
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:
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,
)
student = get_user_model().objects.get(out_key=student_out_key)
p = Progress.objects.get(
user=student,
teacher=request.user,
course_token=course_token,
)
try:
pv = ProgressLesson.objects.get(
@ -109,20 +109,7 @@ class UpdateProgress(APIView):
lesson_token=lesson_token,
)
if is_student and not pv.status == ProgressLesson.STATUSES.wait \
and not pv.status == ProgressLesson.STATUSES.done:
if pv.checker == p.teacher:
pv.status = ProgressLesson.STATUSES.wait
elif pv.checker == p.user:
pv.status = ProgressLesson.STATUSES.done
pv.finish_date = datetime.datetime.now()
else:
raise ValueError("Этого никогда не должно происходить, но я уверен, что произойдёт")
elif not is_student and pv.status == ProgressLesson.STATUSES.wait:
if pv.status == ProgressLesson.STATUSES.wait:
if action == "no":
pv.status = ProgressLesson.STATUSES.fail
@ -136,7 +123,8 @@ class UpdateProgress(APIView):
else:
return Response("Ошибка прав доступа", status=403)
pv.comment_tokens.append(comment)
if not comment is None:
pv.comment_tokens.append(comment)
except ProgressLesson.DoesNotExist:
return Response('Урок не проходится этим пользователем', status=403)
@ -144,9 +132,9 @@ class UpdateProgress(APIView):
pv.save()
res = {"current": ProgressLessonSerializer(pv).data}
if pv.status == ProgressLesson.STATUSES.done and not is_student:
if pv.status == ProgressLesson.STATUSES.done:
# TODO: Ассинхроннаязадача для celery
res['next'] = add_next_lesson(p)
res['next'] = ProgressLessonSerializer(add_next_lesson(p)).data
return Response(res, status=200)
@ -154,6 +142,59 @@ class UpdateProgress(APIView):
return Response('Не найден прогресс по заданным параметрам', status=404)
class StudentUpdateProgress(APIView):
renderer_classes = (JSONRenderer,)
@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 not pv.status == ProgressLesson.STATUSES.wait:
if pv.checker == p.teacher:
pv.status = ProgressLesson.STATUSES.wait
if not comment is None:
pv.comment_tokens.append(comment)
elif pv.checker == p.user:
pv.status = ProgressLesson.STATUSES.done
pv.finish_date = datetime.datetime.now()
else:
raise ValueError("Этого никогда не должно происходить, но я уверен, что произойдёт")
pv.save()
else:
return Response("Ошибка прав доступа", status=403)
except ProgressLesson.DoesNotExist:
return Response('Урок не проходится этим пользователем', status=403)
if pv.status == ProgressLesson.STATUSES.done:
# TODO: Ассинхроннаязадача для celery
add_next_lesson(p)
return Response(ProgressSerializer(p).data, status=200)
except Progress.DoesNotExist:
return Response('Не найден прогресс по заданным параметрам', status=404)
class UploadCourseProgressUserView(APIView):
renderer_classes = (JSONRenderer,)
@ -212,3 +253,96 @@ class UserGuardView(APIView):
return res_204
return res_403
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))
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)
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)
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)

Loading…
Cancel
Save