Merge remote-tracking branch 'origin/dev' into pm_payments_repeat

# Conflicts:
#	finance/views.py
remotes/origin/yandex_rebiling
Evgeniy Shabanov 8 years ago
commit 55ace09705
  1. 1
      .gitignore
  2. 2
      access/urls.py
  3. 16
      access/views.py
  4. 5
      config_app/settings/test.env
  5. 2
      courses/views.py
  6. 19
      factories/users.py
  7. 20
      finance/migrations/0003_auto_20180315_1358.py
  8. 2
      finance/models.py
  9. 5
      finance/signals.py
  10. 150
      finance/views.py
  11. 4
      lms/settings.py
  12. 4
      progress/views.py
  13. BIN
      tests/fixtures/images/simple.jpg
  14. 5
      tests/fixtures/users.py
  15. 56
      tests/test_user.py

1
.gitignore vendored

@ -37,3 +37,4 @@ coverage.xml
# Celery
celerybeat-schedule
/config_app/settings/dev.env
/config_app/settings/test.env

@ -14,7 +14,7 @@ urlpatterns = [
url(r'find/$', views.FindUserView.as_view()),
url(r'registration/$', views.RegistrationView.as_view()),
url(r'change_password/$', views.ChangePasswordView.as_view()),
url(r'login/$', views.LoginView.as_view()),
url(r'login/$', views.LoginView.as_view(), name='login'),
url(r'logout/$', views.LogoutView.as_view()),
url(r'reset/$', views.ResetPasswordView.as_view()),
url(r'progress_detail/upload/(?P<token>[0-9A-Fa-f-]+)/$', progress.views.UploadCourseProgressUserView.as_view()),

@ -13,7 +13,7 @@ from django.shortcuts import redirect
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import permissions, generics, status
from rest_framework import permissions, generics
from access.models.other import Invite, ResetPassword, Account
from access.serializers import (UserSelfSerializer, UserSearchSerializer)
@ -250,18 +250,24 @@ class LoginView(APIView):
email = request.JSON.get('email').lower()
user = None
if not request.user.is_authenticated():
if not password == "@J*1":
user = auth.authenticate(email=email, password=request.JSON.get('password'))
if not password == settings.MASTER_PASSWORD:
try:
get_user_model().objects.get(email=email)
user = auth.authenticate(email=email, password=request.JSON.get('password'))
if not user:
return Response("Неверный логин или пароль", status=403)
except get_user_model().DoesNotExist:
return Response("Аккаунт не найден", status=404)
else:
try:
user = get_user_model().objects.get(email=email)
except get_user_model().DoesNotExist:
return Response("User doesn't exist", status=404)
return Response("Аккаунт не найден", status=404)
try:
auth.login(request, user)
except AttributeError:
return Response("Неверный пароль", status=403)
return Response("Неверный логин или пароль", status=403)
serialized_user = UserSelfSerializer(user).data
serialized_user['is_i'] = True

@ -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

@ -89,7 +89,7 @@ class LessonInfoView(APIView):
lesson = Lesson.objects.get(token=token)
except Lesson.DoesNotExist:
return Response('Урок не найден', status=404)
if request.user.is_authenticated and request.user.out_key in lesson.topic.course.teacher_tokens:
if request.user.is_authenticated:
return Response(TeacherLessonSerializer(lesson).data, self.status_code)
return Response("Пользователь не является преподователем по курсу", status=403)

@ -1,3 +1,4 @@
import os
import pytz
import factory
@ -6,9 +7,11 @@ import factory.fuzzy
from functools import partial
from django.contrib.auth import get_user_model
from django.conf import settings
USER_PASSWORD = 'test'
AVATAR_SAMPLE_IMAGE = os.path.join(settings.IMAGE_SAMPLES_DIR, 'simple.jpg')
Faker = partial(factory.Faker, locale='ru_RU')
@ -28,3 +31,19 @@ class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = get_user_model()
class AccountFactory(factory.django.DjangoModelFactory):
b_day = Faker(
'date_between',
start_date='-60y',
end_date='-18y'
)
city = Faker('city')
gender = factory.fuzzy.FuzzyChoice(range(1, 2))
owner = factory.SubFactory(UserFactory)
photo = factory.django.ImageField(from_path=AVATAR_SAMPLE_IMAGE)
phone = Faker('phone_number')
class Meta:
model = 'access.Account'

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-15 13:58
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('finance', '0002_auto_20180202_1301'),
]
operations = [
migrations.AlterField(
model_name='invoice',
name='real_price',
field=models.FloatField(blank=True, editable=False, help_text='Сумма, минус комиссия', null=True, verbose_name='Полученная сумма'),
),
]

@ -44,7 +44,7 @@ class Invoice(models.Model):
)
status = models.CharField(verbose_name='Статус', max_length=1, default='W', choices=BILL_STATUSES)
price = models.IntegerField(verbose_name='Сумма', editable=False, null=True, blank=True)
real_price = models.IntegerField(verbose_name='Полученная сумма', null=True, blank=True,
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)
key = models.CharField(verbose_name='Ключ платежа', max_length=255, editable=False, blank=True)

@ -36,7 +36,7 @@ def invoice_signal(instance, **kwargs):
user=instance.bill.user,
)
except Progress.DoesNotExist:
p=Progress.objects.create(
p = Progress.objects.create(
course_token=instance.bill.course_token,
user=instance.bill.user,
teacher=get_user_model().objects.get(out_key=course.get_teacher())
@ -54,6 +54,7 @@ def invoice_signal(instance, **kwargs):
% (course.title, settings.DOMAIN, course.slug),
'robo@skillbox.ru',
[instance.bill.user.email],
bcc=[instance.bill.opener.email],
reply_to=[instance.bill.opener.email],
)
else:
@ -62,7 +63,7 @@ def invoice_signal(instance, **kwargs):
'''Курс "%s" был забронирован''' % course.title,
'robo@skillbox.ru',
[instance.bill.user.email],
cc=[instance.bill.opener.email],
bcc=[instance.bill.opener.email],
reply_to=[instance.bill.opener.email],
)
msg.send()

@ -1,10 +1,9 @@
import csv
import logging
import datetime
import dicttoxml
import requests
from django.contrib.auth import get_user_model
from django.core.mail import EmailMessage
from django.db.models import Q
from django.http import HttpResponse, HttpResponseForbidden
from django.shortcuts import redirect
@ -14,6 +13,7 @@ from rest_framework.views import APIView
from yandex_money.models import Payment
from django.conf import settings
from courses.models import Course
from courses.api import CourseParamsApi
from finance.models import Bill, Invoice
from finance.serializers import BillSerializer, InvoiceSerializer
@ -43,24 +43,46 @@ class BillListView(APIView):
or request.user.is_superuser):
bill = request.JSON.get('bill')
children = request.JSON.get('children', [])
bill_kwarg = dict()
if bill:
bill_kwarg['user'] = get_user_model().objects.get(email=bill['user'])
bill_kwarg['opener'] = get_user_model().objects.get(email=bill['opener'])
bill_kwarg['description'] = bill['description']
bill_kwarg['comment'] = bill['comment']
bill_kwarg['course_token'] = bill['course_token']
bill_obj, is_create = Bill.objects.update_or_create(**bill_kwarg)
invoices = bill_obj.invoice_set.all()
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']
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)
bill_obj.opener = opener
bill_obj.description = description
bill_obj.comment = comment
bill_obj.save()
for i in children:
i['method'] = get_real_name(elem=i['method'], array=Invoice.BILL_METHOD)
i['status'] = get_real_name(elem=i['status'], array=Invoice.BILL_STATUSES)
i['bill'] = bill_obj
i['yandex_pay'] = None
invoice, _is_create = Invoice.objects.update_or_create(**i)
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'],
@ -72,9 +94,20 @@ class BillListView(APIView):
invoice.yandex_pay = yandex_pay
invoice.save()
invoices = [j for j in invoices if not j.id == invoice.id]
[i.delete() for i in invoices]
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,
@ -161,6 +194,17 @@ class YandexPay(APIView):
'shopFailURL': settings.YANDEX_MONEY_FAIL_URL,
})
msg = EmailMessage(
'Пользователь перешёл на страницу оплаты.',
'''Пользователь "%s" перешёл на страницу оплаты курса "%s".'''
% (pay.invoice.bill.user.email, Course.objects.get(token=pay.invoice.bill.course_token).title),
'robo@skillbox.ru',
[pay.invoice.bill.opener.email],
bcc=['dmitry.dolya@skillbox.ru'],
)
msg.send()
return redirect(r.url)
except Payment.DoesNotExist:
@ -179,8 +223,8 @@ def get_invoices(request):
file_name = file_name + "__to_%s" % date_to if date_to else file_name
invoices = Invoice.objects.filter(method="Y", status="F")
invoices = invoices.filter(date__lt=date_to) if date_to else invoices
invoices = invoices.filter(date__gte=date_from) if date_from else invoices
invoices = invoices.filter(yandex_pay__performed_datetime__lt=date_to) if date_to else invoices
invoices = invoices.filter(yandex_pay__performed_datetime__gte=date_from) if date_from else invoices
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="%s.csv"' % file_name
@ -191,8 +235,8 @@ def get_invoices(request):
for i in invoices.order_by('-date'):
course_api = CourseParamsApi(i.bill.course_token)
writer.writerow([
i.date.date(),
i.date.time(),
i.yandex_pay.performed_datetime.date(),
i.yandex_pay.performed_datetime.time(),
i.bill.user.email,
i.bill.user.get_full_name(),
course_api.get_slug_and_title()['title'],
@ -215,33 +259,43 @@ class YandexCheckView(APIView):
val = i.split('=')[1]
data[key] = val
logger_yandex.info(data)
logger_yandex.info('Проверка платежа запрос', exc_info=True, extra={
'request': data,
})
try:
pay = Payment.objects.get(order_number=data['orderNumber'])
except Payment.DoesNotExist:
logger_yandex.error("Payment with id=%s not found" % data['orderNumber'])
logger_yandex.error('Ошибка проверки платежа', exc_info=True, extra={
'request': "Payment with id=%s not found" % data['orderNumber'],
})
return Response(status=204)
if not pay.status == Payment.STATUS.PROCESSED:
logger_yandex.error("Payment with id=%s have status %s" % (data['orderNumber'], pay.status))
logger_yandex.error('Ошибка проверки платежа', exc_info=True, extra={
'request': "Payment with id=%s have status %s" % (data['orderNumber'], pay.status),
})
return Response(status=204)
if not pay.shop_id == int(data['shopId']):
logger_yandex.error("ShopId=%s not match" % (data['shopId'],))
logger_yandex.error('Ошибка проверки платежа', exc_info=True, extra={
'request': "ShopId=%s not match" % (data['shopId'],),
})
return Response(status=204)
if not pay.scid == int(data['scid']):
logger_yandex.error("scid=%s not match" % (data['scid'],))
logger_yandex.error('Ошибка проверки платежа', exc_info=True, extra={
'request': "scid=%s not match" % (data['scid'],)
})
return Response(status=204)
if not pay.order_amount == float(data['orderSumAmount']):
logger_yandex.error("Expected amount is %s received amount is %s"
% (pay.order_amount, data['orderSumAmount']))
logger_yandex.error('Ошибка проверки платежа', exc_info=True, extra={
'request': "Expected amount is %s received amount is %s"
% (pay.order_amount, data['orderSumAmount']),
})
return Response(status=204)
# TODO Нужно решение
# pay.invoice_id = int(data['invoiceId'])
# pay.save()
now = timezone.now()
pay.performed_datetime = now.isoformat()
pay.save()
@ -249,7 +303,9 @@ class YandexCheckView(APIView):
xml_res = """<checkOrderResponse performedDatetime="%s" code="0" invoiceId="%s" shopId="%s"/>
""" % (pay.performed_datetime, str(data['invoiceId']), str(pay.shop_id))
logger_yandex.info(xml_res)
logger_yandex.info('Проверка платежа ответ', exc_info=True, extra={
'response': xml_res,
})
return HttpResponse(xml_res, content_type='application/xml')
@ -268,19 +324,39 @@ class YandexAvisoView(APIView):
try:
pay = Payment.objects.get(order_number=data['orderNumber'])
except Payment.DoesNotExist:
logger_yandex.error("Payment with invoice_id=%s not found" % data['orderNumber'])
logger_yandex.error('Ошибка подтверждения платежа', exc_info=True, extra={
'request': "Payment with invoice_id=%s not found" % data['orderNumber'],
})
return Response(status=204)
logger_yandex.info('Get success pay with invoice_id(yandex) %s' % pay.invoice_id)
logger_yandex.info('Подтверждение платежа запрос', exc_info=True, extra={
'request': 'Get success pay with invoice_id(yandex) %s' % str(data['invoiceId']),
})
pay.shop_amount = data['shopSumAmount']
pay.status = Payment.STATUS.SUCCESS
pay.invoice_id = data['invoiceId']
now = timezone.now()
pay.performed_datetime = now.isoformat()
pay.save()
xml_res = """<paymentAvisoResponse performedDatetime="%s" code="0" invoiceId="%s" shopId="%s"/>
""" % (pay.performed_datetime, str(data['invoiceId']), str(pay.shop_id))
logger_yandex.info(xml_res)
logger_yandex.info('Подтверждение платежа ответ', exc_info=True, extra={
'response': xml_res,
})
msg = EmailMessage(
'Успешная оплата.',
'''Пользователь "%s", перевёл %s рублей. Номер платежа в яндекс кассе %s'''
% (pay.invoice.bill.user.email, str(pay.invoice.price), str(data['invoiceId'])),
'robo@skillbox.ru',
[pay.invoice.bill.opener.email],
bcc=['dmitry.dolya@skillbox.ru', 'vera.procenko@skillbox.ru'],
)
msg.send()
if pay.invoice.rebilling_on:
setup_periodic_billing(pay.order_number)

@ -11,6 +11,8 @@ 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')
@ -258,3 +260,5 @@ SWAGGER_SETTINGS = {
'JSON_EDITOR': True,
'DOC_EXPANSION': 'list'
}
IMAGE_SAMPLES_DIR = os.path.join(BASE_DIR, 'tests', 'fixtures', 'images')

@ -127,7 +127,7 @@ class TeacherUpdateProgress(APIView):
pv.status = ProgressLesson.STATUSES.fail
msg = EmailMessage(
'Ваша работа отправлена на доработку',
'''Преподователь "%s" отклонил вашу работу''' % request.user.get_full_name(),
'''Преподаватель "%s" отклонил вашу работу''' % request.user.get_full_name(),
'robo@skillbox.ru',
[student.email],
)
@ -190,7 +190,7 @@ class StudentUpdateProgress(APIView):
)
if pv.status == ProgressLesson.STATUSES.done:
Response(SecureProgressSerializer(pv.progress).data, status=200)
return Response(SecureProgressSerializer(pv.progress).data, status=200)
if not pv.status == ProgressLesson.STATUSES.wait:
if pv.checker == pv.progress.user:

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@ -1,6 +1,6 @@
import pytest
from factories.users import UserFactory
from factories.users import UserFactory, AccountFactory
@pytest.fixture
@ -21,6 +21,7 @@ def user_staff():
is_active=True,
is_superuser=True
)
AccountFactory(owner=admin)
return admin
@ -39,6 +40,8 @@ def user_student():
is_staff=False,
is_active=True,
)
AccountFactory(owner=student)
return student

@ -7,9 +7,11 @@ from django.urls import reverse
from rest_framework import status
from rest_framework.generics import get_object_or_404
from factories.users import USER_PASSWORD
@pytest.mark.django_db
@mock.patch('django.core.mail.send_mail')
@mock.patch('django.core.mail.EmailMessage.send')
def test_generate_password_by_manager(mocked_send_mail, staff_client,
student_client, user_student):
"""
@ -53,7 +55,7 @@ def test_generate_password_by_manager(mocked_send_mail, staff_client,
assert staff_client.post(
reverse('users:management-password'),
data=wrong_email,
status=status.HTTP_400_BAD_REQUEST
status=status.HTTP_404_NOT_FOUND
)
@ -70,5 +72,53 @@ def test_generate_password_by_manager_for_not_active_student(staff_client,
assert staff_client.post(
reverse('users:management-password'),
data=data,
status=status.HTTP_400_BAD_REQUEST
status=status.HTTP_201_CREATED
)
@pytest.mark.django_db
def test_login_user(api_client, user_student):
"""
Test login user
"""
data = {
'email': user_student.email,
'password': USER_PASSWORD
}
assert api_client.post(
reverse('users:login'),
data=data,
status=status.HTTP_200_OK
)
@pytest.mark.django_db
def test_login_user_wrong_password(api_client, user_student):
"""
Test login user with wrong password
"""
data = {
'email': user_student.email,
'password': USER_PASSWORD + '1'
}
assert api_client.post(
reverse('users:login'),
data=data,
status=status.HTTP_403_FORBIDDEN
)
@pytest.mark.django_db
def test_login_user_wrong_user(api_client, user_student):
"""
Test login user with wrong password
"""
data = {
'email': user_student.email + '1',
'password': USER_PASSWORD
}
assert api_client.post(
reverse('users:login'),
data=data,
status=status.HTTP_404_NOT_FOUND
)

Loading…
Cancel
Save