From d7e89aff1d15b26fb26c2f002e796ef1bdfbe292 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 20 Mar 2018 15:15:05 +0300 Subject: [PATCH 01/28] finance logging --- finance/models.py | 6 ++ finance/serializers.py | 2 +- finance/views.py | 146 +++++++++++++++++++++++++---------------- 3 files changed, 97 insertions(+), 57 deletions(-) diff --git a/finance/models.py b/finance/models.py index ae56954..a881d33 100755 --- a/finance/models.py +++ b/finance/models.py @@ -20,6 +20,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): + return self.invoice_set.filter(is_open=True).count() == 1 + + def check_pay(self): + return self.invoice_set.filter(status="F").exists() + class Meta: verbose_name = 'Счет' verbose_name_plural = 'Счета' diff --git a/finance/serializers.py b/finance/serializers.py index a166c1c..6930f5a 100644 --- a/finance/serializers.py +++ b/finance/serializers.py @@ -32,7 +32,7 @@ class InvoiceSerializer(serializers.ModelSerializer): class Meta: model = Invoice - exclude = ('bill',) + fields = '__all__' @staticmethod def get_status(self): diff --git a/finance/views.py b/finance/views.py index 4a4de84..507c5fa 100644 --- a/finance/views.py +++ b/finance/views.py @@ -41,7 +41,6 @@ class BillListView(APIView): 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', []) if bill: user = get_user_model().objects.get(email=bill['user']) @@ -60,66 +59,102 @@ class BillListView(APIView): 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()], - } - - return Response(res, status=200) + return Response(BillSerializer(bill_obj).data, status=200) return Response("Bill not set", status=400) return Response("Course detail access only for manager users", status=403) +class InvoiceDetailView(APIView): + renderer_classes = (JSONRenderer,) + + @staticmethod + def delete(request): + invoice_id = request.JSON.get('invoice_id', None) + if invoice_id is None: + return Response("invoice_id must be set", status=400) + + 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) + + @transaction_decorator + 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): + invoice_data = request.JSON.get('invoice', None) + if invoice_data is None: + return Response("Invoice mast be set", status=400) + + try: + bill = Bill.objects.get(id=invoice_data.pop('bill')) + except (Bill.DoesNotExist, KeyError): + return Response('Bill id must be set', status=400) + + if bill.check_validate() and invoice_data['is_open']: + return Response("Уже есть платёж открывающий курс", status=400) + + try: + invoice = Invoice.objects.get(id=invoice_data['id']) + except (Invoice.DoesNotExist, KeyError): + if bill.check_pay(): + return Response( + "Нельзя добавить новый платёж, так как один из платежей по счёту уже оплачен", status=400) + invoice = Invoice.objects.create(**invoice_data) + + if invoice.status == "F": + return Response(InvoiceSerializer(invoice).data, status=200) + + invoice.real_price = None + invoice.method = invoice_data['method'] + invoice.status = invoice_data['status'] + if invoice.status == "F": + invoice.real_price = invoice_data['real_price'] + + if bill.check_pay() and (invoice.price < invoice_data['price']): + return Response("""Нельзя менять стоимость по счёту в большую сторону, + когда один из платежей оплачен""", status=400) + + invoice.price = invoice_data['price'] + invoice.is_open = invoice_data['is_open'] + + 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 + + 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() + invoice.save() + + return Response(InvoiceSerializer(invoice).data, status=200) + + return Response("Invoice detail access only for manager users", status=403) + + class BillDetailView(APIView): renderer_classes = (JSONRenderer,) status_code = 200 @@ -343,7 +378,6 @@ class YandexAvisoView(APIView): 'response': xml_res, }) - msg = EmailMessage( 'Успешная оплата.', '''Пользователь "%s", перевёл %s рублей. Номер платежа в яндекс кассе %s''' From 7506946cd43c1aae3f64c07b3f6672c73fd47da1 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 22 Mar 2018 12:04:54 +0300 Subject: [PATCH 02/28] finance logging --- access/views.py | 5 ++- courses/models.py | 8 ++-- courses/urls.py | 2 + courses/views.py | 96 ++++++++++++++++++++++++++++++++++------------- finance/views.py | 3 ++ 5 files changed, 82 insertions(+), 32 deletions(-) diff --git a/access/views.py b/access/views.py index 5915943..f698ae0 100644 --- a/access/views.py +++ b/access/views.py @@ -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): diff --git a/courses/models.py b/courses/models.py index 3bc61ac..ce50bf7 100755 --- a/courses/models.py +++ b/courses/models.py @@ -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) diff --git a/courses/urls.py b/courses/urls.py index 819f814..3c78dbf 100644 --- a/courses/urls.py +++ b/courses/urls.py @@ -7,5 +7,7 @@ urlpatterns = [ url(r'lesson/(?P.+)/$', views.LessonDetail.as_view()), url(r'tree/(?P.+)/$', views.TreeView.as_view()), url(r'detail/(?P.+)/$', views.CourseDetailView.as_view()), + url(r'topic/update/$', views.UpdateTopicView.as_view()), + url(r'topic/delete/(?P[0-9]{1,99})/$', views.DeleteTopicView.as_view()), url(r'^$', views.CourseListView.as_view()), ] \ No newline at end of file diff --git a/courses/views.py b/courses/views.py index 5db4617..0b7f74e 100644 --- a/courses/views.py +++ b/courses/views.py @@ -1,17 +1,19 @@ +from django.db.models import F 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 +from courses.serializers import CourseDetailSerializer, CourseTreeSerializer, LessonSerializer, TeacherLessonSerializer, \ + TopicSerializer import jwt from courses.tasks import add_lesson from lms import settings - +import json class TreeView(APIView): renderer_classes = (JSONRenderer,) @@ -31,27 +33,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 +61,69 @@ 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): + 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 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(): + 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 +153,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 @@ -144,7 +188,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) diff --git a/finance/views.py b/finance/views.py index 507c5fa..494d022 100644 --- a/finance/views.py +++ b/finance/views.py @@ -97,6 +97,9 @@ class InvoiceDetailView(APIView): except (Bill.DoesNotExist, KeyError): return Response('Bill id must be set', status=400) + invoice_data['method'] = get_real_name(elem=invoice_data['method'], array=Invoice.BILL_METHOD) + invoice_data['status'] = get_real_name(elem=invoice_data['status'], array=Invoice.BILL_METHOD) + if bill.check_validate() and invoice_data['is_open']: return Response("Уже есть платёж открывающий курс", status=400) From 08b4a28e6e341a594c20ace0f656b25cb092fe96 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 23 Mar 2018 13:46:22 +0300 Subject: [PATCH 03/28] finance logging --- courses/serializers.py | 14 ++++++- courses/urls.py | 2 + courses/views.py | 92 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/courses/serializers.py b/courses/serializers.py index 359386b..b7010dc 100644 --- a/courses/serializers.py +++ b/courses/serializers.py @@ -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 = 'course/%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() diff --git a/courses/urls.py b/courses/urls.py index 3c78dbf..c2f8309 100644 --- a/courses/urls.py +++ b/courses/urls.py @@ -3,6 +3,8 @@ 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.+)/$', views.DeleteLessonView.as_view()), url(r'lesson/teacher/(?P.+)/$', views.LessonInfoView.as_view()), url(r'lesson/(?P.+)/$', views.LessonDetail.as_view()), url(r'tree/(?P.+)/$', views.TreeView.as_view()), diff --git a/courses/views.py b/courses/views.py index 0b7f74e..75437c0 100644 --- a/courses/views.py +++ b/courses/views.py @@ -1,19 +1,16 @@ -from django.db.models import F from jwt import DecodeError 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, \ - TopicSerializer +from courses.serializers import CourseDetailSerializer, CourseTreeSerializer, LessonSerializer, TeacherLessonSerializer import jwt from courses.tasks import add_lesson from lms import settings -import json + class TreeView(APIView): renderer_classes = (JSONRenderer,) @@ -66,12 +63,84 @@ class DeleteTopicView(APIView): @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: - t = Topic.objects.get(id=topic_id) + topic = Topic.objects.get(id=topic_id) except Topic.DoesNotExist: - return Response("Темы не существует", status=404) - t.delete() - return Response(CourseTreeSerializer(t.course).data, status=200) + 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): @@ -99,7 +168,7 @@ class UpdateTopicView(APIView): if topic_id: t = Topic.objects.get(id=topic_id) if not t.sort == sort: - for topic in reversed(): + for topic in reversed(course.topic_set.filter(sort__gte=sort)): topic.sort = topic.sort + 1 topic.save() t.sort = sort @@ -161,7 +230,8 @@ 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.groups.filter(name__in=['supports']).exists() or request.user.is_superuser)): return Response("Bad token", status=400) else: From 32e76a32a888f70e5425789af822ecf00b013a29 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 23 Mar 2018 18:06:34 +0300 Subject: [PATCH 04/28] finance logging --- courses/migrations/0006_auto_20180323_1743.py | 23 +++++++++++++++++++ courses/views.py | 3 +-- finance/models.py | 1 + 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 courses/migrations/0006_auto_20180323_1743.py diff --git a/courses/migrations/0006_auto_20180323_1743.py b/courses/migrations/0006_auto_20180323_1743.py new file mode 100644 index 0000000..a39f900 --- /dev/null +++ b/courses/migrations/0006_auto_20180323_1743.py @@ -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', + ), + ] diff --git a/courses/views.py b/courses/views.py index 75437c0..0964098 100644 --- a/courses/views.py +++ b/courses/views.py @@ -230,8 +230,7 @@ class LessonDetail(APIView): course = lesson.topic.course if payload is None: - if not (lesson.free or request.user.is_authenticated and - (request.user.groups.filter(name__in=['supports']).exists() or request.user.is_superuser)): + if not (lesson.free or request.user.is_authenticated and request.user.is_staff): return Response("Bad token", status=400) else: diff --git a/finance/models.py b/finance/models.py index a881d33..bf83867 100755 --- a/finance/models.py +++ b/finance/models.py @@ -13,6 +13,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) From f027b109a5c30738da48082b5cc7a3519d1c9e1f Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 26 Mar 2018 10:59:06 +0300 Subject: [PATCH 05/28] finance logging --- finance/models.py | 2 +- finance/urls.py | 1 + finance/views.py | 99 +++++++++++++++++++++++++++-------------------- 3 files changed, 59 insertions(+), 43 deletions(-) diff --git a/finance/models.py b/finance/models.py index bf83867..7de64e2 100755 --- a/finance/models.py +++ b/finance/models.py @@ -50,7 +50,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) diff --git a/finance/urls.py b/finance/urls.py index 5da326d..5b875b5 100644 --- a/finance/urls.py +++ b/finance/urls.py @@ -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), ] \ No newline at end of file diff --git a/finance/views.py b/finance/views.py index 494d022..4fcbc13 100644 --- a/finance/views.py +++ b/finance/views.py @@ -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 @@ -40,40 +41,38 @@ 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') + 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 = opener - bill_obj.description = description - bill_obj.comment = comment - bill_obj.save() - - return Response(BillSerializer(bill_obj).data, status=200) + 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 not set", status=400) + return Response(bill_obj.id, status=200) - return Response("Course detail access only for manager users", status=403) + return Response("Ошибка доступа, возможно вы разлогинились из другой вкладки браузера", status=403) class InvoiceDetailView(APIView): renderer_classes = (JSONRenderer,) @staticmethod - def delete(request): - invoice_id = request.JSON.get('invoice_id', None) - if invoice_id is None: - return Response("invoice_id must be set", status=400) + def delete(request, invoice_id): try: i = Invoice.objects.get(id=invoice_id) @@ -84,48 +83,62 @@ class InvoiceDetailView(APIView): return Response(status=204) - @transaction_decorator - def post(self, request): + @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_data = request.JSON.get('invoice', None) - if invoice_data is None: - return Response("Invoice mast be set", status=400) + bill_id = request.JSON.get('bill_id', 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) + 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=invoice_data.pop('bill')) - except (Bill.DoesNotExist, KeyError): - return Response('Bill id must be set', status=400) + bill = Bill.objects.get(id=bill_id) + except Bill.DoesNotExist: + return Response('Не найден счёт с id=%s' % bill_id, status=404) - invoice_data['method'] = get_real_name(elem=invoice_data['method'], array=Invoice.BILL_METHOD) - invoice_data['status'] = get_real_name(elem=invoice_data['status'], array=Invoice.BILL_METHOD) + method = get_real_name(elem=method, array=Invoice.BILL_METHOD) + status = get_real_name(elem=status, array=Invoice.BILL_METHOD) - if bill.check_validate() and invoice_data['is_open']: + if bill.check_validate() and is_open: return Response("Уже есть платёж открывающий курс", status=400) try: - invoice = Invoice.objects.get(id=invoice_data['id']) - except (Invoice.DoesNotExist, KeyError): + invoice = Invoice.objects.get(id=invoice_id) + except Invoice.DoesNotExist: if bill.check_pay(): return Response( "Нельзя добавить новый платёж, так как один из платежей по счёту уже оплачен", status=400) - invoice = Invoice.objects.create(**invoice_data) + 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 = invoice_data['method'] - invoice.status = invoice_data['status'] + invoice.method = method + invoice.status = status if invoice.status == "F": - invoice.real_price = invoice_data['real_price'] + invoice.real_price = invoice.real_price if real_price is None else real_price - if bill.check_pay() and (invoice.price < invoice_data['price']): + if bill.check_pay() and (invoice.price < price): return Response("""Нельзя менять стоимость по счёту в большую сторону, когда один из платежей оплачен""", status=400) - invoice.price = invoice_data['price'] - invoice.is_open = invoice_data['is_open'] + invoice.price = price + invoice.is_open = is_open if invoice.method == 'Y' and invoice.yandex_pay is None: yandex_pay = Payment.objects.create( @@ -196,7 +209,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: From b815f04472854aeef79e28238f819fdcbb5bb630 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 26 Mar 2018 14:01:02 +0300 Subject: [PATCH 06/28] finance logging --- finance/models.py | 4 ++-- finance/views.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/finance/models.py b/finance/models.py index 7de64e2..d11bc85 100755 --- a/finance/models.py +++ b/finance/models.py @@ -21,8 +21,8 @@ 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): - return self.invoice_set.filter(is_open=True).count() == 1 + 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() diff --git a/finance/views.py b/finance/views.py index 4fcbc13..10c1077 100644 --- a/finance/views.py +++ b/finance/views.py @@ -87,7 +87,8 @@ class InvoiceDetailView(APIView): 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): - bill_id = request.JSON.get('bill_id', None) + 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) @@ -105,15 +106,18 @@ class InvoiceDetailView(APIView): except Bill.DoesNotExist: return Response('Не найден счёт с id=%s' % bill_id, status=404) - method = get_real_name(elem=method, array=Invoice.BILL_METHOD) - status = get_real_name(elem=status, array=Invoice.BILL_METHOD) + 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() and is_open: + 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) From 610500446a95abb951de90d0529bae47021536a5 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 26 Mar 2018 14:05:09 +0300 Subject: [PATCH 07/28] finance logging --- finance/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/finance/models.py b/finance/models.py index d11bc85..a58269c 100755 --- a/finance/models.py +++ b/finance/models.py @@ -13,7 +13,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) + # date = models.DateTimeField(verbose_name="Дата выставления", auto_now_add=True) def __str__(self): return '%s: %s' % (self.id, self.user) From 866855ddd2aa2c098e1a12ee5f54842fd5aebda8 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 26 Mar 2018 14:18:34 +0300 Subject: [PATCH 08/28] finance logging --- .gitignore | 2 +- config_app/settings/prod.env.skeleton | 5 +++++ lms/settings.py | 14 +------------- 3 files changed, 7 insertions(+), 14 deletions(-) create mode 100644 config_app/settings/prod.env.skeleton diff --git a/.gitignore b/.gitignore index f55a249..92dfddb 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,4 @@ coverage.xml # Celery celerybeat-schedule -/config_app/settings/dev.env +/config_app/settings/local.env diff --git a/config_app/settings/prod.env.skeleton b/config_app/settings/prod.env.skeleton new file mode 100644 index 0000000..0f438fb --- /dev/null +++ b/config_app/settings/prod.env.skeleton @@ -0,0 +1,5 @@ +DEBUG=False +SECRET_KEY='....' +DATABASE_URL='psql://:@127.0.0.1:5432/' +EMAIL_URL='smtp+tls://:@smtp.gmail.com:587' +CACHE_URL=rediscache://127.0.0.1:6379/?client_class=django_redis.client.DefaultClient \ No newline at end of file diff --git a/lms/settings.py b/lms/settings.py index b4ae38d..c9310e3 100644 --- a/lms/settings.py +++ b/lms/settings.py @@ -8,20 +8,8 @@ import socket root = environ.Path(__file__) - 2 env = environ.Env() -MOD = os.environ.get('MOD', 'Prod') -DEBUG = os.environ.get('DEBUG', 'False') -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') - -elif MOD == 'Prod': - environ.Env.read_env(str(root) + '/config_app/settings/prod.env') - -else: - raise ImportError('no such environ ' + MOD) +environ.Env.read_env(str(root) + '/config_app/settings/local.env') EMAIL_CONFIG = env.email_url('EMAIL_URL',) vars().update(EMAIL_CONFIG) From c67385c21c9528ab01f75e9fd2edada77549b720 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 26 Mar 2018 14:38:31 +0300 Subject: [PATCH 09/28] finance logging --- Envoy.blade.php | 9 ++++++--- config_app/settings/prod.env | 5 ----- config_app/settings/test.env | 5 ----- 3 files changed, 6 insertions(+), 13 deletions(-) delete mode 100644 config_app/settings/prod.env delete mode 100644 config_app/settings/test.env diff --git a/Envoy.blade.php b/Envoy.blade.php index d3d66be..0564a73 100644 --- a/Envoy.blade.php +++ b/Envoy.blade.php @@ -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 diff --git a/config_app/settings/prod.env b/config_app/settings/prod.env deleted file mode 100644 index 6f33e00..0000000 --- a/config_app/settings/prod.env +++ /dev/null @@ -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 \ No newline at end of file diff --git a/config_app/settings/test.env b/config_app/settings/test.env deleted file mode 100644 index 5b922cf..0000000 --- a/config_app/settings/test.env +++ /dev/null @@ -1,5 +0,0 @@ -DEBUG=True -SECRET_KEY='!eiquy7_+2#vn3z%zfp51$m-=tmvtcv*cj*@x$!v(_9btq0w=$' -DATABASE_URL='psql://postgres@127.0.0.1:5432/test_lms' -EMAIL_URL='smtp+tls://9ae31a1a770138:a7d79ee373a14c@smtp.mailtrap.io:2525' -CACHE_URL=rediscache://127.0.0.1:6379/1?client_class=django_redis.client.DefaultClient \ No newline at end of file From 031ab8914e4fdccfcc191e15dc518a6eaca05ba9 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 26 Mar 2018 14:40:11 +0300 Subject: [PATCH 10/28] finance logging --- lms/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lms/settings.py b/lms/settings.py index c9310e3..778f639 100644 --- a/lms/settings.py +++ b/lms/settings.py @@ -11,6 +11,8 @@ env = environ.Env() environ.Env.read_env(str(root) + '/config_app/settings/local.env') +MASTER_PASSWORD = os.environ.get('MASTER_PASSWORD', '@J*1') + EMAIL_CONFIG = env.email_url('EMAIL_URL',) vars().update(EMAIL_CONFIG) From c0b6d529bbe65f8093f444f9260cda663a67d771 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 26 Mar 2018 14:55:54 +0300 Subject: [PATCH 11/28] finance logging --- finance/migrations/0004_bill_date.py | 22 ++++++++++++++++++++++ finance/models.py | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 finance/migrations/0004_bill_date.py diff --git a/finance/migrations/0004_bill_date.py b/finance/migrations/0004_bill_date.py new file mode 100644 index 0000000..84a07fe --- /dev/null +++ b/finance/migrations/0004_bill_date.py @@ -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, + ), + ] diff --git a/finance/models.py b/finance/models.py index a58269c..d11bc85 100755 --- a/finance/models.py +++ b/finance/models.py @@ -13,7 +13,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) + date = models.DateTimeField(verbose_name="Дата выставления", auto_now_add=True) def __str__(self): return '%s: %s' % (self.id, self.user) From 19e5565c0aa9f9a4944683f4e740eae3d1d5ac68 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 26 Mar 2018 16:06:05 +0300 Subject: [PATCH 12/28] finance logging --- courses/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courses/serializers.py b/courses/serializers.py index b7010dc..7b2434b 100644 --- a/courses/serializers.py +++ b/courses/serializers.py @@ -39,7 +39,7 @@ class LessonSerializer(MiniLessonSerializer): @staticmethod def get_materials(self): try: - prefix = 'course/%s/%s/%s' % (self.topic.course.token, self.topic.id, self.token) + 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: From a609141dd4dc579e89468c94297d532af664af47 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 27 Mar 2018 20:23:57 +0300 Subject: [PATCH 13/28] finance logging --- access/serializers.py | 23 ++++- access/urls.py | 1 - .../migrations/0009_progress_is_freeze.py | 20 +++++ progress/models.py | 1 + progress/serializers.py | 17 ++++ progress/urls.py | 3 + progress/views.py | 84 ++++++++++++++----- 7 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 progress/migrations/0009_progress_is_freeze.py diff --git a/access/serializers.py b/access/serializers.py index 2aea54b..f4bb455 100644 --- a/access/serializers.py +++ b/access/serializers.py @@ -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()] + diff --git a/access/urls.py b/access/urls.py index 4198a6a..d445565 100644 --- a/access/urls.py +++ b/access/urls.py @@ -10,7 +10,6 @@ urlpatterns = [ url(r'detail/(?P[0-9A-Fa-f-]+)/$', views.DetailUserView.as_view()), url(r'detail/$', 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.+)/$', 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()), diff --git a/progress/migrations/0009_progress_is_freeze.py b/progress/migrations/0009_progress_is_freeze.py new file mode 100644 index 0000000..9b96892 --- /dev/null +++ b/progress/migrations/0009_progress_is_freeze.py @@ -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='Прохождение было преостановленно'), + ), + ] diff --git a/progress/models.py b/progress/models.py index 2a6e5dc..918ce8d 100644 --- a/progress/models.py +++ b/progress/models.py @@ -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): diff --git a/progress/serializers.py b/progress/serializers.py index c2570e9..83d1ce6 100644 --- a/progress/serializers.py +++ b/progress/serializers.py @@ -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() diff --git a/progress/urls.py b/progress/urls.py index 44b7bd1..eeb675f 100644 --- a/progress/urls.py +++ b/progress/urls.py @@ -5,6 +5,9 @@ from progress import views urlpatterns = [ url(r'students/(?P[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), diff --git a/progress/views.py b/progress/views.py index 01513e8..35f680d 100644 --- a/progress/views.py +++ b/progress/views.py @@ -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.cheker.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): From 91f9fdde67f445f18c4208be0224ff5791d76fcc Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 27 Mar 2018 20:36:34 +0300 Subject: [PATCH 14/28] finance logging --- progress/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progress/views.py b/progress/views.py index 35f680d..c81a361 100644 --- a/progress/views.py +++ b/progress/views.py @@ -206,7 +206,7 @@ class StudentUpdateProgress(APIView): 'Студент оставил комментарий', '''Студент "%s" оставил вам комментарий.''' % request.user.get_full_name(), 'robo@skillbox.ru', - [pv.cheker.email], + [pv.cheсker.email], ) msg.send() From ef5baf777a05ea979e18eaf1dc42ac66e26926d2 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 27 Mar 2018 20:39:36 +0300 Subject: [PATCH 15/28] finance logging --- progress/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progress/views.py b/progress/views.py index c81a361..58dc891 100644 --- a/progress/views.py +++ b/progress/views.py @@ -206,7 +206,7 @@ class StudentUpdateProgress(APIView): 'Студент оставил комментарий', '''Студент "%s" оставил вам комментарий.''' % request.user.get_full_name(), 'robo@skillbox.ru', - [pv.cheсker.email], + [pv.checker.email], ) msg.send() From f3bc397b99d2cd4dcb78d3a1c1416c8a1457cf8f Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 08:12:31 +0300 Subject: [PATCH 16/28] finance email --- finance/views.py | 33 ++++++++++++++++--------- templates/mail/sales/back_set_bill.html | 6 +++++ templates/mail/sales/back_set_bill.txt | 6 +++++ 3 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 templates/mail/sales/back_set_bill.html create mode 100644 templates/mail/sales/back_set_bill.txt diff --git a/finance/views.py b/finance/views.py index 10c1077..677af39 100644 --- a/finance/views.py +++ b/finance/views.py @@ -13,6 +13,9 @@ 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 django.template import Context from courses.models import Course from courses.api import CourseParamsApi @@ -154,20 +157,26 @@ class InvoiceDetailView(APIView): ) invoice.yandex_pay = yandex_pay - 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'], - ) + plaintext = get_template('mail/sales/back_set_bill.txt') + html = get_template('mail/sales/back_set_bill.txt') + + context = 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, + }) + + subject, from_email, to = 'Выставлен новый счёт', 'robo@skillbox.ru', 'invoice.bill.opener.email' + text_content = plaintext.render(context) + html_content = html.render(context) + + msg = EmailMultiAlternatives(subject, text_content, from_email, [to]) + msg.attach_alternative(html_content, "text/html") msg.send() + invoice.save() return Response(InvoiceSerializer(invoice).data, status=200) diff --git a/templates/mail/sales/back_set_bill.html b/templates/mail/sales/back_set_bill.html new file mode 100644 index 0000000..639ed4e --- /dev/null +++ b/templates/mail/sales/back_set_bill.html @@ -0,0 +1,6 @@ +Выставлен новый счет
+Пользователь: {{ user_email }}
+Продавец: {{ opener_full_name }}
+Курс: {{ course_title }}
+Дата продажи: {{ date }}
+Сумма: {{ price }} руб.
\ No newline at end of file diff --git a/templates/mail/sales/back_set_bill.txt b/templates/mail/sales/back_set_bill.txt new file mode 100644 index 0000000..982497f --- /dev/null +++ b/templates/mail/sales/back_set_bill.txt @@ -0,0 +1,6 @@ +Выставлен новый счет +Пользователь: {{ user_email }} +Продавец: {{ opener_full_name }} +Курс: {{ course_title }} +Дата продажи: {{ date }} +Сумма: {{ price }} руб. \ No newline at end of file From 432f52255d5564f1cbf1a53f8b9a028587c4cfdc Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 08:29:36 +0300 Subject: [PATCH 17/28] finance email --- finance/views.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/finance/views.py b/finance/views.py index 677af39..5ca318b 100644 --- a/finance/views.py +++ b/finance/views.py @@ -15,7 +15,6 @@ 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 django.template import Context from courses.models import Course from courses.api import CourseParamsApi @@ -160,20 +159,20 @@ class InvoiceDetailView(APIView): plaintext = get_template('mail/sales/back_set_bill.txt') html = get_template('mail/sales/back_set_bill.txt') - context = Context({ + 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, - }) + } subject, from_email, to = 'Выставлен новый счёт', 'robo@skillbox.ru', 'invoice.bill.opener.email' text_content = plaintext.render(context) html_content = html.render(context) - msg = EmailMultiAlternatives(subject, text_content, from_email, [to]) + msg = EmailMultiAlternatives(subject, text_content, from_email, [to], bcc=['dmitry.dolya@skillbox.ru'],) msg.attach_alternative(html_content, "text/html") msg.send() From 3e55aca89c37652ea7a44b35faf72be31ec2a2ac Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 08:38:24 +0300 Subject: [PATCH 18/28] finance email --- finance/models.py | 7 +++++-- finance/signals.py | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/finance/models.py b/finance/models.py index d11bc85..6521060 100755 --- a/finance/models.py +++ b/finance/models.py @@ -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) @@ -62,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 is None else self.comment + def __str__(self): return '%s:%s %s' % (self.id, self.get_status_display(), self.bill.user) diff --git a/finance/signals.py b/finance/signals.py index bd8d330..18dbd39 100644 --- a/finance/signals.py +++ b/finance/signals.py @@ -19,8 +19,7 @@ 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,), + instance.get_comment(), 'robo@skillbox.ru', [instance.yandex_pay.cps_email], [instance.bill.opener.email], From 8a15099464e8e8aa5053c838b894894bbbad28a2 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 08:44:20 +0300 Subject: [PATCH 19/28] finance email --- finance/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/finance/models.py b/finance/models.py index 6521060..401cfa6 100755 --- a/finance/models.py +++ b/finance/models.py @@ -63,7 +63,7 @@ class Invoice(models.Model): def get_comment(self): return '''Вам выставлен счёт, для оплаты перейдите по ссылке %s/api/v1/finance/payment/%s/''' % (settings.DOMAIN, self.yandex_pay.id,) if\ - self.comment is None else self.comment + self.comment is None or self.comment == "" else self.comment def __str__(self): return '%s:%s %s' % (self.id, self.get_status_display(), self.bill.user) From 861ec28559f964f2e97fa8efd041b2f5a5bf8e07 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 08:53:45 +0300 Subject: [PATCH 20/28] finance email --- finance/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/finance/views.py b/finance/views.py index 5ca318b..7f8d95e 100644 --- a/finance/views.py +++ b/finance/views.py @@ -95,6 +95,7 @@ class InvoiceDetailView(APIView): 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: @@ -136,6 +137,7 @@ class InvoiceDetailView(APIView): 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 @@ -145,6 +147,7 @@ class InvoiceDetailView(APIView): invoice.price = price invoice.is_open = is_open + invoice.comment = comment if comment else None if invoice.method == 'Y' and invoice.yandex_pay is None: yandex_pay = Payment.objects.create( From 309e513bb53bc675ecf5b2794d87ea76264a4c12 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 09:32:42 +0300 Subject: [PATCH 21/28] finance email --- finance/models.py | 4 ++-- finance/views.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/finance/models.py b/finance/models.py index 401cfa6..01bf635 100755 --- a/finance/models.py +++ b/finance/models.py @@ -62,8 +62,8 @@ class Invoice(models.Model): def get_comment(self): return '''Вам выставлен счёт, для оплаты перейдите по ссылке - %s/api/v1/finance/payment/%s/''' % (settings.DOMAIN, self.yandex_pay.id,) if\ - self.comment is None or self.comment == "" else self.comment + %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) diff --git a/finance/views.py b/finance/views.py index 7f8d95e..6dd7339 100644 --- a/finance/views.py +++ b/finance/views.py @@ -147,7 +147,7 @@ class InvoiceDetailView(APIView): invoice.price = price invoice.is_open = is_open - invoice.comment = comment if comment else None + invoice.comment = comment if invoice.method == 'Y' and invoice.yandex_pay is None: yandex_pay = Payment.objects.create( @@ -175,7 +175,7 @@ class InvoiceDetailView(APIView): text_content = plaintext.render(context) html_content = html.render(context) - msg = EmailMultiAlternatives(subject, text_content, from_email, [to], bcc=['dmitry.dolya@skillbox.ru'],) + msg = EmailMultiAlternatives(subject, text_content, from_email, [to], bcc=['dmitry.dolya@skillbox.ru']) msg.attach_alternative(html_content, "text/html") msg.send() From 55eaf9187580e7bd76e87aac13e1425b2ce92224 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 09:55:55 +0300 Subject: [PATCH 22/28] finance email --- finance/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/finance/signals.py b/finance/signals.py index 18dbd39..3a9433b 100644 --- a/finance/signals.py +++ b/finance/signals.py @@ -20,7 +20,7 @@ def invoice_signal(instance, **kwargs): msg = EmailMessage( 'Вам выставлен новый счёт', instance.get_comment(), - 'robo@skillbox.ru', + settings.EMAIL_LOGIN, [instance.yandex_pay.cps_email], [instance.bill.opener.email], reply_to=[instance.bill.opener.email], From 3d1ab96d51a0655bbb6179f02a4886615a031e52 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 09:58:33 +0300 Subject: [PATCH 23/28] finance email --- finance/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/finance/signals.py b/finance/signals.py index 3a9433b..1bd81b3 100644 --- a/finance/signals.py +++ b/finance/signals.py @@ -20,7 +20,7 @@ def invoice_signal(instance, **kwargs): msg = EmailMessage( 'Вам выставлен новый счёт', instance.get_comment(), - settings.EMAIL_LOGIN, + settings.EMAIL_HOST_USER, [instance.yandex_pay.cps_email], [instance.bill.opener.email], reply_to=[instance.bill.opener.email], From f4204913b2cb356941721aff9c9e595a50ab92a4 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 10:01:40 +0300 Subject: [PATCH 24/28] finance email --- finance/signals.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/finance/signals.py b/finance/signals.py index 1bd81b3..08442be 100644 --- a/finance/signals.py +++ b/finance/signals.py @@ -20,9 +20,8 @@ def invoice_signal(instance, **kwargs): msg = EmailMessage( 'Вам выставлен новый счёт', instance.get_comment(), - settings.EMAIL_HOST_USER, - [instance.yandex_pay.cps_email], - [instance.bill.opener.email], + to=[instance.yandex_pay.cps_email], + bcc=[instance.bill.opener.email], reply_to=[instance.bill.opener.email], ) msg.send() @@ -51,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], ) From 548645fb7f1315e0eaa419f4cca12bffe865c5bb Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 10:14:03 +0300 Subject: [PATCH 25/28] finance email --- finance/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/finance/views.py b/finance/views.py index 6dd7339..05be344 100644 --- a/finance/views.py +++ b/finance/views.py @@ -170,12 +170,12 @@ class InvoiceDetailView(APIView): 'price': invoice.price, } - subject, from_email, to = 'Выставлен новый счёт', 'robo@skillbox.ru', 'invoice.bill.opener.email' + subject, to = 'Выставлен новый счёт', invoice.bill.opener.email text_content = plaintext.render(context) html_content = html.render(context) - msg = EmailMultiAlternatives(subject, text_content, from_email, [to], bcc=['dmitry.dolya@skillbox.ru']) + msg = EmailMultiAlternatives(subject, text_content, [to], bcc=['dmitry.dolya@skillbox.ru']) msg.attach_alternative(html_content, "text/html") msg.send() From a4a1f0171f36152b1bf551308bf91cc824e5af18 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 10:18:00 +0300 Subject: [PATCH 26/28] finance email --- finance/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/finance/views.py b/finance/views.py index 05be344..bf163bb 100644 --- a/finance/views.py +++ b/finance/views.py @@ -176,7 +176,7 @@ class InvoiceDetailView(APIView): html_content = html.render(context) msg = EmailMultiAlternatives(subject, text_content, [to], bcc=['dmitry.dolya@skillbox.ru']) - msg.attach_alternative(html_content, "text/html") + # msg.attach_alternative(html_content, "text/html") msg.send() invoice.save() From f6d2d947a1f815590452a0623d9d829504f1ad9c Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 10:34:17 +0300 Subject: [PATCH 27/28] finance email --- finance/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/finance/views.py b/finance/views.py index bf163bb..05be344 100644 --- a/finance/views.py +++ b/finance/views.py @@ -176,7 +176,7 @@ class InvoiceDetailView(APIView): html_content = html.render(context) msg = EmailMultiAlternatives(subject, text_content, [to], bcc=['dmitry.dolya@skillbox.ru']) - # msg.attach_alternative(html_content, "text/html") + msg.attach_alternative(html_content, "text/html") msg.send() invoice.save() From 88e5495b7a48c76d830a67e26191cf868d14af22 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Mar 2018 10:37:21 +0300 Subject: [PATCH 28/28] finance email --- finance/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/finance/views.py b/finance/views.py index 05be344..34283de 100644 --- a/finance/views.py +++ b/finance/views.py @@ -175,7 +175,7 @@ class InvoiceDetailView(APIView): text_content = plaintext.render(context) html_content = html.render(context) - msg = EmailMultiAlternatives(subject, text_content, [to], bcc=['dmitry.dolya@skillbox.ru']) + msg = EmailMultiAlternatives(subject, text_content, to=[to], bcc=['dmitry.dolya@skillbox.ru']) msg.attach_alternative(html_content, "text/html") msg.send()