Merge branch 'dev' into 'master'

Dev

See merge request !284
master
Andrey 8 years ago
commit 5fa5bc11d4
  1. 4
      .gitignore
  2. 9
      Envoy.blade.php
  3. 23
      access/serializers.py
  4. 1
      access/urls.py
  5. 5
      access/views.py
  6. 5
      config_app/settings/prod.env
  7. 5
      config_app/settings/prod.env.skeleton
  8. 23
      courses/migrations/0006_auto_20180323_1743.py
  9. 8
      courses/models.py
  10. 14
      courses/serializers.py
  11. 4
      courses/urls.py
  12. 165
      courses/views.py
  13. 22
      finance/migrations/0004_bill_date.py
  14. 16
      finance/models.py
  15. 2
      finance/serializers.py
  16. 11
      finance/signals.py
  17. 1
      finance/urls.py
  18. 209
      finance/views.py
  19. 16
      lms/settings.py
  20. 20
      progress/migrations/0009_progress_is_freeze.py
  21. 1
      progress/models.py
  22. 17
      progress/serializers.py
  23. 3
      progress/urls.py
  24. 84
      progress/views.py
  25. 6
      templates/mail/sales/back_set_bill.html
  26. 6
      templates/mail/sales/back_set_bill.txt

4
.gitignore vendored

@ -36,5 +36,5 @@ coverage.xml
# Celery
celerybeat-schedule
/config_app/settings/dev.env
/config_app/settings/test.env
/config_app/settings/local.env

@ -29,9 +29,12 @@ echo '{{ $new_release_dir }}'
@task('create_symlinks', ['on' => 'localhost'])
echo '>> Создание симлинков'
@if ($branch)
ln -nfs {{ $app_dir }}/configs/master/prod.env {{ $new_release_dir }}/config_app/settings/prod.env
ln -nfs {{ $app_dir }}/configs/dev/dev.env {{ $new_release_dir }}/config_app/settings/dev.env
@if ($branch == 'master')
ln -nfs {{ $app_dir }}/configs/master/prod.env {{ $new_release_dir }}/config_app/settings/local.env
ln -nfs {{ $app_dir }}/media/master {{ $new_release_dir }}/media
@endif
@if ($branch == 'dev')
ln -nfs {{ $app_dir }}/configs/dev/dev.env {{ $new_release_dir }}/config_app/settings/local.env
ln -nfs {{ $app_dir }}/media/master {{ $new_release_dir }}/media
@endif
@endtask

@ -3,7 +3,7 @@ from rest_framework import serializers
from access.models.other import Account
from achievements.serialers import DiplomaSerializer, AchievementsSerializer
from progress.serializers import SecureProgressSerializer
from progress.serializers import SecureProgressSerializer, SupportProgressSerializer
class AccountSerializer(serializers.ModelSerializer):
@ -48,7 +48,7 @@ class UserSelfSerializer(serializers.ModelSerializer):
@staticmethod
def get_progresses(self):
return [SecureProgressSerializer(i).data for i in self.progress_set.all()]
return [SecureProgressSerializer(i).data for i in self.progress_set.filter(is_freeze=False)]
class UserProfileSerializer(serializers.ModelSerializer):
@ -101,3 +101,22 @@ class UserSearchSerializer(serializers.ModelSerializer):
@staticmethod
def get_last_request(self):
return self.useractivity.last_request
class UserProgressSearchSerializer(serializers.ModelSerializer):
phone = serializers.SerializerMethodField()
progresses = serializers.SerializerMethodField()
class Meta:
model = get_user_model()
fields = ('out_key', 'email', 'first_name',
'last_name', 'phone', 'progresses')
@staticmethod
def get_phone(self):
return None if self.account.phone is None else self.account.phone.raw_input
@staticmethod
def get_progresses(self):
return [SupportProgressSerializer(i).data for i in self.progress_set.all()]

@ -10,7 +10,6 @@ urlpatterns = [
url(r'detail/(?P<out_key>[0-9A-Fa-f-]+)/$', views.DetailUserView.as_view()),
url(r'detail/$', views.DetailUserView.as_view()),
url(r'info/(?P<out_key>[0-9A-Fa-f-]+)/$', views.MinUserView.as_view()),
url(r'guard/(?P<pk>[0-9]{1,99})/(?P<page>.+)/$', 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()),

@ -28,7 +28,10 @@ class TeacherListView(APIView):
status_code = 200
def get(self, request):
return Response([i.email for i in get_user_model().objects.filter(groups__name='teachers')], self.status_code)
return Response([{
'email': i.email,
'token': i.out_key,
} for i in get_user_model().objects.filter(groups__name='teachers')], self.status_code)
class ResetPasswordView(APIView):

@ -1,5 +0,0 @@
DEBUG=False
SECRET_KEY='!eiquy7_+2#vn3z%zfp51$m-=tmvtcv*cj*@x$!v(_9btq0w=$'
DATABASE_URL='psql://team:nu5Xefise@127.0.0.1:5432/new_lms'
EMAIL_URL='smtp+tls://robo@skillbox.ru:nu5Xefise@smtp.gmail.com:587'
CACHE_URL=rediscache://127.0.0.1:6379/1?client_class=django_redis.client.DefaultClient

@ -0,0 +1,5 @@
DEBUG=False
SECRET_KEY='....'
DATABASE_URL='psql://<name>:<password>@127.0.0.1:5432/<db_name>'
EMAIL_URL='smtp+tls://<name>:<password>@smtp.gmail.com:587'
CACHE_URL=rediscache://127.0.0.1:6379/<db>?client_class=django_redis.client.DefaultClient

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-23 17:43
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('courses', '0005_auto_20180222_1911'),
]
operations = [
migrations.RemoveField(
model_name='topic',
name='description',
),
migrations.RemoveField(
model_name='topic',
name='icon',
),
]

@ -58,8 +58,6 @@ class Lesson(models.Model):
class Topic(models.Model):
course = models.ForeignKey(to="Course", verbose_name='курс')
title = models.CharField(verbose_name='Название', max_length=255)
description = models.TextField(verbose_name='Описание', blank=True, null=True)
icon = models.ImageField(verbose_name='Иконка темы', null=True, blank=True)
sort = models.SmallIntegerField(verbose_name='Поле сортировки')
def __str__(self):
@ -75,12 +73,12 @@ class Topic(models.Model):
class CourseManager(models.Manager):
def update_or_create_course(self, image=None, big_image=None, statistic=None,
big_mobile_image=None, slug=None, teachers=None,
big_mobile_image=None, slug=None, teacher_tokens=None,
level=None, direction=None, **kwargs):
slug = slug if slug else slugify(unidecode.unidecode(kwargs['title']))
kwargs['teacher_tokens'] = teachers
kwargs['teacher_tokens'] = teacher_tokens
if image:
path = 'course/image%s.png' % slug
@ -101,7 +99,7 @@ class CourseManager(models.Manager):
kwargs['level'] = get_real_name(COURSE_LEVEL, level)
if direction:
kwargs['direction'] = get_real_name(COURSE_DIRECTION, direction)
kwargs['direction'] = get_real_name(COURSE_DIRECTION, direction[0])
try:
course = self.get(slug=slug)

@ -1,6 +1,8 @@
from rest_framework import serializers
from django.conf import settings
from courses.models import Course, Lesson, Topic
import os
class TopicSerializer(serializers.ModelSerializer):
@ -19,11 +21,12 @@ class MiniLessonSerializer(serializers.ModelSerializer):
class Meta:
model = Lesson
fields = ('title', 'free', 'token', 'is_hm')
fields = ('title', 'free', 'token', 'is_hm', 'sort')
class LessonSerializer(MiniLessonSerializer):
course_slug = serializers.SerializerMethodField()
materials = serializers.SerializerMethodField()
class Meta:
model = Lesson
@ -33,6 +36,15 @@ class LessonSerializer(MiniLessonSerializer):
def get_course_slug(self):
return self.topic.course.slug
@staticmethod
def get_materials(self):
try:
prefix = 'materials/%s/%s/%s' % (self.topic.course.token, self.topic.id, self.token)
name_list = os.listdir('%s/%s/' % (settings.MEDIA_ROOT, prefix))
return ["%s%s/%s" % (settings.MEDIA_URL, prefix, i) for i in name_list]
except FileNotFoundError:
return []
class TeacherLessonSerializer(MiniLessonSerializer):
topic_sort = serializers.SerializerMethodField()

@ -3,9 +3,13 @@ from django.conf.urls import url
from courses import views as views
urlpatterns = [
url(r'lesson/update/$', views.UpdateLessonView.as_view()),
url(r'lesson/delete/(?P<lesson_token>.+)/$', views.DeleteLessonView.as_view()),
url(r'lesson/teacher/(?P<token>.+)/$', views.LessonInfoView.as_view()),
url(r'lesson/(?P<token>.+)/$', views.LessonDetail.as_view()),
url(r'tree/(?P<slug>.+)/$', views.TreeView.as_view()),
url(r'detail/(?P<slug>.+)/$', views.CourseDetailView.as_view()),
url(r'topic/update/$', views.UpdateTopicView.as_view()),
url(r'topic/delete/(?P<topic_id>[0-9]{1,99})/$', views.DeleteTopicView.as_view()),
url(r'^$', views.CourseListView.as_view()),
]

@ -1,10 +1,9 @@
from jwt import DecodeError
from courses.models import Course, Lesson
from courses.models import Course, Lesson, Topic
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from django.contrib.auth import get_user_model
from courses.serializers import CourseDetailSerializer, CourseTreeSerializer, LessonSerializer, TeacherLessonSerializer
import jwt
@ -31,27 +30,6 @@ class CourseListView(APIView):
status_code = 200
def post(self, request):
"""
This API endpoint create/update course.
---
parameters:
- name: level
type: string
required: true
location: form
- name: direction
type: string
required: true
location: form
- name: statistic
type: string
required: true
location: form
...
"""
# TODO: Костыль
teachers_emails = request.JSON.get('teachers', [])
request.JSON['teachers'] = [get_user_model().objects.get(email=i).out_key for i in teachers_emails]
course = Course.objects.update_or_create_course(**request.JSON.dict())
return Response(CourseDetailSerializer(course).data, status=self.status_code)
@ -80,6 +58,141 @@ class CourseDetailView(APIView):
return Response(CourseDetailSerializer(Course.objects.get(slug=slug)).data, self.status_code)
class DeleteTopicView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def delete(request, topic_id):
if request.user and request.user.is_staff:
try:
t = Topic.objects.get(id=topic_id)
except Topic.DoesNotExist:
return Response("Темы не существует", status=404)
t.delete()
return Response(CourseTreeSerializer(t.course).data, status=200)
class DeleteLessonView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def delete(request, lesson_token):
if request.user and request.user.is_staff:
try:
l = Lesson.objects.get(token=lesson_token)
except Lesson.DoesNotExist:
return Response("Темы не существует", status=404)
l.delete()
return Response(CourseTreeSerializer(l.topic.course).data, status=200)
class UpdateLessonView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def post(request):
lesson_token = request.JSON.get('token', None)
sort = request.JSON.get('sort', None)
topic_id = request.JSON.get('topic', None)
title = request.JSON.get('title', None)
description = request.JSON.get('description', None)
video = request.JSON.get('video', None)
free = request.JSON.get('free', None)
is_hm = request.JSON.get('is_hm', None)
if topic_id is None:
return Response("topic не передан", status=400)
if sort is None:
return Response("sort не передан", status=400)
try:
topic = Topic.objects.get(id=topic_id)
except Topic.DoesNotExist:
return Response("Тема не найдена", status=404)
if lesson_token is None:
if title is None:
return Response("Название урока не переданно", status=400)
for lesson in reversed(topic.lesson_set.filter(sort__gte=sort)):
lesson.sort = lesson.sort + 1
lesson.save()
l = Lesson.objects.create(
title=title,
topic=topic,
sort=sort,
)
else:
try:
l = Lesson.objects.get(token=lesson_token)
except Lesson.DoesNotExist:
return Response("Урок не найден", status=404)
l.title = l.title if title is None else title
l.video = l.video if video is None else video
l.free = l.free if free is None else free
l.is_hm = l.is_hm if is_hm is None else is_hm
l.description = l.description if description is None else description
if not l.sort == sort:
for lesson in reversed(topic.lesson_set.filter(sort__gte=sort)):
lesson.sort = lesson.sort + 1
lesson.save()
l.sort = sort
l.save()
return Response(CourseTreeSerializer(topic.course).data, status=200)
class UpdateTopicView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def post(request):
topic_id = request.JSON.get('id', None)
sort = request.JSON.get('sort', None)
course_token = request.JSON.get('course_token', None)
title = request.JSON.get('title', None)
if course_token is None:
return Response("Не передан course_token", status=400)
if sort is None:
return Response("Не передан sort", status=400)
try:
course = Course.objects.get(token=course_token)
except Course.DoesNotExist:
return Response("Курс не найден", status=404)
try:
if topic_id:
t = Topic.objects.get(id=topic_id)
if not t.sort == sort:
for topic in reversed(course.topic_set.filter(sort__gte=sort)):
topic.sort = topic.sort + 1
topic.save()
t.sort = sort
t.title = t.title if title is None else title
t.save()
else:
raise Topic.DoesNotExist()
except Topic.DoesNotExist:
if title is None:
return Response("Не передан title", status=400)
for topic in reversed(course.topic_set.filter(sort__gte=sort)):
topic.sort = topic.sort + 1
topic.save()
Topic.objects.create(
course=course,
title=title,
sort=sort,
)
return Response(CourseTreeSerializer(course).data, status=200)
class LessonInfoView(APIView):
renderer_classes = (JSONRenderer,)
status_code = 200
@ -109,7 +222,7 @@ class LessonDetail(APIView):
l = LessonSerializer(lesson).data
try:
payload = None if jwt_token is None\
payload = None if jwt_token is None \
else jwt.decode(jwt_token, settings.COURSE_PROGRESS_SECRET_KEY, algorithms=['HS256'])
except DecodeError:
payload = None
@ -117,7 +230,7 @@ class LessonDetail(APIView):
course = lesson.topic.course
if payload is None:
if not lesson.free:
if not (lesson.free or request.user.is_authenticated and request.user.is_staff):
return Response("Bad token", status=400)
else:
@ -144,7 +257,7 @@ class LessonDetail(APIView):
if not new_lesson:
return Response("Permission denied", status=403)
#TODO Задача для селери
# TODO Задача для селери
add_lesson(request.user.out_key, course.token, lesson.token, course.get_teacher(), lesson.is_hm)
return Response(l, status=200)

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-23 17:43
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('finance', '0003_auto_20180315_1358'),
]
operations = [
migrations.AddField(
model_name='bill',
name='date',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Дата выставления'),
preserve_default=False,
),
]

@ -3,8 +3,6 @@ from django.conf import settings
from django.db import models
from yandex_money.models import Payment
from courses.models import Course, Lesson
class Bill(models.Model):
course_token = models.UUIDField(verbose_name="Токен курса", editable=False)
@ -13,6 +11,7 @@ class Bill(models.Model):
comment = models.TextField(verbose_name='Комментарий продавца', help_text='Будет показано пользователю',
blank=True, editable=False)
description = models.TextField(verbose_name='Внутренняя заметка', blank=True)
date = models.DateTimeField(verbose_name="Дата выставления", auto_now_add=True)
def __str__(self):
return '%s: %s' % (self.id, self.user)
@ -20,6 +19,12 @@ class Bill(models.Model):
def get_full_price(self):
return sum([i.price for i in self.invoice_set.all() if not i.price is None])
def check_validate(self, invoice_id):
return self.invoice_set.filter(is_open=True).exclude(id=invoice_id).count() == 1
def check_pay(self):
return self.invoice_set.filter(status="F").exists()
class Meta:
verbose_name = 'Счет'
verbose_name_plural = 'Счета'
@ -43,7 +48,7 @@ class Invoice(models.Model):
('C', 'Отклонен'),
)
status = models.CharField(verbose_name='Статус', max_length=1, default='W', choices=BILL_STATUSES)
price = models.IntegerField(verbose_name='Сумма', editable=False, null=True, blank=True)
price = models.IntegerField(verbose_name='Сумма', editable=False, null=True, blank=True) #Todo На самом деле тут не далжно быть значений null
real_price = models.FloatField(verbose_name='Полученная сумма', null=True, blank=True,
help_text='Сумма, минус комиссия', editable=False)
method = models.CharField(verbose_name='Способ оплаты', max_length=2, default='Y', choices=BILL_METHOD)
@ -55,6 +60,11 @@ class Invoice(models.Model):
is_open = models.BooleanField(default=True, verbose_name="Открывает ли платёж курс")
date = models.DateTimeField(auto_now_add=True)
def get_comment(self):
return '''Вам выставлен счёт, для оплаты перейдите по ссылке
%s/api/v1/finance/payment/%s/''' % (settings.DOMAIN, self.yandex_pay.id,) if \
self.comment == "" else self.comment
def __str__(self):
return '%s:%s %s' % (self.id, self.get_status_display(), self.bill.user)

@ -32,7 +32,7 @@ class InvoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Invoice
exclude = ('bill',)
fields = '__all__'
@staticmethod
def get_status(self):

@ -19,11 +19,9 @@ def invoice_signal(instance, **kwargs):
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],
instance.get_comment(),
to=[instance.yandex_pay.cps_email],
bcc=[instance.bill.opener.email],
reply_to=[instance.bill.opener.email],
)
msg.send()
@ -52,8 +50,7 @@ def invoice_signal(instance, **kwargs):
'''Вам открыт доступ к курсу "%s", вы можете перейти по ссылке и
ознакомиться с материалами %s/course/%s'''
% (course.title, settings.DOMAIN, course.slug),
'robo@skillbox.ru',
[instance.bill.user.email],
to=[instance.bill.user.email],
bcc=[instance.bill.opener.email],
reply_to=[instance.bill.opener.email],
)

@ -7,5 +7,6 @@ urlpatterns = [
url(r'bills/([0-9]{1,99})/$', views.BillDetailView.as_view()),
url(r'bills_find/$', views.FindBillView.as_view()),
url(r'yandex/fail/$', views.YandexFailView.as_view()),
url(r'invoice/([0-9]{1,99})/$', views.InvoiceDetailView.as_view()),
url(r'invoices/$', views.get_invoices),
]

@ -4,6 +4,7 @@ import logging
import requests
from django.contrib.auth import get_user_model
from django.core.mail import EmailMessage
from django.db import IntegrityError
from django.db.models import Q
from django.http import HttpResponse, HttpResponseForbidden
from django.shortcuts import redirect
@ -12,6 +13,8 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from yandex_money.models import Payment
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.template.loader import get_template
from courses.models import Course
from courses.api import CourseParamsApi
@ -40,84 +43,147 @@ class BillListView(APIView):
def post(self, request):
if request.user.is_authenticated and (request.user.groups.filter(name__in=['managers','lead_managers']).exists()
or request.user.is_superuser):
bill = request.JSON.get('bill')
children = request.JSON.get('children', [])
user = get_user_model().objects.get(email=request.JSON.get('user'))
opener = get_user_model().objects.get(email=request.JSON.get('opener'))
description = request.JSON.get('description', None)
comment = request.JSON.get('comment', None)
course_token = request.JSON.get('course_token', None)
if bill:
user = get_user_model().objects.get(email=bill['user'])
opener = get_user_model().objects.get(email=bill['opener'])
description = bill['description']
comment = bill['comment']
course_token = bill['course_token']
if course_token is None:
return Response("Идентификатор курса не передан", status=400)
try:
bill_obj = Bill.objects.get(user=user, course_token=course_token)
except Bill.DoesNotExist:
try:
bill_obj = Bill.objects.get(user=user, course_token=course_token)
except Bill.DoesNotExist:
bill_obj = Bill.objects.create(user=user, course_token=course_token)
except IntegrityError:
return Response("У пользователя уже есть счёт на этот курс", status=400)
bill_obj.opener = bill_obj.opener if opener is None else opener
bill_obj.description = bill_obj.description if description is None else description
bill_obj.comment = bill_obj.comment if comment is None else comment
bill_obj.save()
return Response(bill_obj.id, status=200)
return Response("Ошибка доступа, возможно вы разлогинились из другой вкладки браузера", status=403)
class InvoiceDetailView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def delete(request, invoice_id):
bill_obj.opener = opener
bill_obj.description = description
bill_obj.comment = comment
bill_obj.save()
for i in children:
status = get_real_name(elem=i['status'], array=Invoice.BILL_STATUSES)
try:
invoice_id = i['id']
except KeyError:
invoice_id = None
try:
if not invoice_id is None:
invoice = Invoice.objects.get(id=i['id'])
if invoice.status == "P" or invoice.status == status:
continue
else:
raise Invoice.DoesNotExist
except Invoice.DoesNotExist:
i['method'] = get_real_name(elem=i['method'], array=Invoice.BILL_METHOD)
i['status'] = status
i['bill'] = bill_obj
i['yandex_pay'] = None
invoice = Invoice.objects.create(**i)
if i['method'] == 'Y' and invoice.yandex_pay is None:
yandex_pay = Payment.objects.create(
order_amount=i['price'],
shop_amount=0,
customer_number=bill_obj.user.id,
user=bill_obj.user,
cps_email=bill_obj.user.email,
)
invoice.yandex_pay = yandex_pay
invoice.save()
msg = EmailMessage(
'Выставлен новый счёт.',
'''Менеджер %s выставил счёт пользователю %s на курс "%s".'''
% (
invoice.bill.opener.get_full_name(),
invoice.bill.user.email,
Course.objects.get(token=invoice.bill.course_token).title,
),
'robo@skillbox.ru',
[invoice.bill.opener.email],
bcc=['dmitry.dolya@skillbox.ru'],
)
msg.send()
res = {
"bill": BillSerializer(bill_obj).data,
"children": [InvoiceSerializer(i).data for i in bill_obj.invoice_set.all()],
try:
i = Invoice.objects.get(id=invoice_id)
if not i.status == "F" and not (i.bill.check_pay() and i.is_open):
i.delete()
except Invoice.DoesNotExist:
pass
return Response(status=204)
@staticmethod
def post(request, invoice_id):
if request.user.is_authenticated and (request.user.groups.filter(name__in=['managers','lead_managers']).exists()
or request.user.is_superuser):
invoice_id = int(invoice_id)
bill_id = request.JSON.get('bill', None)
is_open = request.JSON.get('is_open', None)
method = request.JSON.get('method', None)
status = request.JSON.get('status', None)
price = request.JSON.get('price', None)
comment = request.JSON.get('comment', None)
real_price = request.JSON.get('real_price', None)
if bill_id is None:
return Response("Не передан id счёта", status=400)
if is_open is None or method is None or status is None or price is None:
return Response("Не передан один из пораметров is_open, method, status, price", status=400)
try:
bill = Bill.objects.get(id=bill_id)
except Bill.DoesNotExist:
return Response('Не найден счёт с id=%s' % bill_id, status=404)
method = get_real_name(elem=method[0], array=Invoice.BILL_METHOD)
status = get_real_name(elem=status[0], array=Invoice.BILL_STATUSES)
if bill.check_validate(invoice_id) and is_open:
return Response("Уже есть платёж открывающий курс", status=400)
try:
invoice = Invoice.objects.get(id=invoice_id)
except Invoice.DoesNotExist:
if not invoice_id == 0:
return Response("Платёж не найден", status=404)
if bill.check_pay():
return Response(
"Нельзя добавить новый платёж, так как один из платежей по счёту уже оплачен", status=400)
invoice = Invoice.objects.create(
bill=bill,
method=method,
status=status,
is_open=is_open,
)
if invoice.status == "F":
return Response(InvoiceSerializer(invoice).data, status=200)
invoice.real_price = None
invoice.method = method
invoice.status = status
if invoice.status == "F":
invoice.real_price = invoice.real_price if real_price is None else real_price
if bill.check_pay() and (invoice.price < price):
return Response("""Нельзя менять стоимость по счёту в большую сторону,
когда один из платежей оплачен""", status=400)
invoice.price = price
invoice.is_open = is_open
invoice.comment = comment
if invoice.method == 'Y' and invoice.yandex_pay is None:
yandex_pay = Payment.objects.create(
order_amount=invoice.price,
shop_amount=0,
customer_number=bill.user.id,
user=bill.user,
cps_email=bill.user.email,
)
invoice.yandex_pay = yandex_pay
plaintext = get_template('mail/sales/back_set_bill.txt')
html = get_template('mail/sales/back_set_bill.txt')
context = {
'user_email': invoice.bill.user.email,
'opener_full_name': invoice.bill.opener.get_full_name(),
'course_title': Course.objects.get(token=invoice.bill.course_token).title,
'date': str(invoice.date),
'price': invoice.price,
}
return Response(res, status=200)
subject, to = 'Выставлен новый счёт', invoice.bill.opener.email
return Response("Bill not set", status=400)
text_content = plaintext.render(context)
html_content = html.render(context)
return Response("Course detail access only for manager users", status=403)
msg = EmailMultiAlternatives(subject, text_content, to=[to], bcc=['dmitry.dolya@skillbox.ru'])
msg.attach_alternative(html_content, "text/html")
msg.send()
invoice.save()
return Response(InvoiceSerializer(invoice).data, status=200)
return Response("Invoice detail access only for manager users", status=403)
class BillDetailView(APIView):
@ -158,7 +224,9 @@ class FindBillView(APIView):
if key:
res = Bill.objects.filter(
Q(opener__email__contains=key.lower()) | Q(user__email__contains=key.lower())
Q(opener__email__contains=key.lower())
| Q(user__email__contains=key.lower())
| Q(id__contains=key)
)
else:
@ -343,7 +411,6 @@ class YandexAvisoView(APIView):
'response': xml_res,
})
msg = EmailMessage(
'Успешная оплата.',
'''Пользователь "%s", перевёл %s рублей. Номер платежа в яндекс кассе %s'''

@ -8,22 +8,10 @@ import socket
root = environ.Path(__file__) - 2
env = environ.Env()
MOD = os.environ.get('MOD', 'Prod')
DEBUG = os.environ.get('DEBUG', 'False')
MASTER_PASSWORD = os.environ.get('MASTER_PASSWORD', '@J*1')
if MOD == 'Test':
environ.Env.read_env(str(root) + '/config_app/settings/test.env')
elif MOD == 'Dev':
environ.Env.read_env(str(root) + '/config_app/settings/dev.env')
environ.Env.read_env(str(root) + '/config_app/settings/local.env')
elif MOD == 'Prod':
environ.Env.read_env(str(root) + '/config_app/settings/prod.env')
else:
raise ImportError('no such environ ' + MOD)
MASTER_PASSWORD = os.environ.get('MASTER_PASSWORD', '@J*1')
EMAIL_CONFIG = env.email_url('EMAIL_URL',)
vars().update(EMAIL_CONFIG)

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-27 13:29
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('progress', '0008_auto_20180227_1803'),
]
operations = [
migrations.AddField(
model_name='progress',
name='is_freeze',
field=models.BooleanField(default=False, verbose_name='Прохождение было преостановленно'),
),
]

@ -12,6 +12,7 @@ class Progress(models.Model):
user = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Студент')
course_token = models.UUIDField(verbose_name="Токен курса", editable=False)
is_finish = models.BooleanField(verbose_name="Окончен ли курс", default=False)
is_freeze = models.BooleanField(verbose_name="Прохождение было преостановленно", default=False)
only_watch = models.BooleanField(verbose_name="Только просмотр", default=False)
def progress_status(self, sorted_token_list):

@ -17,6 +17,23 @@ class ProgressSerializer(serializers.ModelSerializer):
return [ProgressLessonSerializer(i).data for i in self.progresslesson_set.all()]
class SupportProgressSerializer(serializers.ModelSerializer):
teacher_email = serializers.SerializerMethodField()
mod = serializers.SerializerMethodField()
class Meta:
model = Progress
fields = ('id', 'course_token', 'mod', 'teacher_email', "is_freeze")
@staticmethod
def get_teacher_email(self):
return self.teacher.email
@staticmethod
def get_mod(self):
return "Без дз" if self.only_watch else "Стандарт"
class SecureProgressSerializer(serializers.ModelSerializer):
jwt_token = serializers.SerializerMethodField()

@ -5,6 +5,9 @@ from progress import views
urlpatterns = [
url(r'students/(?P<teacher_token>[0-9A-Fa-f-]+)/$', views.StudentWorkView.as_view()),
url(r'student/$', views.StudentUpdateProgress.as_view()),
url(r'find/$', views.FindProgressView.as_view()),
url(r'freeze/$', views.FreezeProgressView.as_view()),
url(r'change_teacher/$', views.ChangeTeacherView.as_view()),
url(r'teacher/$', views.TeacherUpdateProgress.as_view()),
url(r'set-progress/$', views.SetProgress.as_view()),
url(r'get_hw_pay/$', views.get_teachers_pay),

@ -12,6 +12,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from django.db.models import Q
from access.serializers import UserProgressSearchSerializer
from courses.models import Course
from progress.models import ProgressLesson, Progress
from progress.serializers import ProgressAnalyticSerializer, ProgressLessonSerializer, ProgressSerializer, \
@ -201,6 +202,13 @@ class StudentUpdateProgress(APIView):
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)
@ -248,36 +256,74 @@ class UploadCourseProgressUserView(APIView):
return Response(status=403)
class UserGuardView(APIView):
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 get(request, pk, page):
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:
user = get_user_model().objects.get(out_key=pk)
except get_user_model().DoesNotExist:
return Response("User doesn't exist", status=404)
p = Progress.objects.get(id=key)
p.is_freeze = is_freeze
p.save()
except Progress.DoesNotExist:
return Response("не найден прогресс по заданному id", status=404)
is_i = request.user == user
res_403 = Response('Permission denied', status=403)
res_204 = Response(status=204)
return 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
class ChangeTeacherView(APIView):
renderer_classes = (JSONRenderer,)
permission_classes = (IsAuthenticated,)
if is_i:
return res_204
@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)
if page == 'profile' and (request.user.is_superuser or request.user.is_staff):
return res_204
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 res_403
return Response(status=204)
class SetProgress(APIView):

@ -0,0 +1,6 @@
Выставлен новый счет <br>
Пользователь: {{ user_email }} <br>
Продавец: {{ opener_full_name }} <br>
Курс: {{ course_title }} <br>
Дата продажи: {{ date }} <br>
Сумма: {{ price }} руб. <br>

@ -0,0 +1,6 @@
Выставлен новый счет
Пользователь: {{ user_email }}
Продавец: {{ opener_full_name }}
Курс: {{ course_title }}
Дата продажи: {{ date }}
Сумма: {{ price }} руб.
Loading…
Cancel
Save