Compare commits

..

No commits in common. 'master' and 'feature/fix_accout_not_found' have entirely different histories.

  1. 5
      .gitignore
  2. 23
      Envoy.blade.php
  3. 42
      access/management/commands/set_tokens.py
  4. 7
      access/models/other.py
  5. 40
      access/serializers.py
  6. 8
      access/urls.py
  7. 30
      access/views.py
  8. 31
      config_app/management/commands/for_test.py
  9. 5
      config_app/settings/prod.env
  10. 5
      config_app/settings/prod.env.skeleton
  11. 23
      courses/migrations/0006_auto_20180323_1743.py
  12. 34
      courses/models.py
  13. 19
      courses/serializers.py
  14. 13
      courses/tasks.py
  15. 6
      courses/urls.py
  16. 240
      courses/views.py
  17. 8
      finance/admin.py
  18. 56
      finance/loggers.py
  19. 0
      finance/management/__init__.py
  20. 14
      finance/management/commands/yandex_repeat_payment.py
  21. 20
      finance/migrations/0003_auto_20180315_1358.py
  22. 22
      finance/migrations/0004_bill_date.py
  23. 35
      finance/migrations/0005_auto_20180329_1346.py
  24. 37
      finance/migrations/0006_auto_20180330_1121.py
  25. 23
      finance/migrations/0007_auto_20180330_1452.py
  26. 28
      finance/migrations/0008_invoicerebilling.py
  27. 21
      finance/migrations/0009_invoicerebilling_pay_count.py
  28. 40
      finance/migrations/0010_auto_20180412_1628.py
  29. 128
      finance/models.py
  30. 6
      finance/serializers.py
  31. 21
      finance/signals.py
  32. 81
      finance/tasks.py
  33. 8
      finance/urls.py
  34. 439
      finance/views.py
  35. 12
      lms/celery.py
  36. 60
      lms/settings.py
  37. 6
      lms/urls.py
  38. 520
      logs/celery.log
  39. 623
      logs/flower.log
  40. 121
      logs/schedule.log
  41. 20
      progress/migrations/0009_progress_is_freeze.py
  42. 20
      progress/migrations/0010_progress_exp_date.py
  43. 2
      progress/models.py
  44. 40
      progress/serializers.py
  45. 5
      progress/urls.py
  46. 243
      progress/views.py
  47. 22
      storage/views.py
  48. 6
      templates/mail/sales/back_set_bill.html
  49. 7
      templates/mail/sales/pay_access.html
  50. 57
      templates/yandex/test_pay.html

5
.gitignore vendored

@ -3,7 +3,6 @@
# use glob syntax. # use glob syntax.
syntax: glob syntax: glob
logs/
docs/_build docs/_build
.idea .idea
.DS_Store .DS_Store
@ -37,5 +36,5 @@ coverage.xml
# Celery # Celery
celerybeat-schedule celerybeat-schedule
/config_app/settings/local.env /config_app/settings/dev.env
/config_app/settings/test.env

@ -29,16 +29,11 @@ echo '{{ $new_release_dir }}'
@task('create_symlinks', ['on' => 'localhost']) @task('create_symlinks', ['on' => 'localhost'])
echo '>> Создание симлинков' echo '>> Создание симлинков'
ln -s {{ $app_dir }}/configs/master/ssl {{ $new_release_dir }}/ssl @if ($branch)
@if ($branch == 'master') ln -nfs {{ $app_dir }}/configs/master/prod.env {{ $new_release_dir }}/config_app/settings/prod.env
ln -nfs {{ $app_dir }}/configs/master/prod.env {{ $new_release_dir }}/config_app/settings/local.env ln -nfs {{ $app_dir }}/configs/dev/dev.env {{ $new_release_dir }}/config_app/settings/dev.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 ln -nfs {{ $app_dir }}/media/master {{ $new_release_dir }}/media
@endif @endif
ln -nfs {{ $app_dir }}/logs/{{ $branch }} {{ $new_release_dir }}/logs
@endtask @endtask
@task('install_req', ['on' => 'localhost']) @task('install_req', ['on' => 'localhost'])
@ -50,18 +45,10 @@ echo '>> Подтягиваем зависимости'
@endtask @endtask
@task('run_tests', ['on' => 'localhost']) @task('run_tests', ['on' => 'localhost'])
echo '>> Запускаем тесты' echo '>> Запускаем миграции тесты'
@if ($branch)
cd {{ $new_release_dir }}
source /env/bin/activate && python manage.py test
@endif
@endtask
@task('run_migrate', ['on' => 'localhost'])
echo '>> Запускаем миграции'
@if ($branch) @if ($branch)
cd {{ $new_release_dir }} cd {{ $new_release_dir }}
source /env/bin/activate && python manage.py migrate source /env/bin/activate && python manage.py migrate && python manage.py test
@endif @endif
@endtask @endtask

@ -1,42 +0,0 @@
import csv
import jwt
from django.conf import settings
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Добавляет 1 или нескольких юзеров в указанные группы'
def add_arguments(self, parser):
parser.add_argument(
'--from',
type=str,
dest='from',
help='Файл подгрузки данных'
)
parser.add_argument(
'--to',
type=str,
dest='to',
help='Файл выгрузки'
)
def handle(self, *args, **options):
from_path = options['from']
to_path = options['to']
with open(from_path) as f:
with open(to_path, 'w') as out_f:
fw = csv.writer(out_f)
fr = csv.reader(f, delimiter=';')
for row in fr:
email = row[0]
course_token = row[1]
period = row[2]
payload = {
'period': period,
'course_token': course_token,
'email': email.lower(),
}
token = jwt.encode(payload, settings.COURSE_PROGRESS_SECRET_KEY, algorithm='HS256').decode("utf-8")
url = "https://go.skillbox.ru/api/v1/progress/progress_token/?token=%s" % str(token)
fw.writerow([email.lower(), url])

@ -53,13 +53,6 @@ class Account(models.Model):
def __str__(self): def __str__(self):
return self.owner.email return self.owner.email
def get_phone(self):
try:
return '' if self.phone is None else ('' if self.phone.national_number else \
(self.phone.national_number if self.phone.country_code is None else str(self.phone)))
except AttributeError:
return self.phone
class Meta: class Meta:
verbose_name = 'Дополнительная информация о пользователе' verbose_name = 'Дополнительная информация о пользователе'
verbose_name_plural = 'Дополнительная информация о пользователе' verbose_name_plural = 'Дополнительная информация о пользователе'

@ -1,14 +1,14 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from rest_framework import serializers from rest_framework import serializers
from rest_framework.generics import get_object_or_404
from access.models.other import Account from access.models.other import Account
from achievements.serialers import DiplomaSerializer, AchievementsSerializer from achievements.serialers import DiplomaSerializer, AchievementsSerializer
from progress.serializers import SecureProgressSerializer, SupportProgressSerializer from progress.serializers import ProgressSerializer
from django.utils import timezone
class AccountSerializer(serializers.ModelSerializer): class AccountSerializer(serializers.ModelSerializer):
gender = serializers.SerializerMethodField() gender = serializers.SerializerMethodField()
phone = serializers.SerializerMethodField()
class Meta: class Meta:
model = Account model = Account
@ -18,21 +18,17 @@ class AccountSerializer(serializers.ModelSerializer):
def get_gender(self): def get_gender(self):
return self.get_gender_display() return self.get_gender_display()
@staticmethod
def get_phone(self):
return self.get_phone()
class UserSelfSerializer(serializers.ModelSerializer): class UserSelfSerializer(serializers.ModelSerializer):
account = serializers.SerializerMethodField() account = serializers.SerializerMethodField()
groups = serializers.SerializerMethodField() groups = serializers.SerializerMethodField()
progresses = serializers.SerializerMethodField() progress = serializers.SerializerMethodField()
diplomas = serializers.SerializerMethodField() diplomas = serializers.SerializerMethodField()
achievements = serializers.SerializerMethodField() achievements = serializers.SerializerMethodField()
class Meta: class Meta:
model = get_user_model() model = get_user_model()
fields = ('out_key', 'email', 'first_name', 'last_name', 'progresses', 'achievements', fields = ('out_key', 'email', 'first_name', 'last_name', 'progress', 'achievements',
'account', 'groups', 'is_staff', 'is_superuser', 'diplomas', 'is_active') 'account', 'groups', 'is_staff', 'is_superuser', 'diplomas', 'is_active')
@staticmethod @staticmethod
@ -52,9 +48,8 @@ class UserSelfSerializer(serializers.ModelSerializer):
return [group.name for group in self.groups.all()] return [group.name for group in self.groups.all()]
@staticmethod @staticmethod
def get_progresses(self): def get_progress(self):
return [SecureProgressSerializer(i).data for i in return [ProgressSerializer(i).data for i in self.progress_set.all()]
self.progress_set.filter(is_freeze=False).exclude(exp_date__lt=timezone.now())]
class UserProfileSerializer(serializers.ModelSerializer): class UserProfileSerializer(serializers.ModelSerializer):
@ -90,7 +85,7 @@ class UserSearchSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_phone(self): def get_phone(self):
return self.account.get_phone() return None if self.account.phone is None else self.account.phone.raw_input
@staticmethod @staticmethod
def get_pay(self): def get_pay(self):
@ -107,22 +102,3 @@ class UserSearchSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_last_request(self): def get_last_request(self):
return self.useractivity.last_request 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 self.account.get_phone()
@staticmethod
def get_progresses(self):
return [SupportProgressSerializer(i).data for i in self.progress_set.all()]

@ -5,10 +5,12 @@ from access import views
urlpatterns = [ urlpatterns = [
url(r'progress_detail/(?P<token>[0-9A-Fa-f-]+)/$', progress.views.CourseProgressUserView.as_view()), url(r'progress_detail/(?P<token>[0-9A-Fa-f-]+)/$', progress.views.CourseProgressUserView.as_view()),
url(r'progress_dynamic/(?P<token>[0-9A-Fa-f-]+)/$', progress.views.CourseProgressDynamicView.as_view()),
url(r'teachers/$', views.TeacherListView.as_view()), url(r'teachers/$', views.TeacherListView.as_view()),
url(r'detail/(?P<out_key>[0-9A-Fa-f-]+)/$', views.DetailUserView.as_view()), url(r'detail/(?P<out_key>[0-9A-Fa-f-]+)/$', views.DetailUserView.as_view()),
url(r'detail/$', 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'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'find/$', views.FindUserView.as_view()),
url(r'registration/$', views.RegistrationView.as_view()), url(r'registration/$', views.RegistrationView.as_view()),
url(r'change_password/$', views.ChangePasswordView.as_view()), url(r'change_password/$', views.ChangePasswordView.as_view()),
@ -16,6 +18,10 @@ urlpatterns = [
url(r'logout/$', views.LogoutView.as_view()), url(r'logout/$', views.LogoutView.as_view()),
url(r'reset/$', views.ResetPasswordView.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()), url(r'progress_detail/upload/(?P<token>[0-9A-Fa-f-]+)/$', progress.views.UploadCourseProgressUserView.as_view()),
url(r'management/password/$', views.ManagementPassword.as_view(), name='management-password') url(
r'management/password/$',
views.ManagementPassword.as_view(),
name='management-password'
)
] ]

@ -28,10 +28,7 @@ class TeacherListView(APIView):
status_code = 200 status_code = 200
def get(self, request): def get(self, request):
return Response([{ return Response([i.email for i in get_user_model().objects.filter(groups__name='teachers')], self.status_code)
'email': i.email,
'token': i.out_key,
} for i in get_user_model().objects.filter(groups__name='teachers')], self.status_code)
class ResetPasswordView(APIView): class ResetPasswordView(APIView):
@ -128,7 +125,6 @@ class FindUserView(APIView):
class DetailUserView(APIView): class DetailUserView(APIView):
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
permission_classes = (permissions.IsAuthenticated,)
@staticmethod @staticmethod
def post(request, out_key=None): def post(request, out_key=None):
@ -238,7 +234,7 @@ class ChangePasswordView(APIView):
@staticmethod @staticmethod
def post(request): def post(request):
if request.user.is_authenticated() and not request.user.check_password(request.JSON['old_password']): if request.user.is_authenticated() or not request.user.check_password(request.JSON['old_password']):
return Response("Неверный пароль", status=400) return Response("Неверный пароль", status=400)
request.user.set_password(request.JSON['new_password']) request.user.set_password(request.JSON['new_password'])
request.user.save() request.user.save()
@ -252,15 +248,21 @@ class LoginView(APIView):
def post(request): def post(request):
password = request.JSON.get('password') password = request.JSON.get('password')
email = request.JSON.get('email').lower() email = request.JSON.get('email').lower()
user = None
try:
user = get_user_model().objects.get(email=email)
except get_user_model().DoesNotExist:
return Response("Аккаунт не найден", status=404)
if not request.user.is_authenticated(): if not request.user.is_authenticated():
if not password == settings.MASTER_PASSWORD and not auth.authenticate(email=email, password=password): if not password == settings.MASTER_PASSWORD:
return Response("Неверный пароль", status=403) 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("Аккаунт не найден", status=404)
try: try:
auth.login(request, user) auth.login(request, user)

@ -1,31 +0,0 @@
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Команда для тестов'
def handle(self, *args, **options):
from xml.dom import minidom
xx = """<?xml version="1.0" encoding="UTF-8"?>
<repeatCardPaymentResponse status="3" error="112" processedDT="2018-04-23T19:37:04.544+03:00" techMessage="Неверное значение параметра invoiceId" clientOrderId="unknown"/>"""
dom = minidom.parseString(xx)
dom.normalize()
node1 = dom.getElementsByTagName("repeatCardPaymentResponse")[0]
print(node1)
print(node1.getAttribute('error'))
print(node1.getAttribute('processedDT'))
print(node1.getAttribute('techMessage'))
# def handle(self, *args, **options):
# from finance.loggers import FinanceLogger
# logger = FinanceLogger()
# try:
# 10 / 0
# except Exception as exc:
# logger.exception('FinanceLogger test log record', extra={'asdf': 3246523},
# invoice_id=42, some_param={'a': 1, 'b': 3, 'привет': 'медвед'}
# )
#

@ -0,0 +1,5 @@
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

@ -1,5 +0,0 @@
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

@ -1,23 +0,0 @@
# -*- 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,6 +58,8 @@ class Lesson(models.Model):
class Topic(models.Model): class Topic(models.Model):
course = models.ForeignKey(to="Course", verbose_name='курс') course = models.ForeignKey(to="Course", verbose_name='курс')
title = models.CharField(verbose_name='Название', max_length=255) 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='Поле сортировки') sort = models.SmallIntegerField(verbose_name='Поле сортировки')
def __str__(self): def __str__(self):
@ -72,13 +74,13 @@ class Topic(models.Model):
class CourseManager(models.Manager): class CourseManager(models.Manager):
def update_or_create_course(self, image=None, big_image=None, statistic=None, old_slug=None, def update_or_create_course(self, image=None, big_image=None, statistic=None,
big_mobile_image=None, slug=None, teacher_tokens=None, big_mobile_image=None, slug=None, teachers=None,
level=None, direction=None, **kwargs): level=None, direction=None, **kwargs):
slug = slug if slug else slugify(unidecode.unidecode(kwargs['title'])) slug = slug if slug else slugify(unidecode.unidecode(kwargs['title']))
kwargs['teacher_tokens'] = teacher_tokens kwargs['teacher_tokens'] = teachers
if image: if image:
path = 'course/image%s.png' % slug path = 'course/image%s.png' % slug
@ -99,14 +101,13 @@ class CourseManager(models.Manager):
kwargs['level'] = get_real_name(COURSE_LEVEL, level) kwargs['level'] = get_real_name(COURSE_LEVEL, level)
if direction: if direction:
kwargs['direction'] = get_real_name(COURSE_DIRECTION, direction[0]) kwargs['direction'] = get_real_name(COURSE_DIRECTION, direction)
try: try:
course = self.get(slug=old_slug) course = self.get(slug=slug)
for i in kwargs: for i in kwargs:
if kwargs[i]: if kwargs[i]:
setattr(course, i, kwargs[i]) setattr(course, i, kwargs[i])
course.slug = slug
course.save() course.save()
except ObjectDoesNotExist: except ObjectDoesNotExist:
@ -153,26 +154,19 @@ class Course(models.Model):
lesson_list += list(topic.lesson_set.all()) lesson_list += list(topic.lesson_set.all())
return lesson_list return lesson_list
def get_next(self, lesson: Lesson, f=None) -> Lesson: def get_next(self, lesson: Lesson) -> Lesson:
lessons = self.get_lesson_list() lessons = self.get_lesson_list()
try: try:
n = lessons[lessons.index(lesson)+1] return lessons[lessons.index(lesson)]
if f is None or f(n):
return n
else:
return self.get_next(n, f)
except IndexError: except IndexError:
pass pass
def get_previous(self, lesson: Lesson, f=None): def get_previous(self, lesson: Lesson):
lessons = self.get_lesson_list() lessons = self.get_lesson_list()
idx = lessons.index(lesson) - 1 try:
if idx > -1: return lessons[lessons.index(lesson) - 2]
prev = lessons[idx] except IndexError:
if f is None or f(prev): pass
return prev
else:
return self.get_previous(prev, f)
def get_first_lesson(self) -> Lesson: def get_first_lesson(self) -> Lesson:
lessons = self.get_lesson_list() lessons = self.get_lesson_list()

@ -1,8 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
from django.conf import settings
from courses.models import Course, Lesson, Topic from courses.models import Course, Lesson, Topic
import os
class TopicSerializer(serializers.ModelSerializer): class TopicSerializer(serializers.ModelSerializer):
@ -21,30 +19,15 @@ class MiniLessonSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Lesson model = Lesson
fields = ('title', 'free', 'token', 'is_hm', 'sort') fields = ('title', 'free', 'token', 'is_hm')
class LessonSerializer(MiniLessonSerializer): class LessonSerializer(MiniLessonSerializer):
course_slug = serializers.SerializerMethodField()
materials = serializers.SerializerMethodField()
class Meta: class Meta:
model = Lesson model = Lesson
exclude = ('id', 'topic', 'key') exclude = ('id', 'topic', 'key')
@staticmethod
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): class TeacherLessonSerializer(MiniLessonSerializer):
topic_sort = serializers.SerializerMethodField() topic_sort = serializers.SerializerMethodField()

@ -1,13 +0,0 @@
from progress.models import ProgressLesson, Progress
from django.contrib.auth import get_user_model
def add_lesson(user_out_key: str, course_token: str, lesson_token: str, teacher_key: str, is_hm: bool):
p = Progress.objects.get(course_token=course_token, user__out_key=user_out_key)
ProgressLesson.objects.get_or_create(
progress=p,
lesson_token=lesson_token,
checker=get_user_model().objects.get(out_key=(teacher_key if is_hm else user_out_key)),
)

@ -3,13 +3,9 @@ from django.conf.urls import url
from courses import views as views from courses import views as views
urlpatterns = [ urlpatterns = [
url(r'lesson/update/$', views.UpdateLessonView.as_view()), url(r'vertex/(?P<token>.+)/$', views.LessonDetail.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/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'tree/(?P<slug>.+)/$', views.TreeView.as_view()),
url(r'detail/(?P<slug>.+)/$', views.CourseDetailView.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()), url(r'^$', views.CourseListView.as_view()),
] ]

@ -1,15 +1,11 @@
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.renderers import JSONRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView 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
import jwt from progress.models import ProgressLesson
from courses.tasks import add_lesson
from lms import settings
class TreeView(APIView): class TreeView(APIView):
@ -30,6 +26,27 @@ class CourseListView(APIView):
status_code = 200 status_code = 200
def post(self, request): 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()) course = Course.objects.update_or_create_course(**request.JSON.dict())
return Response(CourseDetailSerializer(course).data, status=self.status_code) return Response(CourseDetailSerializer(course).data, status=self.status_code)
@ -58,141 +75,6 @@ class CourseDetailView(APIView):
return Response(CourseDetailSerializer(Course.objects.get(slug=slug)).data, self.status_code) 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): class LessonInfoView(APIView):
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
status_code = 200 status_code = 200
@ -202,7 +84,7 @@ class LessonInfoView(APIView):
lesson = Lesson.objects.get(token=token) lesson = Lesson.objects.get(token=token)
except Lesson.DoesNotExist: except Lesson.DoesNotExist:
return Response('Урок не найден', status=404) return Response('Урок не найден', status=404)
if request.user.is_authenticated: if request.user.is_authenticated and request.user.out_key in lesson.topic.course.teacher_tokens:
return Response(TeacherLessonSerializer(lesson).data, self.status_code) return Response(TeacherLessonSerializer(lesson).data, self.status_code)
return Response("Пользователь не является преподователем по курсу", status=403) return Response("Пользователь не является преподователем по курсу", status=403)
@ -211,53 +93,33 @@ class LessonDetail(APIView):
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
@staticmethod @staticmethod
def post(request, token): def get(request, token):
jwt_token = request.JSON.get('jwt_token', None)
try: try:
lesson = Lesson.objects.get(token=token) lesson = Lesson.objects.get(token=token)
except Lesson.DoesNotExist: except Lesson.DoesNotExist:
return Response("Урока не существует", status=404) return Response("Lesson doesn't exist", status=404)
l = LessonSerializer(lesson).data if not lesson.free and not ProgressLesson.objects.filter(lesson_token=lesson.token).exists():
previous_lesson = lesson.topic.course.get_previous(lesson)
try:
payload = None if jwt_token is None \ if not previous_lesson is None or not ProgressLesson.objects.filter(
else jwt.decode(jwt_token, settings.COURSE_PROGRESS_SECRET_KEY, algorithms=['HS256']) lesson_token=previous_lesson.token, status=ProgressLesson.STATUSES.done).exists():
except DecodeError: return Response("Lesson doesn't access", status=403)
payload = None
# TODO: Доделать систему прав на курс
course = lesson.topic.course
res = LessonSerializer(lesson).data
if payload is None: # progress = vertex.course.progress_set.filter(user=request.user)
if not (lesson.free or request.user.is_authenticated and request.user.is_staff): # try:
return Response("Bad token", status=400) # if progress.exists():
# next_vertex = vertex.get_next(progress[0].get_template())
else: # if next_vertex:
return Response(l, status=200) # res['next'] = MiniVertexSerializer(next_vertex).data
# res['is_in_progress'] = vertex in progress[0].get_objects_in_progress()
prev_lesson = course.get_previous(lesson, (lambda x: not x.is_hm) if payload['only_watch'] else None) # else:
next_lesson = course.get_next(lesson, (lambda x: not x.is_hm) if payload['only_watch'] else None) # res['next'] = MiniVertexSerializer(vertex.get_next(vertex.course.route)).data
# except Thread.DoesNotExist or Vertex.DoesNotExist:
if not prev_lesson is None: # res['next'] = MiniVertexSerializer(vertex.get_next(vertex.course.route)).data
l['prev_token'] = prev_lesson.token
return Response(res, status=200)
if not next_lesson is None:
l['next_token'] = next_lesson.token
new_lesson = False
for payload_lesson in payload['lessons']:
if payload_lesson['lesson_token'] == str(lesson.token):
return Response(l, status=200)
if not prev_lesson is None and str(prev_lesson.token) == payload_lesson['lesson_token']:
new_lesson = True if prev_lesson is None else \
(payload_lesson['status'] == "done" or payload_lesson['status'] == "wait")
if not new_lesson:
return Response("Permission denied", status=403)
# TODO Задача для селери
add_lesson(request.user.out_key, course.token, lesson.token, course.get_teacher(), lesson.is_hm)
return Response(l, status=200)

@ -1,13 +1,7 @@
# coding=utf-8 # coding=utf-8
from django.contrib import admin from django.contrib import admin
from finance.models import Bill, Invoice, InvoiceRebilling from finance.models import Bill, Invoice
class InvoiceAdmin(admin.ModelAdmin):
list_display = ('__str__', 'rebilling_on',)
admin.site.register(Bill) admin.site.register(Bill)
admin.site.register(Invoice) admin.site.register(Invoice)
admin.site.register(InvoiceRebilling, InvoiceAdmin)

@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
import logging
_logger = logging.getLogger('finance_data')
class FinanceLogger:
"""
Все kwargs попадют в %(finance_data)s и логируются
'format': '%(asctime)s - %(levelname)s - %(message)s - %(finance_data)s'
"""
def __init__(self, prefix=None):
self.prefix = prefix
def log(self, level, msg, *args, **kwargs):
_logger.log(level=level, msg=self._get_msg(msg), *args, **self._make_kwargs(kwargs))
def _get_msg(self, msg):
if self.prefix:
msg = '{}: {}'.format(self.prefix, msg)
return msg
def _make_kwargs(self, kwargs):
new_kwargs = {}
for inspected_kwarg in ('exc_info', 'stack_info', 'extra'):
try:
new_kwargs[inspected_kwarg] = kwargs.pop(inspected_kwarg)
except KeyError:
pass
if 'extra' in new_kwargs:
new_kwargs['extra']['finance_data'] = kwargs
else:
new_kwargs['extra'] = dict(finance_data=kwargs)
return new_kwargs
def debug(self, msg, *args, **kwargs):
self.log(level=logging.DEBUG, msg=msg, *args, **kwargs)
def info(self, msg, *args, **kwargs):
self.log(level=logging.INFO, msg=msg, *args, **kwargs)
def warning(self, msg, *args, **kwargs):
self.log(level=logging.WARNING, msg=msg, *args, **kwargs)
# TODO отделить логирование ошибок в другой лог
def error(self, msg, *args, **kwargs):
self.log(level=logging.ERROR, msg=msg, *args, **kwargs)
def critical(self, msg, *args, **kwargs):
self.log(level=logging.CRITICAL, msg=msg, *args, **kwargs)
def exception(self, msg, *args, **kwargs):
kwargs['stack_info'] = True
_logger.exception(self._get_msg(msg), *args, **self._make_kwargs(kwargs))

@ -1,14 +0,0 @@
from django.core.management.base import BaseCommand
from finance.loggers import FinanceLogger
from finance.tasks import periodic_billing
finance_logger = FinanceLogger() # prefix='YandexMoney'
class Command(BaseCommand):
def handle(self, *args, **options):
finance_logger.info("start console repeat payment command")
print('Started')
periodic_billing()

@ -1,20 +0,0 @@
# -*- 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='Полученная сумма'),
),
]

@ -1,22 +0,0 @@
# -*- 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,
),
]

@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-29 13:46
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('finance', '0004_bill_date'),
]
operations = [
migrations.CreateModel(
name='InstallmentPlan',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('invoice_amount', models.IntegerField(verbose_name='Количество платежей')),
('price', models.IntegerField(verbose_name='Цена одного платежа')),
('bill', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='finance.Bill', verbose_name='Связный счёт')),
],
options={
'verbose_name': 'Рассрочка',
'verbose_name_plural': 'Рассрочки',
},
),
migrations.AlterField(
model_name='invoice',
name='date',
field=models.DateTimeField(auto_now_add=True, verbose_name='Дата создания'),
),
]

@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-30 11:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('finance', '0005_auto_20180329_1346'),
]
operations = [
migrations.RemoveField(
model_name='installmentplan',
name='bill',
),
migrations.AddField(
model_name='invoice',
name='rebilling',
field=models.BooleanField(default=False, editable=False, verbose_name='Повторный платеж'),
),
migrations.AddField(
model_name='invoice',
name='rebilling_on',
field=models.BooleanField(default=False, editable=False, verbose_name='Повторять платеж'),
),
migrations.AlterField(
model_name='invoice',
name='date',
field=models.DateTimeField(auto_now_add=True),
),
migrations.DeleteModel(
name='InstallmentPlan',
),
]

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-30 14:52
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('finance', '0006_auto_20180330_1121'),
]
operations = [
migrations.RemoveField(
model_name='invoice',
name='rebilling',
),
migrations.RemoveField(
model_name='invoice',
name='rebilling_on',
),
]

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-30 14:52
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('finance', '0007_auto_20180330_1452'),
]
operations = [
migrations.CreateModel(
name='InvoiceRebilling',
fields=[
('invoice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='finance.Invoice')),
('rebilling_on', models.BooleanField(default=False, editable=False, verbose_name='Повторять платеж')),
],
options={
'verbose_name': 'Повторный платёж',
'verbose_name_plural': 'Повторные платежи',
},
bases=('finance.invoice',),
),
]

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-03-30 15:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('finance', '0008_invoicerebilling'),
]
operations = [
migrations.AddField(
model_name='invoicerebilling',
name='pay_count',
field=models.SmallIntegerField(default=2, editable=False, verbose_name='Всего платежей'),
preserve_default=False,
),
]

@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-04-12 16:28
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('finance', '0009_invoicerebilling_pay_count'),
]
operations = [
migrations.RemoveField(
model_name='invoicerebilling',
name='pay_count',
),
migrations.AddField(
model_name='bill',
name='freeze',
field=models.BooleanField(default=False, verbose_name='Отказ от платежей'),
),
migrations.AddField(
model_name='invoice',
name='date_of_payment',
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата фактической оплаты'),
),
migrations.AddField(
model_name='invoice',
name='expected_date',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Ожидаемая дата платежа'),
),
migrations.AlterField(
model_name='invoice',
name='date',
field=models.DateTimeField(auto_now_add=True, verbose_name='Дата создания платежа'),
),
]

@ -1,17 +1,9 @@
# coding=utf-8 # coding=utf-8
from dateutil.relativedelta import relativedelta
from django.conf import settings from django.conf import settings
from django.core.mail import EmailMessage
from django.db import models from django.db import models
from django.utils import timezone
from yandex_money.models import Payment from yandex_money.models import Payment
import logging
from progress.models import Progress
logger_business_rules = logging.getLogger('business_rules') from courses.models import Course, Lesson
class Bill(models.Model): class Bill(models.Model):
@ -21,85 +13,13 @@ class Bill(models.Model):
comment = models.TextField(verbose_name='Комментарий продавца', help_text='Будет показано пользователю', comment = models.TextField(verbose_name='Комментарий продавца', help_text='Будет показано пользователю',
blank=True, editable=False) blank=True, editable=False)
description = models.TextField(verbose_name='Внутренняя заметка', blank=True) description = models.TextField(verbose_name='Внутренняя заметка', blank=True)
date = models.DateTimeField(verbose_name="Дата выставления", auto_now_add=True)
freeze = models.BooleanField(verbose_name='Отказ от платежей', default=False)
def __str__(self): def __str__(self):
return '%s: %s' % (self.id, self.user) return '%s: %s' % (self.id, self.user)
def freeze_course(self):
if self.invoice_set.exclude(status='F').exists():
log = False
try:
p = Progress.objects.get(user=self.user, course_token=str(self.course_token))
p.is_freeze = True
p.save()
except Progress.DoesNotExist:
log = True
if log:
logger_business_rules.info('Отказ от платежей прошёл успешно', exc_info=True, extra={
'description': 'The privileges were not taken away, as they were not granted',
'user': self.user,
})
msg = EmailMessage(
'Вы откозались от оплаты по счёту',
"""Вы откозались от оплаты по счёту.
Вы сможете продолжить оплату в личном кабинете""",
to=[self.user.email],
bcc=[self.opener.email],
reply_to=[self.opener.email],
)
msg.send()
self.freeze = True
self.save()
else:
logger_business_rules.warning('Попытка нарушения правила отказа от платежей', exc_info=True, extra={
'description': 'All payments already paid',
'user': self.user,
})
def unfreeze_course(self, force=False):
if force or self.invoice_set.exclude(status='F')\
.filter(expected_date__lt=timezone.now() + relativedelta(days=1)).exists():
if self.invoice_set.filter(status='F').exclude(expected_date__lt=timezone.now()).exists():
try:
p = Progress.objects.get(user=self.user, course_token=str(self.course_token))
p.is_freeze = False
p.save()
except Progress.DoesNotExist:
pass
msg = EmailMessage(
'Вы возобновили оплату по счёту',
"""Вы возобновили оплату по счёту.""",
to=[self.user.email],
bcc=[self.opener.email],
reply_to=[self.opener.email],
)
msg.send()
self.freeze = False
self.save()
else:
logger_business_rules.warning('Попытка нарушения правила возобновления платежей', exc_info=True, extra={
'description': 'Excepted date more than one day',
'user': self.user,
})
def get_full_price(self): def get_full_price(self):
return sum([i.price for i in self.invoice_set.all() if not i.price is None]) 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: class Meta:
verbose_name = 'Счет' verbose_name = 'Счет'
verbose_name_plural = 'Счета' verbose_name_plural = 'Счета'
@ -123,8 +43,8 @@ class Invoice(models.Model):
('C', 'Отклонен'), ('C', 'Отклонен'),
) )
status = models.CharField(verbose_name='Статус', max_length=1, default='W', choices=BILL_STATUSES) status = models.CharField(verbose_name='Статус', max_length=1, default='W', choices=BILL_STATUSES)
price = models.IntegerField(verbose_name='Сумма', editable=False, null=True, blank=True) #Todo На самом деле тут не далжно быть значений null price = models.IntegerField(verbose_name='Сумма', editable=False, null=True, blank=True)
real_price = models.FloatField(verbose_name='Полученная сумма', null=True, blank=True, real_price = models.IntegerField(verbose_name='Полученная сумма', null=True, blank=True,
help_text='Сумма, минус комиссия', editable=False) help_text='Сумма, минус комиссия', editable=False)
method = models.CharField(verbose_name='Способ оплаты', max_length=2, default='Y', choices=BILL_METHOD) 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) key = models.CharField(verbose_name='Ключ платежа', max_length=255, editable=False, blank=True)
@ -133,24 +53,7 @@ class Invoice(models.Model):
blank=True, editable=False) blank=True, editable=False)
bill = models.ForeignKey(to=Bill, verbose_name="Связный счёт") bill = models.ForeignKey(to=Bill, verbose_name="Связный счёт")
is_open = models.BooleanField(default=True, verbose_name="Открывает ли платёж курс") is_open = models.BooleanField(default=True, verbose_name="Открывает ли платёж курс")
date = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания платежа") date = models.DateTimeField(auto_now_add=True)
expected_date = models.DateTimeField(default=timezone.now, verbose_name="Ожидаемая дата платежа")
date_of_payment = models.DateTimeField(verbose_name="Дата фактической оплаты", blank=True, null=True)
def get_comment(self):
return '''Вам выставлен счёт,''' if \
self.comment == "" else self.comment
def send_link(self):
msg = EmailMessage(
'Вам выставлен новый счёт',
"""%s для оплаты перейдите по ссылке
%s/api/v1/finance/payment/%s/""" % (self.get_comment(), settings.DOMAIN, self.yandex_pay.id),
to=[self.yandex_pay.cps_email],
bcc=[self.bill.opener.email],
reply_to=[self.bill.opener.email],
)
msg.send()
def __str__(self): def __str__(self):
return '%s:%s %s' % (self.id, self.get_status_display(), self.bill.user) return '%s:%s %s' % (self.id, self.get_status_display(), self.bill.user)
@ -158,26 +61,3 @@ class Invoice(models.Model):
class Meta: class Meta:
verbose_name = 'Платёж' verbose_name = 'Платёж'
verbose_name_plural = 'Платежи' verbose_name_plural = 'Платежи'
class InvoiceRebilling(Invoice):
rebilling_on = models.BooleanField(verbose_name='Повторять платеж', default=False, editable=False)
def create_child_pays(self, count):
# TODO Заменить expected_date
for idx in range(1, int(count)):
InvoiceRebilling.objects.create(
bill=self.bill,
comment=self.comment,
method=self.method,
status='W',
is_open=self.is_open,
price=self.price,
rebilling_on=False,
key=self.key,
expected_date=(timezone.now() + relativedelta(months=idx)),
)
class Meta:
verbose_name = 'Повторный платёж'
verbose_name_plural = 'Повторные платежи'

@ -28,18 +28,18 @@ class BillSerializer(serializers.ModelSerializer):
class InvoiceSerializer(serializers.ModelSerializer): class InvoiceSerializer(serializers.ModelSerializer):
status = serializers.SerializerMethodField() status = serializers.SerializerMethodField()
method = serializers.SerializerMethodField() method = serializers.SerializerMethodField()
yandex_pay = serializers.SerializerMethodField() yandex_pay_id = serializers.SerializerMethodField()
class Meta: class Meta:
model = Invoice model = Invoice
fields = '__all__' exclude = ('bill',)
@staticmethod @staticmethod
def get_status(self): def get_status(self):
return self.get_status_display() return self.get_status_display()
@staticmethod @staticmethod
def get_yandex_pay(self): def get_yandex_pay_id(self):
return None if self.yandex_pay is None else self.yandex_pay.id return None if self.yandex_pay is None else self.yandex_pay.id
@staticmethod @staticmethod

@ -16,6 +16,18 @@ def invoice_signal(instance, **kwargs):
course = Course.objects.get(token=instance.bill.course_token) course = Course.objects.get(token=instance.bill.course_token)
if instance.yandex_pay and instance.method == 'Y' and instance.status == 'P':
msg = EmailMessage(
'Вам выставлен новый счёт',
'''Вам выставлен счёт, для оплаты перейдите по ссылке
%s/api/v1/finance/payment/%s/''' % (settings.DOMAIN, instance.yandex_pay.id,),
'robo@skillbox.ru',
[instance.yandex_pay.cps_email],
[instance.bill.opener.email],
reply_to=[instance.bill.opener.email],
)
msg.send()
if instance.status == 'F': if instance.status == 'F':
if instance.is_open: if instance.is_open:
try: try:
@ -24,7 +36,7 @@ def invoice_signal(instance, **kwargs):
user=instance.bill.user, user=instance.bill.user,
) )
except Progress.DoesNotExist: except Progress.DoesNotExist:
p = Progress.objects.create( p=Progress.objects.create(
course_token=instance.bill.course_token, course_token=instance.bill.course_token,
user=instance.bill.user, user=instance.bill.user,
teacher=get_user_model().objects.get(out_key=course.get_teacher()) teacher=get_user_model().objects.get(out_key=course.get_teacher())
@ -40,8 +52,9 @@ def invoice_signal(instance, **kwargs):
'''Вам открыт доступ к курсу "%s", вы можете перейти по ссылке и '''Вам открыт доступ к курсу "%s", вы можете перейти по ссылке и
ознакомиться с материалами %s/course/%s''' ознакомиться с материалами %s/course/%s'''
% (course.title, settings.DOMAIN, course.slug), % (course.title, settings.DOMAIN, course.slug),
to=[instance.bill.user.email], 'robo@skillbox.ru',
bcc=[instance.bill.opener.email], [instance.bill.user.email],
cc=[instance.bill.opener.email],
reply_to=[instance.bill.opener.email], reply_to=[instance.bill.opener.email],
) )
else: else:
@ -50,7 +63,7 @@ def invoice_signal(instance, **kwargs):
'''Курс "%s" был забронирован''' % course.title, '''Курс "%s" был забронирован''' % course.title,
'robo@skillbox.ru', 'robo@skillbox.ru',
[instance.bill.user.email], [instance.bill.user.email],
bcc=[instance.bill.opener.email], cc=[instance.bill.opener.email],
reply_to=[instance.bill.opener.email], reply_to=[instance.bill.opener.email],
) )
msg.send() msg.send()

@ -1,81 +0,0 @@
import logging
import os
import requests
from django.conf import settings
from django.db import transaction
from django.utils import timezone
from yandex_money.models import Payment
from finance.loggers import FinanceLogger
from finance.models import InvoiceRebilling, Invoice
from lms import celery_app
finance_logger = FinanceLogger()
@celery_app.task
def periodic_billing():
finance_logger.info("start periodic billing task")
# TODO заюзать Invoice.BILL_STATUSES
invoices = InvoiceRebilling.objects.filter(method='Y', status='W')
# TODO тут был exclude('F') то есть все неоплаченные... но это не верно!
for invoice in invoices.filter(expected_date__lt=timezone.now()):
# выбираем все необработанные из прошлого
with transaction.atomic():
try:
_yandex_repeat_card_payment(invoice)
except Exception as exc:
finance_logger.exception('Yandex Money repeatCardPayment Exception', invoice_id=invoice.id)
invoice.comment += 'Yandex Money: Ошибка при попытке повторного платежа, свяжитесь с клиентской службой'
invoice.save()
def _yandex_repeat_card_payment(invoice):
user = invoice.bill.user
yandex_pay = Payment.objects.create(
order_amount=invoice.price,
customer_number=user.id,
user=user,
cps_email=user.email,
shop_id=settings.YANDEX_MONEY_REBILLING_SHOP_ID,
scid=settings.YANDEX_MONEY_REBILLING_SCID
)
invoice.yandex_pay = yandex_pay
finance_logger.info('YandexMoney repeatCardPayment start', invoice_id=invoice.id)
resp = requests.post(
url=settings.YANDEX_MONEY_MWS_URL + 'repeatCardPayment',
data={
'clientOrderId': invoice.id, # уникальное возрастающее целое число
'invoiceId': invoice.key,
'amount': invoice.price,
'orderNumber': invoice.yandex_pay.order_number
},
cert=(
os.path.join(settings.SSL_ROOT, 'skillbox.cer'),
os.path.join(settings.SSL_ROOT, 'skillbox.key')
),
verify=os.path.join(settings.SSL_ROOT, 'yamoney_chain.cer')
)
resp_text = resp.text
finance_logger.info('YandexMoney repeatCardPayment response',
invoice_id=invoice.id, response=resp_text, code=resp.status_code, )
try:
_check_yandex_response_status(invoice, resp_text)
except Exception as exc:
finance_logger.exception("Can't parse yandex response", invoice_id=invoice.id, response=resp_text)
def _check_yandex_response_status(invoice, resp_text):
from xml.dom import minidom
dom = minidom.parseString(resp_text)
dom.normalize()
resp_node = dom.getElementsByTagName("repeatCardPaymentResponse")[0]
status = resp_node.getAttribute('status')
if status != '0':
error = resp_node.getAttribute('error')
processed_dt = resp_node.getAttribute('processedDT')
tech_message = resp_node.getAttribute('techMessage')
invoice.status = Invoice.BILL_STATUSES[-1][0]
invoice.comment += 'Yandex Money: ошибка № {}, сообщение {} от {}'.format(error, tech_message, processed_dt)

@ -2,14 +2,10 @@ from django.conf.urls import url
from finance import views from finance import views
urlpatterns = [ urlpatterns = [
url(r'payment/([0-9]{1,99})/$', views.YandexPay.as_view()),
url(r'bill/([0-9]{1,99})/freeze/$', views.FreezeView.as_view()),
url(r'bill/([0-9]{1,99})/unfreeze/$', views.UnFreezeView.as_view()),
url(r'bills/$', views.BillListView.as_view()),
url(r'bills/([0-9]{1,99})/$', views.BillDetailView.as_view()), url(r'bills/([0-9]{1,99})/$', views.BillDetailView.as_view()),
url(r'payment/([0-9]{1,99})/$', views.YandexPay.as_view()),
url(r'bills_find/$', views.FindBillView.as_view()), url(r'bills_find/$', views.FindBillView.as_view()),
url(r'bills/$', views.BillListView.as_view()),
url(r'yandex/fail/$', views.YandexFailView.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), url(r'invoices/$', views.get_invoices),
url(r'test/pay/$', views.test_pay),
] ]

@ -1,73 +1,26 @@
import csv import csv
import uuid import logging
import datetime
import dicttoxml
import requests import requests
from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.mail import EmailMessage
from django.core.mail import EmailMultiAlternatives
from django.db import IntegrityError
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse, HttpResponseForbidden from django.http import HttpResponse, HttpResponseForbidden
from django.shortcuts import redirect, render_to_response from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.html import strip_tags
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from yandex_money.models import Payment from yandex_money.models import Payment
from django.conf import settings
from courses.api import CourseParamsApi from courses.api import CourseParamsApi
from courses.models import Course from finance.models import Bill, Invoice
from finance.loggers import FinanceLogger
from finance.models import Bill, Invoice, InvoiceRebilling
from finance.serializers import BillSerializer, InvoiceSerializer from finance.serializers import BillSerializer, InvoiceSerializer
from lms.global_decorators import transaction_decorator from lms.global_decorators import transaction_decorator
from lms.tools import get_real_name from lms.tools import get_real_name
finance_logger = FinanceLogger() # prefix='YandexMoney' logger_yandex = logging.getLogger('yandex_money')
def test_pay(request):
return render_to_response('yandex/test_pay.html', context={
'shop_id': settings.YANDEX_MONEY_SHOP_ID,
'sc_id': settings.YANDEX_MONEY_TEST_SCID,
'order_number': str(uuid.uuid4())[:12],
'user_email': request.user.email if request.user.is_authenticated else 'user@domain.com'
})
class FreezeView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def post(request, pk):
try:
bill = Bill.objects.get(id=pk)
except Bill.DoesNotExist:
return Response("Счёт не найден", status=404)
if request.user.is_authenticated and request.user.email == bill.user.email:
bill.freeze_course()
return Response(status=204)
return Response("Permission denied", status=403)
class UnFreezeView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def post(request, pk):
try:
bill = Bill.objects.get(id=pk)
except Bill.DoesNotExist:
return Response("Счёт не найден", status=404)
if request.user.is_authenticated and request.user.email == bill.user.email:
bill.unfreeze_course()
return Response(status=204)
return Response("Permission denied", status=403)
class BillListView(APIView): class BillListView(APIView):
@ -86,164 +39,52 @@ class BillListView(APIView):
def post(self, request): def post(self, request):
if request.user.is_authenticated and (request.user.groups.filter(name__in=['managers','lead_managers']).exists() if request.user.is_authenticated and (request.user.groups.filter(name__in=['managers','lead_managers']).exists()
or request.user.is_superuser): or request.user.is_superuser):
user = get_user_model().objects.get(email=request.JSON.get('user')) bill = request.JSON.get('bill')
opener = get_user_model().objects.get(email=request.JSON.get('opener')) children = request.JSON.get('children', [])
description = request.JSON.get('description', None) bill_kwarg = dict()
comment = request.JSON.get('comment', None)
course_token = request.JSON.get('course_token', None) if bill:
bill_kwarg['user'] = get_user_model().objects.get(email=bill['user'])
if course_token is None: bill_kwarg['opener'] = get_user_model().objects.get(email=bill['opener'])
return Response("Идентификатор курса не передан", status=400) bill_kwarg['description'] = bill['description']
bill_kwarg['comment'] = bill['comment']
try: bill_kwarg['course_token'] = bill['course_token']
bill_obj = Bill.objects.get(user=user, course_token=course_token)
except Bill.DoesNotExist: bill_obj, is_create = Bill.objects.update_or_create(**bill_kwarg)
try: invoices = bill_obj.invoice_set.all()
bill_obj = Bill.objects.create(user=user, course_token=course_token)
except IntegrityError: for i in children:
return Response("У пользователя уже есть счёт на этот курс", status=400) i['method'] = get_real_name(elem=i['method'], array=Invoice.BILL_METHOD)
i['status'] = get_real_name(elem=i['status'], array=Invoice.BILL_STATUSES)
bill_obj.opener = bill_obj.opener if opener is None else opener i['bill'] = bill_obj
bill_obj.description = bill_obj.description if description is None else description i['yandex_pay'] = None
bill_obj.comment = bill_obj.comment if comment is None else comment invoice, _is_create = Invoice.objects.update_or_create(**i)
bill_obj.save() if i['method'] == 'Y':
yandex_pay, _is_create = Payment.objects.get_or_create(
return Response(bill_obj.id, status=200) order_amount=i['price'],
order_number=invoice.id,
return Response("Ошибка доступа, возможно вы разлогинились из другой вкладки браузера", status=403) shop_amount=0,
customer_number=bill_obj.user.id,
user=bill_obj.user,
class InvoiceDetailView(APIView): cps_email=bill_obj.user.email,
renderer_classes = (JSONRenderer,) )
invoice.yandex_pay = yandex_pay
@staticmethod invoice.save()
def delete(request, invoice_id):
invoices = [j for j in invoices if not j.id == invoice.id]
try:
i = Invoice.objects.get(id=invoice_id) [i.delete() for i in invoices]
if not i.status == "F" and not (i.bill.check_pay() and i.is_open):
i.delete() res = {
except Invoice.DoesNotExist: "bill": BillSerializer(bill_obj).data,
pass "children": [InvoiceSerializer(i).data for i in bill_obj.invoice_set.all()],
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)
pay_count = int(request.JSON.get('pay_count', '1'))
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)
if pay_count > 1:
invoice = InvoiceRebilling.objects.create(
bill=bill,
method=method,
status=status,
is_open=is_open,
rebilling_on=True,
)
else:
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":
# TODO это никогда не выполнится
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 pay_count > 1:
invoice.create_child_pays(pay_count)
if invoice.method == 'Y' and invoice.yandex_pay is None:
yandex_pay = Payment(
order_amount=invoice.price,
shop_amount=0,
customer_number=bill.user.id,
user=bill.user,
cps_email=bill.user.email,
)
if pay_count > 1:
yandex_pay.shop_id = settings.YANDEX_MONEY_REBILLING_SHOP_ID
yandex_pay.scid = settings.YANDEX_MONEY_REBILLING_SCID
yandex_pay.save()
invoice.yandex_pay = yandex_pay
invoice.send_link()
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, to = 'Выставлен новый счёт', invoice.bill.opener.email return Response(res, status=200)
html_content = render_to_string('mail/sales/back_set_bill.html', context)
text_content = strip_tags(html_content)
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("Bill not set", status=400)
return Response(InvoiceSerializer(invoice).data, status=200) return Response("Course detail access only for manager users", status=403)
return Response("Invoice detail access only for manager users", status=403)
class BillDetailView(APIView): class BillDetailView(APIView):
@ -284,9 +125,7 @@ class FindBillView(APIView):
if key: if key:
res = Bill.objects.filter( res = Bill.objects.filter(
Q(opener__email__contains=key.lower()) Q(opener__email__contains=key.lower()) | Q(user__email__contains=key.lower())
| Q(user__email__contains=key.lower())
| Q(id__contains=key)
) )
else: else:
@ -309,10 +148,6 @@ class YandexPay(APIView):
def get(request, pk): def get(request, pk):
try: try:
pay = Payment.objects.get(id=pk) pay = Payment.objects.get(id=pk)
try:
inv = InvoiceRebilling.objects.get(yandex_pay=pay)
except InvoiceRebilling.DoesNotExist:
inv = None
r = requests.post('https://money.yandex.ru/eshop.xml', data={ r = requests.post('https://money.yandex.ru/eshop.xml', data={
'shopId': pay.shop_id, 'shopId': pay.shop_id,
'scid': pay.scid, 'scid': pay.scid,
@ -320,24 +155,11 @@ class YandexPay(APIView):
'customerNumber': pay.customer_number, 'customerNumber': pay.customer_number,
'orderNumber': pay.order_number, 'orderNumber': pay.order_number,
'cps_email': pay.cps_email, 'cps_email': pay.cps_email,
'rebillingOn': False if inv is None else inv.rebilling_on,
'shopSuccessURL': settings.YANDEX_MONEY_SUCCESS_URL, 'shopSuccessURL': settings.YANDEX_MONEY_SUCCESS_URL,
'shopFailURL': settings.YANDEX_MONEY_FAIL_URL, 'shopFailURL': settings.YANDEX_MONEY_FAIL_URL,
}) })
try: logger_yandex.info(r)
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()
except Exception as exc:
finance_logger.exception('YandexPay: Email not sended')
return redirect(r.url) return redirect(r.url)
@ -357,8 +179,8 @@ def get_invoices(request):
file_name = file_name + "__to_%s" % date_to if date_to else file_name file_name = file_name + "__to_%s" % date_to if date_to else file_name
invoices = Invoice.objects.filter(method="Y", status="F") invoices = Invoice.objects.filter(method="Y", status="F")
invoices = invoices.filter(yandex_pay__performed_datetime__lt=date_to) if date_to else invoices invoices = invoices.filter(date__lt=date_to) if date_to else invoices
invoices = invoices.filter(yandex_pay__performed_datetime__gte=date_from) if date_from else invoices invoices = invoices.filter(date__gte=date_from) if date_from else invoices
response = HttpResponse(content_type='text/csv') response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="%s.csv"' % file_name response['Content-Disposition'] = 'attachment; filename="%s.csv"' % file_name
@ -369,8 +191,8 @@ def get_invoices(request):
for i in invoices.order_by('-date'): for i in invoices.order_by('-date'):
course_api = CourseParamsApi(i.bill.course_token) course_api = CourseParamsApi(i.bill.course_token)
writer.writerow([ writer.writerow([
i.yandex_pay.performed_datetime.date(), i.date.date(),
i.yandex_pay.performed_datetime.time(), i.date.time(),
i.bill.user.email, i.bill.user.email,
i.bill.user.get_full_name(), i.bill.user.get_full_name(),
course_api.get_slug_and_title()['title'], course_api.get_slug_and_title()['title'],
@ -388,53 +210,48 @@ class YandexCheckView(APIView):
@staticmethod @staticmethod
def post(request): def post(request):
data = dict() data = dict()
# TODO заюзать url.parse https://goo.gl/s8Ygos
for i in request.body.decode('utf-8').split('&'): for i in request.body.decode('utf-8').split('&'):
key = i.split('=')[0] key = i.split('=')[0]
val = i.split('=')[1] val = i.split('=')[1]
data[key] = val data[key] = val
finance_logger.info('Проверка платежа запрос', request=data) logger_yandex.info(data)
order_number = data['orderNumber']
try: try:
pay = Payment.objects.get(order_number=order_number) pay = Payment.objects.get(order_number=data['orderNumber'])
except Payment.DoesNotExist: except Payment.DoesNotExist:
finance_logger.error('Ошибка проверки платежа', exc_info=True, logger_yandex.error("Payment with id=%s not found" % data['orderNumber'])
request="Payment with id=%s not found" % order_number)
return Response(status=204) return Response(status=204)
if not pay.status == Payment.STATUS.PROCESSED: if not pay.status == Payment.STATUS.PROCESSED:
finance_logger.error('Ошибка проверки платежа', logger_yandex.error("Payment with id=%s have status %s" % (data['orderNumber'], pay.status))
order_number=order_number,
request="Payment with id=%s have status %s" % (order_number, pay.status))
return Response(status=204) return Response(status=204)
if not pay.shop_id == int(data['shopId']): if not pay.shop_id == int(data['shopId']):
finance_logger.error('Ошибка проверки платежа', logger_yandex.error("ShopId=%s not match" % (data['shopId'],))
request="ShopId=%s not match" % (data['shopId'],))
return Response(status=204) return Response(status=204)
if not pay.scid == int(data['scid']): if not pay.scid == int(data['scid']):
finance_logger.error('Ошибка проверки платежа', logger_yandex.error("scid=%s not match" % (data['scid'],))
request="scid=%s not match" % (data['scid'],))
return Response(status=204) return Response(status=204)
if not pay.order_amount == float(data['orderSumAmount']): if not pay.order_amount == float(data['orderSumAmount']):
finance_logger.error('Ошибка проверки платежа', logger_yandex.error("Expected amount is %s received amount is %s"
request="Expected amount is %s received amount is %s" % (pay.order_amount, data['orderSumAmount'])) % (pay.order_amount, data['orderSumAmount']))
return Response(status=204) return Response(status=204)
now = timezone.now() # TODO Нужно решение
pay.performed_datetime = now.isoformat() # pay.invoice_id = int(data['invoiceId'])
pay.save() # pay.save()
xml_res = dicttoxml.dicttoxml({
xml_res = """<checkOrderResponse performedDatetime="%s" code="0" invoiceId="%s" shopId="%s"/> 'code': 0,
""" % (pay.performed_datetime, str(data['invoiceId']), str(pay.shop_id)) 'shopId': int(pay.shop_id),
'invoiceId': int(data['invoiceId']),
'orderSumAmount': pay.order_amount,
'performedDatetime': datetime.datetime.now(),
})
logger_yandex.info(xml_res)
finance_logger.info('Проверка платежа ответ', response=xml_res) return Response(xml_res, content_type='application/xml')
return HttpResponse(xml_res, content_type='application/xml')
class YandexAvisoView(APIView): class YandexAvisoView(APIView):
@ -448,45 +265,26 @@ class YandexAvisoView(APIView):
val = i.split('=')[1] val = i.split('=')[1]
data[key] = val data[key] = val
finance_logger.info('Подтверждение платежа запрос', request=data)
order_number = data['orderNumber']
try: try:
pay = Payment.objects.get(order_number=order_number) pay = Payment.objects.get(order_number=data['orderNumber'])
except Payment.DoesNotExist: except Payment.DoesNotExist:
finance_logger.error('Ошибка подтверждения платежа', exc_info=True, logger_yandex.error("Payment with invoice_id=%s not found" % data['orderNumber'])
request="Payment with invoice_id=%s not found" % order_number)
return Response(status=204) return Response(status=204)
logger_yandex.info('Get success pay with invoice_id(yandex) %s' % pay.invoice_id)
pay.shop_amount = data['shopSumAmount'] pay.shop_amount = data['shopSumAmount']
pay.status = Payment.STATUS.SUCCESS pay.status = Payment.STATUS.SUCCESS
now = timezone.now()
pay.performed_datetime = now.isoformat()
pay.save() pay.save()
InvoiceRebilling.objects.filter(bill=pay.invoice.bill).update(key=data['invoiceId'])
xml_res = """<paymentAvisoResponse performedDatetime="%s" code="0" invoiceId="%s" shopId="%s"/>
""" % (pay.performed_datetime, str(data['invoiceId']), str(pay.shop_id))
finance_logger.info('Подтверждение платежа ответ', response=xml_res)
context = {
'user_email': pay.invoice.bill.user.email,
'opener_full_name': pay.invoice.bill.opener.get_full_name(),
'course_title': Course.objects.get(token=pay.invoice.bill.course_token).title,
'date': str(pay.invoice.date),
'price': pay.invoice.price,
'finish_date': pay.performed_datetime,
}
subject, to = 'Счёт оплачен', pay.invoice.bill.opener.email
html_content = render_to_string('mail/sales/pay_access.html', context) xml_res = dicttoxml.dicttoxml({
text_content = strip_tags(html_content) 'code': 0,
'shopId':int(pay.shop_id),
msg = EmailMultiAlternatives( 'invoiceId': int(data['invoiceId']),
subject, text_content, to=[to], bcc=['dmitry.dolya@skillbox.ru', 'vera.procenko@skillbox.ru']) 'orderSumAmount': pay.order_amount,
msg.attach_alternative(html_content, "text/html") 'performedDatetime': datetime.datetime.now(),
msg.send() })
logger_yandex.info(xml_res)
return HttpResponse(xml_res, content_type='application/xml') return HttpResponse(xml_res, content_type='application/xml')
@ -495,72 +293,13 @@ class YandexFailView(APIView):
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
@staticmethod @staticmethod
def get(request): def post(request):
data = dict() data = dict()
for i in request.body.decode('utf-8').split('&'): for i in request.body.decode('utf-8').split('&'):
key = i.split('=')[0] key = i.split('=')[0]
val = i.split('=')[1] val = i.split('=')[1]
data[key] = val data[key] = val
finance_logger.error('YandexFailView', data=data) logger_yandex.error(data)
return redirect(to=settings.DOMAIN) return redirect(to=settings.DOMAIN)
class DemoYandexCheckView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def post(request):
# Деалем допущение, что customerNumber=abc1111111 иначе всё плохо
data = dict()
try:
for i in request.body.decode('utf-8').split('&'):
key = i.split('=')[0]
val = i.split('=')[1]
data[key] = val
finance_logger.info('Проверка демо платежа запрос', request=data)
except IndexError:
finance_logger.exception("Непредвиденная ошибка проверки тестогого платежа")
return Response(status=204)
now = timezone.now()
try:
if data['customerNumber'] == "abc000":
xml_res = """<checkOrderResponse performedDatetime="%s" code="0" invoiceId="%s" shopId="%s"/>
""" % (now.isoformat(), str(data['invoiceId']), str(data['shopId']))
else:
xml_res = """<checkOrderResponse performedDatetime="%s" code="100" invoiceId="%s" shopId="%s"
message="Неверный номер ордера"/>""" % (now.isoformat(), str(data['invoiceId']), str(data['shopId']))
finance_logger.warning("Ошибка проверки тестового платежа", response=xml_res)
return HttpResponse(xml_res, content_type='application/xml')
except KeyError:
finance_logger.exception('Ошибка в данных checkYandex')
return Response(status=204)
class DemoYandexAvisoView(APIView):
@staticmethod
def post(request):
finance_logger.info('Начало тестирования avisoYandex')
data = dict()
for i in request.body.decode('utf-8').split('&'):
key = i.split('=')[0]
val = i.split('=')[1]
data[key] = val
now = timezone.now()
if float(data['orderSumAmount']) < 1001:
xml_res = """<paymentAvisoResponse performedDatetime="%s" code="0" invoiceId="%s" shopId="%s"/>
""" % (now.isoformat(), str(data['invoiceId']), str(data['shopId']))
finance_logger.info('Подтверждение демо платежа ответ', response=xml_res)
else:
xml_res = """<paymentAvisoResponse performedDatetime="%s" code="100" invoiceId="%s" shopId="%s"
message="Нам не позволяет совесть принять от вас более 1000 рублей"/
>""" % (now.isoformat(), str(data['invoiceId']), str(data['shopId']))
finance_logger.warning("Ошибка подтверждения тестогого платежа", response=xml_res)
return HttpResponse(xml_res, content_type='application/xml')

@ -3,8 +3,6 @@ import os
from celery import Celery from celery import Celery
from raven import Client from raven import Client
from raven.contrib.celery import register_signal, register_logger_signal from raven.contrib.celery import register_signal, register_logger_signal
from celery.schedules import crontab
# set the default Django settings module for the 'celery' program. # set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lms.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lms.settings')
@ -15,18 +13,10 @@ app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks() app.autodiscover_tasks()
client = Client('http://caaea487274f4e23a9107862484c79f3:3d463ad4717942508536f7a659921950@sentry.skillbox.ru/7') client = Client('http://caaea487274f4e23a9107862484c79f3:3d463ad4717942508536f7a659921950@sentry.skillbox.ru/3')
register_logger_signal(client) register_logger_signal(client)
register_signal(client) register_signal(client)
app.conf.beat_schedule = {
'periodic_billing': {
# 'schedule': crontab(day_of_month='1', hour='1', minute='1'), # заглушка на время отладки
'schedule': crontab(minute='0', hour='*/3',),
'task': 'finance.tasks.periodic_billing'
}
}
@app.task(bind=True) @app.task(bind=True)
def debug_task(self): def debug_task(self):

@ -5,15 +5,26 @@ import raven
import environ import environ
import socket import socket
root = environ.Path(__file__) - 2 root = environ.Path(__file__) - 2
env = environ.Env() env = environ.Env()
MOD = os.environ.get('MOD', 'Prod')
environ.Env.read_env(str(root) + '/config_app/settings/local.env') DEBUG = os.environ.get('DEBUG', 'False')
MASTER_PASSWORD = os.environ.get('MASTER_PASSWORD', '@J*1') 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')
elif MOD == 'Prod':
environ.Env.read_env(str(root) + '/config_app/settings/prod.env')
else:
raise ImportError('no such environ ' + MOD)
EMAIL_CONFIG = env.email_url('EMAIL_URL',) EMAIL_CONFIG = env.email_url('EMAIL_URL',)
vars().update(EMAIL_CONFIG) vars().update(EMAIL_CONFIG)
@ -44,17 +55,10 @@ DOMAIN = 'https://go.skillbox.ru'
YANDEX_MONEY_DEBUG = False YANDEX_MONEY_DEBUG = False
YANDEX_MONEY_SCID = '149639' YANDEX_MONEY_SCID = '149639'
YANDEX_MONEY_TEST_SCID = '558146'
YANDEX_MONEY_SHOP_ID = '157133' YANDEX_MONEY_SHOP_ID = '157133'
#Todo костыль на рассрочку
YANDEX_MONEY_REBILLING_SCID = '723030'
YANDEX_MONEY_REBILLING_SHOP_ID = '199287'
YANDEX_MONEY_SHOP_PASSWORD = 'nu5Xefise' YANDEX_MONEY_SHOP_PASSWORD = 'nu5Xefise'
YANDEX_MONEY_FAIL_URL = '%s/api/v1/finance/yandex/fail/' % DOMAIN YANDEX_MONEY_FAIL_URL = '%s/api/v1/finance/yandex/fail/' % DOMAIN
YANDEX_MONEY_SUCCESS_URL = '%s/' % DOMAIN YANDEX_MONEY_SUCCESS_URL = '%s/' % DOMAIN
YANDEX_MONEY_MWS_URL = 'https://penelope.yamoney.ru/webservice/mws/api/'
# информировать о случаях, когда модуль вернул Яндекс.Кассе ошибку # информировать о случаях, когда модуль вернул Яндекс.Кассе ошибку
YANDEX_MONEY_MAIL_ADMINS_ON_PAYMENT_ERROR = True YANDEX_MONEY_MAIL_ADMINS_ON_PAYMENT_ERROR = True
# Application definition # Application definition
@ -66,8 +70,6 @@ DATABASES = {
'default': env.db(), 'default': env.db(),
} }
COURSE_PROGRESS_SECRET_KEY = "!gf?s3@4Hr5#J#&%Kfr@56s"
SESSION_ENGINE = 'redis_sessions.session' SESSION_ENGINE = 'redis_sessions.session'
CELERY_EMAIL_CHUNK_SIZE = 1 CELERY_EMAIL_CHUNK_SIZE = 1
@ -188,8 +190,6 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = '/static/' STATIC_URL = '/static/'
SSL_ROOT = os.path.join(BASE_DIR, 'ssl')
RAVEN_CONFIG = { RAVEN_CONFIG = {
'dsn': 'http://1a09557dbd144e52af4b14bea569c114:fbb5dfaa39e64f02a1b4cc7ac665d7d7@sentry.skillbox.ru/7' 'dsn': 'http://1a09557dbd144e52af4b14bea569c114:fbb5dfaa39e64f02a1b4cc7ac665d7d7@sentry.skillbox.ru/7'
} }
@ -206,51 +206,31 @@ LOGGING = {
'format': '%(levelname)s %(asctime)s %(module)s ' 'format': '%(levelname)s %(asctime)s %(module)s '
'%(process)d %(thread)d %(message)s' '%(process)d %(thread)d %(message)s'
}, },
'finance_data_formatter': {
'format': '%(asctime)s - %(levelname)s - %(message)s - %(finance_data)s'
},
}, },
'handlers': { 'handlers': {
'sentry': { 'sentry': {
'level': 'WARNING', # To capture more than ERROR, change to WARNING, INFO, etc. 'level': 'WARNING', # To capture more than ERROR, change to WARNING, INFO, etc.
'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
'tags': {'custom-tag': 'x'}, 'tags': {'custom-tag': 'x'},
}, },
'finance_data': { 'yandex_money': {
'level': 'DEBUG',
'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
'tags': {'custom-tag': 'yandex'}, # TODO переделать на finance_data
},
'finance_data_file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_DIR, 'logs', 'finance_data.log'),
'maxBytes': 1024 * 1024 * 10,
'formatter': 'finance_data_formatter'
},
'business_rules': {
'level': 'DEBUG', 'level': 'DEBUG',
'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
'tags': {'custom-tag': 'business_rules'}, 'tags': {'custom-tag': 'yandex'},
}, },
'console': { 'console': {
'level': 'DEBUG', 'level': 'DEBUG',
'class': 'logging.StreamHandler', 'class': 'logging.StreamHandler',
'formatter': 'verbose' 'formatter': 'verbose'
}, }
}, },
'loggers': { 'loggers': {
'': { '': {
'level': 'WARNING', 'level': 'WARNING',
'handlers': ['sentry'], 'handlers': ['sentry'],
}, },
'finance_data': { 'yandex_money': {
'handlers': ['finance_data', 'finance_data_file', ], 'handlers': ['yandex_money'],
'level': 'DEBUG',
'propagate': False
},
'business_rules': {
'handlers': ['business_rules'],
'level': 'DEBUG', 'level': 'DEBUG',
'propagate': False 'propagate': False
}, },

@ -1,7 +1,7 @@
from django.conf.urls import url, include from django.conf.urls import url, include
from django.contrib import admin from django.contrib import admin
from django.views.static import serve from django.views.static import serve
from finance.views import YandexCheckView, YandexAvisoView, DemoYandexCheckView, DemoYandexAvisoView from finance.views import YandexCheckView, YandexAvisoView
from django.conf import settings from django.conf import settings
@ -12,7 +12,5 @@ urlpatterns = [
url(r'^static/(?P<path>.*)/$', serve, {'document_root': settings.STATIC_ROOT}), url(r'^static/(?P<path>.*)/$', serve, {'document_root': settings.STATIC_ROOT}),
url(r'^wallet/pay/check/$', YandexCheckView.as_view(), name='yandex_money_check'), url(r'^wallet/pay/check/$', YandexCheckView.as_view(), name='yandex_money_check'),
url(r'^wallet/pay/result/$', YandexAvisoView.as_view(), name='yandex_money_notice'), url(r'^wallet/pay/result/$', YandexAvisoView.as_view(), name='yandex_money_notice'),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
url(r'^yandex-money/check/$', DemoYandexCheckView.as_view()),
url(r'^yandex-money/aviso/$', DemoYandexAvisoView.as_view())
] ]

@ -0,0 +1,520 @@
[2017-10-20 13:12:58,041: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-20 13:12:58,049: INFO/MainProcess] mingle: searching for neighbors
[2017-10-20 13:12:59,065: INFO/MainProcess] mingle: all alone
[2017-10-20 13:12:59,110: WARNING/MainProcess] /home/andrey/skillbox/lib/python3.6/site-packages/celery/fixups/django.py:202: UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments!
warnings.warn('Using settings.DEBUG leads to a memory leak, never '
[2017-10-20 13:12:59,113: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-20 13:13:02,946: INFO/MainProcess] Events of group {task} enabled by remote.
[2017-10-20 13:17:23,104: INFO/MainProcess] Received task: lms.celery.debug_task[e7d9bdc2-a3a8-44b2-b3c6-935acfa092d7]
[2017-10-20 13:17:23,106: WARNING/ForkPoolWorker-2] Request: <Context: {'lang': 'py', 'task': 'lms.celery.debug_task', 'id': 'e7d9bdc2-a3a8-44b2-b3c6-935acfa092d7', 'eta': None, 'expires': None, 'group': None, 'retries': 0, 'timelimit': [None, None], 'root_id': 'e7d9bdc2-a3a8-44b2-b3c6-935acfa092d7', 'parent_id': None, 'argsrepr': '()', 'kwargsrepr': '{}', 'origin': 'gen13641@andrey-desktop', 'reply_to': 'b8c6ce7f-8ee0-35c7-ad70-026ad2f36d4a', 'correlation_id': 'e7d9bdc2-a3a8-44b2-b3c6-935acfa092d7', 'delivery_info': {'exchange': '', 'routing_key': 'celery', 'priority': 0, 'redelivered': False}, 'args': [], 'kwargs': {}, 'hostname': 'celery@andrey-desktop', 'is_eager': False, 'callbacks': None, 'errbacks': None, 'chain': None, 'chord': None, 'called_directly': False, '_protected': 1}>
[2017-10-20 13:17:23,124: INFO/ForkPoolWorker-2] Task lms.celery.debug_task[e7d9bdc2-a3a8-44b2-b3c6-935acfa092d7] succeeded in 0.018874458000027516s: None
[2017-10-20 13:17:29,057: INFO/MainProcess] Received task: lms.celery.debug_task[88f178fc-7222-4de0-b671-57e7472a9be1]
[2017-10-20 13:17:29,059: WARNING/ForkPoolWorker-1] Request: <Context: {'lang': 'py', 'task': 'lms.celery.debug_task', 'id': '88f178fc-7222-4de0-b671-57e7472a9be1', 'eta': None, 'expires': None, 'group': None, 'retries': 0, 'timelimit': [None, None], 'root_id': '88f178fc-7222-4de0-b671-57e7472a9be1', 'parent_id': None, 'argsrepr': '()', 'kwargsrepr': '{}', 'origin': 'gen13641@andrey-desktop', 'reply_to': 'b8c6ce7f-8ee0-35c7-ad70-026ad2f36d4a', 'correlation_id': '88f178fc-7222-4de0-b671-57e7472a9be1', 'delivery_info': {'exchange': '', 'routing_key': 'celery', 'priority': 0, 'redelivered': False}, 'args': [], 'kwargs': {}, 'hostname': 'celery@andrey-desktop', 'is_eager': False, 'callbacks': None, 'errbacks': None, 'chain': None, 'chord': None, 'called_directly': False, '_protected': 1}>
[2017-10-20 13:17:29,081: INFO/ForkPoolWorker-1] Task lms.celery.debug_task[88f178fc-7222-4de0-b671-57e7472a9be1] succeeded in 0.022606980999626103s: None
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-20 13:12:57
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7f47cb927be0
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send_multiple
. lms.celery.debug_task
[2017-10-20 13:32:32,128: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-20 13:32:32,135: INFO/MainProcess] mingle: searching for neighbors
[2017-10-20 13:32:33,150: INFO/MainProcess] mingle: all alone
[2017-10-20 13:32:33,201: WARNING/MainProcess] /home/andrey/skillbox/lib/python3.6/site-packages/celery/fixups/django.py:202: UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments!
warnings.warn('Using settings.DEBUG leads to a memory leak, never '
[2017-10-20 13:32:33,202: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-20 13:32:36,911: INFO/MainProcess] Events of group {task} enabled by remote.
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-20 13:32:32
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7f34afe2fbe0
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send_multiple
. lms.celery.debug_task
[2017-10-20 13:38:00,333: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-20 13:38:00,341: INFO/MainProcess] mingle: searching for neighbors
[2017-10-20 13:38:01,361: INFO/MainProcess] mingle: all alone
[2017-10-20 13:38:01,408: WARNING/MainProcess] /home/andrey/skillbox/lib/python3.6/site-packages/celery/fixups/django.py:202: UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments!
warnings.warn('Using settings.DEBUG leads to a memory leak, never '
[2017-10-20 13:38:01,409: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-20 13:38:05,171: INFO/MainProcess] Events of group {task} enabled by remote.
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-20 13:38:00
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7fd8781acbe0
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send_multiple
. lms.celery.debug_task
[2017-10-20 13:47:50,974: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-20 13:47:50,985: INFO/MainProcess] mingle: searching for neighbors
[2017-10-20 13:47:52,012: INFO/MainProcess] mingle: all alone
[2017-10-20 13:47:52,069: WARNING/MainProcess] /home/andrey/skillbox/lib/python3.6/site-packages/celery/fixups/django.py:202: UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments!
warnings.warn('Using settings.DEBUG leads to a memory leak, never '
[2017-10-20 13:47:52,069: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-20 13:47:56,047: INFO/MainProcess] Events of group {task} enabled by remote.
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-20 13:47:50
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7fc6511c1b70
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send_multiple
. lms.celery.debug_task
[2017-10-20 14:42:47,658: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-20 14:42:47,664: INFO/MainProcess] mingle: searching for neighbors
[2017-10-20 14:42:48,682: INFO/MainProcess] mingle: all alone
[2017-10-20 14:42:48,727: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-20 14:42:52,394: INFO/MainProcess] Events of group {task} enabled by remote.
[2017-10-20 14:47:53,063: INFO/MainProcess] Tasks flagged as revoked: ff8246ff-1904-47da-ac57-1a844272c826
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-20 14:42:47
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7f89a2228be0
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send_multiple
. lms.celery.debug_task
[2017-10-20 14:56:43,024: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-20 14:56:43,031: INFO/MainProcess] mingle: searching for neighbors
[2017-10-20 14:56:44,048: INFO/MainProcess] mingle: all alone
[2017-10-20 14:56:44,112: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-20 14:56:47,869: INFO/MainProcess] Events of group {task} enabled by remote.
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-20 14:56:42
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7f4f0c601be0
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send_multiple
. lms.celery.debug_task
[2017-10-20 15:14:13,071: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-20 15:14:13,086: INFO/MainProcess] mingle: searching for neighbors
[2017-10-20 15:14:14,109: INFO/MainProcess] mingle: all alone
[2017-10-20 15:14:14,123: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-20 15:14:18,076: INFO/MainProcess] Events of group {task} enabled by remote.
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-20 15:14:12
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7fd94e575b70
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send_multiple
. lms.celery.debug_task
[2017-10-20 15:18:26,330: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-20 15:18:26,337: INFO/MainProcess] mingle: searching for neighbors
[2017-10-20 15:18:27,355: INFO/MainProcess] mingle: all alone
[2017-10-20 15:18:27,417: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-20 15:18:31,190: INFO/MainProcess] Events of group {task} enabled by remote.
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-20 15:18:26
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7f4b2ac05be0
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send_multiple
. lms.celery.debug_task
[2017-10-20 15:31:17,246: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-20 15:31:17,253: INFO/MainProcess] mingle: searching for neighbors
[2017-10-20 15:31:18,269: INFO/MainProcess] mingle: all alone
[2017-10-20 15:31:18,311: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-20 15:31:22,075: INFO/MainProcess] Events of group {task} enabled by remote.
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-20 15:31:17
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7f2e6cc96be0
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send_multiple
. lms.celery.debug_task
[2017-10-20 16:52:27,050: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-20 16:52:27,057: INFO/MainProcess] mingle: searching for neighbors
[2017-10-20 16:52:28,074: INFO/MainProcess] mingle: all alone
[2017-10-20 16:52:28,128: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-20 16:52:31,818: INFO/MainProcess] Events of group {task} enabled by remote.
[2017-10-20 17:00:17,556: INFO/MainProcess] Received task: djcelery_email_send[8735440e-9760-43c7-b84c-c6efdac4e64d]
[2017-10-20 17:00:19,202: INFO/ForkPoolWorker-2] Task djcelery_email_send[8735440e-9760-43c7-b84c-c6efdac4e64d] succeeded in 1.4465199199985364s: 1
[2017-10-20 17:39:12,619: INFO/MainProcess] Received task: djcelery_email_send[243eb7a2-b4e0-4221-8ab2-c83c94d8f241]
[2017-10-20 17:39:14,523: INFO/ForkPoolWorker-1] Task djcelery_email_send[243eb7a2-b4e0-4221-8ab2-c83c94d8f241] succeeded in 1.4357735300000058s: 1
[2017-10-20 17:40:33,125: INFO/MainProcess] Received task: djcelery_email_send[863adc65-6bf5-4a1d-a2b9-91ebb9737520]
[2017-10-20 17:40:34,548: INFO/ForkPoolWorker-2] Task djcelery_email_send[863adc65-6bf5-4a1d-a2b9-91ebb9737520] succeeded in 1.3558088320023671s: 1
[2017-10-20 17:42:16,950: INFO/MainProcess] Received task: djcelery_email_send[a60b5727-8d77-49f8-8008-82ae064ed682]
[2017-10-20 17:42:18,493: INFO/ForkPoolWorker-1] Task djcelery_email_send[a60b5727-8d77-49f8-8008-82ae064ed682] succeeded in 1.1552653329999885s: 1
[2017-10-20 17:57:36,541: INFO/MainProcess] Received task: djcelery_email_send[8184ca5f-b2e2-42ee-a096-b3c2ecf152e2]
[2017-10-20 17:57:37,905: INFO/ForkPoolWorker-2] Task djcelery_email_send[8184ca5f-b2e2-42ee-a096-b3c2ecf152e2] succeeded in 1.251813505001337s: 1
[2017-10-20 19:20:51,999: WARNING/MainProcess] consumer: Connection to broker lost. Trying to re-establish the connection...
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/consumer/consumer.py", line 320, in start
blueprint.start(self)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/bootsteps.py", line 119, in start
step.start(parent)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/consumer/consumer.py", line 596, in start
c.loop(*c.loop_args())
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/loops.py", line 88, in asynloop
next(loop)
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/async/hub.py", line 354, in create_loop
cb(*cbargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/transport/base.py", line 236, in on_readable
reader(loop)
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/transport/base.py", line 218, in _read
drain_events(timeout=0)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 471, in drain_events
while not self.blocking_read(timeout):
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 477, in blocking_read
return self.on_inbound_frame(frame)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/method_framing.py", line 55, in on_frame
callback(channel, method_sig, buf, None)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 481, in on_inbound_method
method_sig, payload, content,
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/abstract_channel.py", line 128, in dispatch_method
listener(*args)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 603, in _on_close
(class_id, method_id), ConnectionError)
amqp.exceptions.ConnectionForced: (0, 0): (320) CONNECTION_FORCED - broker forced connection closure with reason 'shutdown'
[2017-10-20 19:20:52,012: ERROR/MainProcess] consumer: Cannot connect to amqp://guest:**@127.0.0.1:5672//: [Errno 111] Connection refused.
Trying again in 2.00 seconds...
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-20 16:52:26
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7fe25f735be0
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send
. lms.celery.debug_task
[2017-10-23 08:32:22,988: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-23 08:32:22,998: INFO/MainProcess] mingle: searching for neighbors
[2017-10-23 08:32:24,013: INFO/MainProcess] mingle: all alone
[2017-10-23 08:32:24,021: INFO/MainProcess] celery@andrey-desktop ready.
[2017-10-23 08:32:27,769: INFO/MainProcess] Events of group {task} enabled by remote.
[2017-10-23 11:42:09,222: INFO/MainProcess] Received task: djcelery_email_send[54f16e2a-a437-4000-850f-0bf29ff758b8]
[2017-10-23 11:42:10,844: INFO/ForkPoolWorker-2] Task djcelery_email_send[54f16e2a-a437-4000-850f-0bf29ff758b8] succeeded in 1.4740454479997425s: 1
[2017-10-23 11:44:36,901: INFO/MainProcess] Received task: djcelery_email_send[2ef60ead-2523-4bf8-b117-366bb120359d]
[2017-10-23 11:44:39,194: INFO/ForkPoolWorker-1] Task djcelery_email_send[2ef60ead-2523-4bf8-b117-366bb120359d] succeeded in 1.4229817790001107s: 1
[2017-10-23 11:45:49,202: INFO/MainProcess] Received task: djcelery_email_send[3e60e35a-c846-4f37-bd88-1d92afc9dcac]
[2017-10-23 11:45:50,624: INFO/ForkPoolWorker-2] Task djcelery_email_send[3e60e35a-c846-4f37-bd88-1d92afc9dcac] succeeded in 1.1725643370009493s: 1
[2017-10-23 11:47:18,371: INFO/MainProcess] Received task: djcelery_email_send[4530e853-0db4-499d-8a65-7128121ee5bc]
[2017-10-23 11:47:20,136: INFO/ForkPoolWorker-1] Task djcelery_email_send[4530e853-0db4-499d-8a65-7128121ee5bc] succeeded in 1.3828427380012727s: 1
[2017-10-23 11:56:05,834: INFO/MainProcess] Received task: djcelery_email_send[9fc201ce-f3ec-40af-9dd1-bf998f34a57d]
[2017-10-23 11:56:08,831: INFO/ForkPoolWorker-2] Task djcelery_email_send[9fc201ce-f3ec-40af-9dd1-bf998f34a57d] succeeded in 1.293271452001136s: 1
[2017-10-23 11:59:57,509: INFO/MainProcess] Received task: djcelery_email_send[7945e9d6-ef7c-4e19-9c59-422100ac9f73]
[2017-10-23 11:59:58,959: INFO/ForkPoolWorker-1] Task djcelery_email_send[7945e9d6-ef7c-4e19-9c59-422100ac9f73] succeeded in 1.1895115009992878s: 1
[2017-10-23 12:08:24,457: INFO/MainProcess] Received task: djcelery_email_send[f0968ae1-8fbd-4d2c-905a-2ecf445aa10e]
[2017-10-23 12:08:25,890: INFO/ForkPoolWorker-2] Task djcelery_email_send[f0968ae1-8fbd-4d2c-905a-2ecf445aa10e] succeeded in 1.3443215190000046s: 1
[2017-10-23 12:20:29,414: INFO/MainProcess] Received task: djcelery_email_send[4438a0d4-228c-40a9-b91d-612f82ea791b]
[2017-10-23 12:20:31,027: INFO/ForkPoolWorker-1] Task djcelery_email_send[4438a0d4-228c-40a9-b91d-612f82ea791b] succeeded in 1.452125650999733s: 1
[2017-10-23 12:21:31,101: INFO/MainProcess] Received task: djcelery_email_send[c3793dcb-e5f9-42bd-83b2-dd94f166c741]
[2017-10-23 12:21:32,804: INFO/ForkPoolWorker-2] Task djcelery_email_send[c3793dcb-e5f9-42bd-83b2-dd94f166c741] succeeded in 1.1413313480006764s: 1
[2017-10-23 12:38:27,027: INFO/MainProcess] Received task: djcelery_email_send[ea55b25a-8e6d-4e88-8b18-52d561bd7c95]
[2017-10-23 12:38:28,225: INFO/ForkPoolWorker-1] Task djcelery_email_send[ea55b25a-8e6d-4e88-8b18-52d561bd7c95] succeeded in 1.150906519000273s: 1
[2017-10-23 13:47:05,338: INFO/MainProcess] Received task: djcelery_email_send[b05bd9eb-848d-49b6-9725-d190e256527e]
[2017-10-23 13:47:08,582: INFO/ForkPoolWorker-2] Task djcelery_email_send[b05bd9eb-848d-49b6-9725-d190e256527e] succeeded in 1.5058496720012045s: 1
[2017-10-23 13:48:09,314: INFO/MainProcess] Received task: djcelery_email_send[fe0bd44a-f23e-476b-a447-c03d8b5bb266]
[2017-10-23 13:48:12,620: INFO/ForkPoolWorker-1] Task djcelery_email_send[fe0bd44a-f23e-476b-a447-c03d8b5bb266] succeeded in 1.340003754998179s: 1
[2017-10-23 14:36:33,382: INFO/MainProcess] Received task: djcelery_email_send[33418ad8-1f87-4a6f-ba11-a2fc0f52374a]
[2017-10-23 14:36:35,323: INFO/ForkPoolWorker-2] Task djcelery_email_send[33418ad8-1f87-4a6f-ba11-a2fc0f52374a] succeeded in 1.3137642000001506s: 1
[2017-10-23 14:36:49,212: INFO/MainProcess] Received task: djcelery_email_send[5f3beb45-7d03-447a-be90-41506efb897b]
[2017-10-23 14:36:51,271: INFO/ForkPoolWorker-1] Task djcelery_email_send[5f3beb45-7d03-447a-be90-41506efb897b] succeeded in 1.23961846200109s: 1
[2017-10-23 14:46:42,199: INFO/MainProcess] Received task: djcelery_email_send[69d6f018-e571-4d65-93a4-151fa78361ae]
[2017-10-23 14:46:44,060: INFO/ForkPoolWorker-2] Task djcelery_email_send[69d6f018-e571-4d65-93a4-151fa78361ae] succeeded in 1.2866839900016203s: 1
[2017-10-23 14:55:56,121: WARNING/MainProcess] consumer: Connection to broker lost. Trying to re-establish the connection...
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/consumer/consumer.py", line 320, in start
blueprint.start(self)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/bootsteps.py", line 119, in start
step.start(parent)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/consumer/consumer.py", line 596, in start
c.loop(*c.loop_args())
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/loops.py", line 88, in asynloop
next(loop)
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/async/hub.py", line 354, in create_loop
cb(*cbargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/transport/base.py", line 236, in on_readable
reader(loop)
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/transport/base.py", line 218, in _read
drain_events(timeout=0)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 471, in drain_events
while not self.blocking_read(timeout):
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 477, in blocking_read
return self.on_inbound_frame(frame)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/method_framing.py", line 55, in on_frame
callback(channel, method_sig, buf, None)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 481, in on_inbound_method
method_sig, payload, content,
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/abstract_channel.py", line 128, in dispatch_method
listener(*args)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 601, in _on_close
self._x_close_ok()
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 616, in _x_close_ok
self.send_method(spec.Connection.CloseOk, callback=self._on_close_ok)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/abstract_channel.py", line 50, in send_method
conn.frame_writer(1, self.channel_id, sig, args, content)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/method_framing.py", line 166, in write_frame
write(view[:offset])
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/transport.py", line 258, in write
self._write(s)
ConnectionResetError: [Errno 104] Connection reset by peer
[2017-10-23 14:55:56,137: ERROR/MainProcess] consumer: Cannot connect to amqp://guest:**@127.0.0.1:5672//: [Errno 111] Connection refused.
Trying again in 2.00 seconds...
[2017-10-23 14:55:58,150: ERROR/MainProcess] consumer: Cannot connect to amqp://guest:**@127.0.0.1:5672//: [Errno 111] Connection refused.
Trying again in 4.00 seconds...
[2017-10-23 14:56:02,176: ERROR/MainProcess] consumer: Cannot connect to amqp://guest:**@127.0.0.1:5672//: [Errno 111] Connection refused.
Trying again in 6.00 seconds...
[2017-10-23 14:56:08,193: ERROR/MainProcess] consumer: Cannot connect to amqp://guest:**@127.0.0.1:5672//: [Errno 111] Connection refused.
Trying again in 8.00 seconds...
[2017-10-23 14:56:16,210: ERROR/MainProcess] consumer: Cannot connect to amqp://guest:**@127.0.0.1:5672//: [Errno 111] Connection refused.
Trying again in 10.00 seconds...
[2017-10-23 14:56:26,228: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-23 14:56:26,237: INFO/MainProcess] mingle: searching for neighbors
[2017-10-23 14:56:26,241: WARNING/MainProcess] consumer: Connection to broker lost. Trying to re-establish the connection...
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/consumer/consumer.py", line 320, in start
blueprint.start(self)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/bootsteps.py", line 119, in start
step.start(parent)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/consumer/mingle.py", line 38, in start
self.sync(c)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/consumer/mingle.py", line 42, in sync
replies = self.send_hello(c)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/consumer/mingle.py", line 55, in send_hello
replies = inspect.hello(c.hostname, our_revoked._data) or {}
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/control.py", line 129, in hello
return self._request('hello', from_node=from_node, revoked=revoked)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/control.py", line 81, in _request
timeout=self.timeout, reply=True,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/control.py", line 436, in broadcast
limit, callback, channel=channel,
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/pidbox.py", line 315, in _broadcast
serializer=serializer)
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/pidbox.py", line 290, in _publish
serializer=serializer,
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/messaging.py", line 181, in publish
exchange_name, declare,
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/messaging.py", line 203, in _publish
mandatory=mandatory, immediate=immediate,
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/channel.py", line 1734, in _basic_publish
(0, exchange, routing_key, mandatory, immediate), msg
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/abstract_channel.py", line 50, in send_method
conn.frame_writer(1, self.channel_id, sig, args, content)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/method_framing.py", line 166, in write_frame
write(view[:offset])
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/transport.py", line 258, in write
self._write(s)
ConnectionResetError: [Errno 104] Connection reset by peer
[2017-10-23 14:56:26,250: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2017-10-23 14:56:26,256: INFO/MainProcess] mingle: searching for neighbors
[2017-10-23 14:56:27,273: INFO/MainProcess] mingle: all alone
[2017-10-23 14:56:27,310: INFO/MainProcess] Received task: djcelery_email_send[455cf8cb-b29f-4159-a81b-b33e5dbee1f3]
[2017-10-23 14:56:28,959: INFO/ForkPoolWorker-2] Task djcelery_email_send[455cf8cb-b29f-4159-a81b-b33e5dbee1f3] succeeded in 1.1903551169998536s: 1
[2017-10-23 18:52:13,477: INFO/MainProcess] Received task: djcelery_email_send[0fb5c171-4123-47fa-8089-823904fde5fd]
[2017-10-23 18:52:16,661: INFO/ForkPoolWorker-1] Task djcelery_email_send[0fb5c171-4123-47fa-8089-823904fde5fd] succeeded in 1.222238110996841s: 1
[2017-10-23 19:05:28,619: WARNING/MainProcess] consumer: Connection to broker lost. Trying to re-establish the connection...
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/consumer/consumer.py", line 320, in start
blueprint.start(self)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/bootsteps.py", line 119, in start
step.start(parent)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/consumer/consumer.py", line 596, in start
c.loop(*c.loop_args())
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/worker/loops.py", line 88, in asynloop
next(loop)
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/async/hub.py", line 354, in create_loop
cb(*cbargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/transport/base.py", line 236, in on_readable
reader(loop)
File "/home/andrey/skillbox/lib/python3.6/site-packages/kombu/transport/base.py", line 218, in _read
drain_events(timeout=0)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 471, in drain_events
while not self.blocking_read(timeout):
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 477, in blocking_read
return self.on_inbound_frame(frame)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/method_framing.py", line 55, in on_frame
callback(channel, method_sig, buf, None)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 481, in on_inbound_method
method_sig, payload, content,
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/abstract_channel.py", line 128, in dispatch_method
listener(*args)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 601, in _on_close
self._x_close_ok()
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/connection.py", line 616, in _x_close_ok
self.send_method(spec.Connection.CloseOk, callback=self._on_close_ok)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/abstract_channel.py", line 50, in send_method
conn.frame_writer(1, self.channel_id, sig, args, content)
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/method_framing.py", line 166, in write_frame
write(view[:offset])
File "/home/andrey/skillbox/lib/python3.6/site-packages/amqp/transport.py", line 258, in write
self._write(s)
ConnectionResetError: [Errno 104] Connection reset by peer
[2017-10-23 19:05:28,624: ERROR/MainProcess] consumer: Cannot connect to amqp://guest:**@127.0.0.1:5672//: [Errno 111] Connection refused.
Trying again in 2.00 seconds...
worker: Warm shutdown (MainProcess)
-------------- celery@andrey-desktop v4.1.0 (latentcall)
---- **** -----
--- * *** * -- Linux-4.10.0-37-generic-x86_64-with-Ubuntu-16.04-xenial 2017-10-23 08:32:22
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: lms:0x7f574ff21ba8
- ** ---------- .> transport: amqp://guest:**@localhost:5672//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. djcelery_email_send
. lms.celery.debug_task

@ -0,0 +1,623 @@
[E 171020 13:12:57 signal:232] Signal handler <promise@0x7f84e8f79678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7f84e711bbe0>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171020 13:12:57 command:139] Visit me at http://localhost:5555
[I 171020 13:12:57 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171020 13:12:57 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send_multiple',
'lms.celery.debug_task']
[I 171020 13:12:57 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171020 13:13:00 control:44] 'stats' inspect method failed
[W 171020 13:13:00 control:44] 'active_queues' inspect method failed
[W 171020 13:13:00 control:44] 'registered' inspect method failed
[W 171020 13:13:00 control:44] 'scheduled' inspect method failed
[W 171020 13:13:00 control:44] 'active' inspect method failed
[W 171020 13:13:00 control:44] 'reserved' inspect method failed
[W 171020 13:13:00 control:44] 'revoked' inspect method failed
[W 171020 13:13:00 control:44] 'conf' inspect method failed
[I 171020 13:32:28 command:47] SIGTERM detected, shutting down
[E 171020 13:32:31 signal:232] Signal handler <promise@0x7f72af4b2678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7f72ad655b70>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171020 13:32:31 command:139] Visit me at http://localhost:5555
[I 171020 13:32:31 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171020 13:32:31 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send_multiple',
'lms.celery.debug_task']
[I 171020 13:32:31 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171020 13:32:34 control:44] 'stats' inspect method failed
[W 171020 13:32:34 control:44] 'active_queues' inspect method failed
[W 171020 13:32:34 control:44] 'registered' inspect method failed
[W 171020 13:32:34 control:44] 'scheduled' inspect method failed
[W 171020 13:32:34 control:44] 'active' inspect method failed
[W 171020 13:32:34 control:44] 'reserved' inspect method failed
[W 171020 13:32:34 control:44] 'revoked' inspect method failed
[W 171020 13:32:34 control:44] 'conf' inspect method failed
[I 171020 13:37:56 command:47] SIGTERM detected, shutting down
[E 171020 13:38:00 signal:232] Signal handler <promise@0x7f00bb71c678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7f00b98bebe0>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171020 13:38:00 command:139] Visit me at http://localhost:5555
[I 171020 13:38:00 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171020 13:38:00 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send_multiple',
'lms.celery.debug_task']
[I 171020 13:38:00 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171020 13:38:02 control:44] 'stats' inspect method failed
[W 171020 13:38:02 control:44] 'active_queues' inspect method failed
[W 171020 13:38:02 control:44] 'registered' inspect method failed
[W 171020 13:38:02 control:44] 'scheduled' inspect method failed
[W 171020 13:38:02 control:44] 'active' inspect method failed
[W 171020 13:38:02 control:44] 'reserved' inspect method failed
[W 171020 13:38:02 control:44] 'revoked' inspect method failed
[W 171020 13:38:02 control:44] 'conf' inspect method failed
[I 171020 13:47:47 command:47] SIGTERM detected, shutting down
[E 171020 13:47:51 signal:232] Signal handler <promise@0x7f7efbb18678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7f7ef9cbabe0>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171020 13:47:51 command:139] Visit me at http://localhost:5555
[I 171020 13:47:51 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171020 13:47:51 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send_multiple',
'lms.celery.debug_task']
[I 171020 13:47:51 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171020 13:47:53 control:44] 'stats' inspect method failed
[W 171020 13:47:53 control:44] 'active_queues' inspect method failed
[W 171020 13:47:53 control:44] 'registered' inspect method failed
[W 171020 13:47:53 control:44] 'scheduled' inspect method failed
[I 171020 14:42:44 command:47] SIGTERM detected, shutting down
[E 171020 14:42:47 signal:232] Signal handler <promise@0x7f9b93353678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7f9b914f5be0>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171020 14:42:47 command:139] Visit me at http://localhost:5555
[I 171020 14:42:47 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171020 14:42:47 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send_multiple',
'lms.celery.debug_task']
[I 171020 14:42:47 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171020 14:42:49 control:44] 'stats' inspect method failed
[W 171020 14:42:49 control:44] 'active_queues' inspect method failed
[W 171020 14:42:49 control:44] 'registered' inspect method failed
[W 171020 14:42:49 control:44] 'scheduled' inspect method failed
[W 171020 14:42:49 control:44] 'active' inspect method failed
[W 171020 14:42:49 control:44] 'reserved' inspect method failed
[W 171020 14:42:49 control:44] 'revoked' inspect method failed
[W 171020 14:42:49 control:44] 'conf' inspect method failed
[I 171020 14:56:39 command:47] SIGTERM detected, shutting down
[E 171020 14:56:42 signal:232] Signal handler <promise@0x7f15cad6f678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7f15c8f11be0>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171020 14:56:42 command:139] Visit me at http://localhost:5555
[I 171020 14:56:42 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171020 14:56:42 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send_multiple',
'lms.celery.debug_task']
[I 171020 14:56:42 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171020 14:56:45 control:44] 'stats' inspect method failed
[W 171020 14:56:45 control:44] 'active_queues' inspect method failed
[W 171020 14:56:45 control:44] 'registered' inspect method failed
[W 171020 14:56:45 control:44] 'scheduled' inspect method failed
[W 171020 14:56:45 control:44] 'active' inspect method failed
[W 171020 14:56:45 control:44] 'reserved' inspect method failed
[W 171020 14:56:45 control:44] 'revoked' inspect method failed
[W 171020 14:56:45 control:44] 'conf' inspect method failed
[I 171020 15:14:09 command:47] SIGTERM detected, shutting down
[E 171020 15:14:13 signal:232] Signal handler <promise@0x7f60e14ab678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7f60df64dbe0>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171020 15:14:13 command:139] Visit me at http://localhost:5555
[I 171020 15:14:13 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171020 15:14:13 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send_multiple',
'lms.celery.debug_task']
[I 171020 15:14:13 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171020 15:14:15 control:44] 'stats' inspect method failed
[W 171020 15:14:15 control:44] 'active_queues' inspect method failed
[W 171020 15:14:15 control:44] 'registered' inspect method failed
[W 171020 15:14:15 control:44] 'scheduled' inspect method failed
[I 171020 15:18:22 command:47] SIGTERM detected, shutting down
[E 171020 15:18:26 signal:232] Signal handler <promise@0x7ff7eb19f678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7ff7e9341b70>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171020 15:18:26 command:139] Visit me at http://localhost:5555
[I 171020 15:18:26 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171020 15:18:26 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send_multiple',
'lms.celery.debug_task']
[I 171020 15:18:26 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171020 15:18:28 control:44] 'stats' inspect method failed
[W 171020 15:18:28 control:44] 'active_queues' inspect method failed
[W 171020 15:18:28 control:44] 'registered' inspect method failed
[W 171020 15:18:28 control:44] 'scheduled' inspect method failed
[W 171020 15:18:28 control:44] 'active' inspect method failed
[W 171020 15:18:28 control:44] 'reserved' inspect method failed
[W 171020 15:18:28 control:44] 'revoked' inspect method failed
[W 171020 15:18:28 control:44] 'conf' inspect method failed
[I 171020 15:31:13 command:47] SIGTERM detected, shutting down
[E 171020 15:31:17 signal:232] Signal handler <promise@0x7fcacf94e678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7fcacdaf0be0>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171020 15:31:17 command:139] Visit me at http://localhost:5555
[I 171020 15:31:17 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171020 15:31:17 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send_multiple',
'lms.celery.debug_task']
[I 171020 15:31:17 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171020 15:31:19 control:44] 'stats' inspect method failed
[W 171020 15:31:19 control:44] 'active_queues' inspect method failed
[W 171020 15:31:19 control:44] 'registered' inspect method failed
[W 171020 15:31:19 control:44] 'scheduled' inspect method failed
[W 171020 15:31:19 control:44] 'active' inspect method failed
[W 171020 15:31:19 control:44] 'reserved' inspect method failed
[W 171020 15:31:19 control:44] 'revoked' inspect method failed
[W 171020 15:31:19 control:44] 'conf' inspect method failed
[I 171020 16:52:23 command:47] SIGTERM detected, shutting down
[E 171020 16:52:26 signal:232] Signal handler <promise@0x7fb7bea9a678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7fb7bcc3cb70>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171020 16:52:26 command:139] Visit me at http://localhost:5555
[I 171020 16:52:26 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171020 16:52:26 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send',
'lms.celery.debug_task']
[I 171020 16:52:26 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171020 16:52:28 control:44] 'stats' inspect method failed
[W 171020 16:52:28 control:44] 'active_queues' inspect method failed
[W 171020 16:52:28 control:44] 'registered' inspect method failed
[W 171020 16:52:28 control:44] 'scheduled' inspect method failed
[W 171020 16:52:28 control:44] 'active' inspect method failed
[W 171020 16:52:28 control:44] 'reserved' inspect method failed
[W 171020 16:52:28 control:44] 'revoked' inspect method failed
[W 171020 16:52:28 control:44] 'conf' inspect method failed
[I 171020 19:20:51 command:47] SIGTERM detected, shutting down
[E 171023 08:32:22 signal:232] Signal handler <promise@0x7f249b502678 --> <bound method Celery._autodiscover_tasks of <Celery lms at 0x7f24996a4be0>>> raised: ImportError("cannot import name 'TrafSource'",)
Traceback (most recent call last):
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/utils/dispatch/signal.py", line 227, in send
response = receiver(signal=self, sender=sender, **named)
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 143, in __call__
return self.throw()
File "/home/andrey/skillbox/lib/python3.6/site-packages/vine/promises.py", line 140, in __call__
retval = fun(*final_args, **final_kwargs)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 671, in _autodiscover_tasks
return self._autodiscover_tasks_from_fixups(related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 684, in _autodiscover_tasks_from_fixups
], related_name=related_name)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/app/base.py", line 676, in _autodiscover_tasks_from_names
packages() if callable(packages) else packages, related_name,
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 223, in autodiscover_tasks
related_name) if mod)
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in autodiscover_tasks
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 248, in <listcomp>
return [find_related_module(pkg, related_name) for pkg in packages]
File "/home/andrey/skillbox/lib/python3.6/site-packages/celery/loaders/base.py", line 274, in find_related_module
return importlib.import_module('{0}.{1}'.format(package, related_name))
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/andrey/skillbox/go.skillbox.ru/access/tasks.py", line 4, in <module>
from access.models import User, TrafSource, TrafTokenHistory
ImportError: cannot import name 'TrafSource'
[I 171023 08:32:22 command:139] Visit me at http://localhost:5555
[I 171023 08:32:22 command:144] Broker: amqp://guest:**@localhost:5672//
[I 171023 08:32:22 command:147] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'djcelery_email_send',
'lms.celery.debug_task']
[I 171023 08:32:22 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[W 171023 08:32:24 control:44] 'stats' inspect method failed
[W 171023 08:32:24 control:44] 'active_queues' inspect method failed
[W 171023 08:32:24 control:44] 'registered' inspect method failed
[W 171023 08:32:24 control:44] 'scheduled' inspect method failed
[W 171023 08:32:24 control:44] 'active' inspect method failed
[W 171023 08:32:24 control:44] 'reserved' inspect method failed
[W 171023 08:32:24 control:44] 'revoked' inspect method failed
[W 171023 08:32:24 control:44] 'conf' inspect method failed
[E 171023 14:55:56 events:123] Failed to capture events: '[Errno 104] Connection reset by peer', trying again in 1 seconds.
[I 171023 14:56:17 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[E 171023 14:56:17 events:123] Failed to capture events: '[Errno 104] Connection reset by peer', trying again in 1 seconds.
[I 171023 14:56:18 mixins:224] Connected to amqp://guest:**@127.0.0.1:5672//
[I 171023 19:05:28 command:47] SIGTERM detected, shutting down

@ -0,0 +1,121 @@
[2017-10-20 13:12:57,958: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-20 13:12:57
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2017-10-20 13:32:31,865: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-20 13:32:31
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2017-10-20 13:38:00,107: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-20 13:38:00
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2017-10-20 13:47:51,058: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-20 13:47:51
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2017-10-20 14:42:47,578: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-20 14:42:47
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2017-10-20 14:56:42,777: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-20 14:56:42
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2017-10-20 15:14:13,137: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-20 15:14:13
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2017-10-20 15:18:26,051: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-20 15:18:26
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2017-10-20 15:31:17,073: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-20 15:31:17
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2017-10-20 16:52:26,958: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-20 16:52:26
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2017-10-23 08:32:22,755: INFO/MainProcess] beat: Starting...
celery beat v4.1.0 (latentcall) is starting.
__ - ... __ - _
LocalTime -> 2017-10-23 08:32:22
Configuration ->
. broker -> amqp://guest:**@localhost:5672//
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)

@ -1,20 +0,0 @@
# -*- 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='Прохождение было преостановленно'),
),
]

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-04-16 18:50
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('progress', '0009_progress_is_freeze'),
]
operations = [
migrations.AddField(
model_name='progress',
name='exp_date',
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата сгорания'),
),
]

@ -12,9 +12,7 @@ class Progress(models.Model):
user = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Студент') user = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Студент')
course_token = models.UUIDField(verbose_name="Токен курса", editable=False) course_token = models.UUIDField(verbose_name="Токен курса", editable=False)
is_finish = models.BooleanField(verbose_name="Окончен ли курс", default=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) only_watch = models.BooleanField(verbose_name="Только просмотр", default=False)
exp_date = models.DateTimeField(verbose_name='Дата сгорания', blank=True, null=True)
def progress_status(self, sorted_token_list): def progress_status(self, sorted_token_list):
""" """

@ -1,8 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
from progress.models import Progress, ProgressLesson from progress.models import Progress, ProgressLesson
import jwt
from django.conf import settings
class ProgressSerializer(serializers.ModelSerializer): class ProgressSerializer(serializers.ModelSerializer):
@ -17,40 +15,6 @@ class ProgressSerializer(serializers.ModelSerializer):
return [ProgressLessonSerializer(i).data for i in self.progresslesson_set.all()] 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()
class Meta:
model = Progress
fields = ('jwt_token', )
@staticmethod
def get_jwt_token(self):
payload = {
'lessons': [ProgressLessonSerializer(i).data for i in self.progresslesson_set.all()],
'course_token': str(self.course_token),
'only_watch': self.only_watch,
}
return jwt.encode(payload, settings.COURSE_PROGRESS_SECRET_KEY, algorithm='HS256')
class ProgressAnalyticSerializer(serializers.ModelSerializer): class ProgressAnalyticSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField() name = serializers.SerializerMethodField()
email = serializers.SerializerMethodField() email = serializers.SerializerMethodField()
@ -83,8 +47,8 @@ class ProgressLessonSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_student(self): def get_student(self):
return {'name': self.progress.user.get_full_name(), "out_key": str(self.progress.user.out_key)} return {'name': self.progress.user.get_full_name(), "out_key": self.progress.user.out_key}
@staticmethod @staticmethod
def get_course_token(self): def get_course_token(self):
return str(self.progress.course_token) return self.progress.course_token

@ -3,14 +3,9 @@ from django.conf.urls import url
from progress import views from progress import views
urlpatterns = [ urlpatterns = [
url(r'progress_dynamic/$', views.CourseProgressDynamicView.as_view()),
url(r'students/(?P<teacher_token>[0-9A-Fa-f-]+)/$', views.StudentWorkView.as_view()), url(r'students/(?P<teacher_token>[0-9A-Fa-f-]+)/$', views.StudentWorkView.as_view()),
url(r'student/$', views.StudentUpdateProgress.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'teacher/$', views.TeacherUpdateProgress.as_view()),
url(r'set-progress/$', views.SetProgress.as_view()), url(r'set-progress/$', views.SetProgress.as_view()),
url(r'get_hw_pay/$', views.get_teachers_pay), url(r'get_hw_pay/$', views.get_teachers_pay),
url(r'progress_token/$', views.ProgressToken.as_view()),
] ]

@ -1,11 +1,6 @@
import csv import csv
import datetime import datetime
from django.db import IntegrityError
from django.shortcuts import redirect
from dateutil.relativedelta import relativedelta
from django.contrib import auth
from django.utils import timezone
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
@ -15,15 +10,11 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from django.db.models import Q, Count from django.db.models import Q
import jwt
from django.conf import settings
from access.serializers import UserProgressSearchSerializer from courses.models import Course
from courses.models import Course, Lesson
from progress.models import ProgressLesson, Progress from progress.models import ProgressLesson, Progress
from progress.serializers import ProgressAnalyticSerializer, ProgressLessonSerializer, ProgressSerializer, \ from progress.serializers import ProgressAnalyticSerializer, ProgressLessonSerializer, ProgressSerializer
SecureProgressSerializer
from courses.api import CourseProgressApi, CourseParamsApi from courses.api import CourseProgressApi, CourseParamsApi
from progress.tasks import add_next_lesson from progress.tasks import add_next_lesson
@ -64,31 +55,19 @@ class CourseProgressDynamicView(APIView):
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
@staticmethod @staticmethod
def get(request): def get(request, token):
course_token = request.GET.get('course_token', None)
from_date = int(request.GET.get('from', '7'))
to_date = int(request.GET.get('to', '0'))
if request.user.is_authenticated() and request.user.is_staff: if request.user.is_authenticated() and request.user.is_staff:
progresses = ProgressLesson.objects.filter(status='done') try:
if not course_token is None: progresses = Progress.objects.filter(course_token=token)
try: res = {}
progresses = progresses.filter(progress__course_token=course_token) for i in progresses:
except ProgressLesson.DoesNotExist: key = i.progresslesson_set.filter(status="done").count() + 1
return Response('incorrect course token', status=404) res[key] = 1 if not key in res.keys() else res[key] + 1
return Response(res, status=200)
progresses = progresses.filter(finish_date__gt=timezone.now() - relativedelta(days=from_date)) except ValidationError:
progresses = progresses.filter(finish_date__lt=timezone.now() - relativedelta(days=to_date)) return Response("Bad request", status=400)
progresses = progresses.order_by('finish_date')
res = {}
progresses = progresses.extra({'finish_date_day': "date(finish_date)"})\
.values('progress__course_token', 'finish_date_day')\
.annotate(total=Count('progress'))
#TODO Доделать
return Response(res, status=200) return Response(status=403)
return Response('Доступно только персоналу', status=403)
class CourseProgressUserView(APIView): class CourseProgressUserView(APIView):
@ -112,7 +91,6 @@ class CourseProgressUserView(APIView):
class TeacherUpdateProgress(APIView): class TeacherUpdateProgress(APIView):
permission_classes = (permissions.IsAuthenticated, )
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
@staticmethod @staticmethod
@ -133,6 +111,7 @@ class TeacherUpdateProgress(APIView):
student = get_user_model().objects.get(out_key=student_out_key) student = get_user_model().objects.get(out_key=student_out_key)
p = Progress.objects.get( p = Progress.objects.get(
user=student, user=student,
teacher=request.user,
course_token=course_token, course_token=course_token,
) )
@ -147,7 +126,7 @@ class TeacherUpdateProgress(APIView):
pv.status = ProgressLesson.STATUSES.fail pv.status = ProgressLesson.STATUSES.fail
msg = EmailMessage( msg = EmailMessage(
'Ваша работа отправлена на доработку', 'Ваша работа отправлена на доработку',
'''Преподаватель "%s" отклонил вашу работу''' % request.user.get_full_name(), '''Преподователь "%s" отклонил вашу работу''' % request.user.get_full_name(),
'robo@skillbox.ru', 'robo@skillbox.ru',
[student.email], [student.email],
) )
@ -196,52 +175,47 @@ class StudentUpdateProgress(APIView):
@staticmethod @staticmethod
def post(request): def post(request):
lesson_token = request.JSON.get('lesson_token', None) lesson_token = request.JSON.get('lesson_token', None)
course_token = request.JSON.get('course_token', None)
comment = request.JSON.get('comment', None) comment = request.JSON.get('comment', None)
if lesson_token is None: if lesson_token is None or course_token is None:
return Response('Не передан токен урока', status=400) return Response('Не передан слаг курса или токен урока', status=400)
try:
student = request.user
student = request.user p = Progress.objects.get(user=student, course_token=course_token)
try: try:
pv = ProgressLesson.objects.get( pv = ProgressLesson.objects.get(
progress__user=student, progress=p,
lesson_token=lesson_token, lesson_token=lesson_token,
) )
if pv.status == ProgressLesson.STATUSES.done: if not pv.status == ProgressLesson.STATUSES.wait:
return Response(SecureProgressSerializer(pv.progress).data, status=200) if pv.checker == p.teacher and not comment is None:
pv.status = ProgressLesson.STATUSES.wait
pv.comment_tokens.append(comment)
if not pv.status == ProgressLesson.STATUSES.wait: elif pv.checker == p.user:
if pv.checker == pv.progress.user: pv.status = ProgressLesson.STATUSES.done
pv.status = ProgressLesson.STATUSES.done pv.finish_date = datetime.datetime.now()
pv.finish_date = datetime.datetime.now()
elif not comment is None and\ else:
not pv.progress.progresslesson_set.filter(status=ProgressLesson.STATUSES.wait).exists(): raise ValueError("Этого никогда не должно происходить, но я уверен, что произойдёт")
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: pv.save()
return Response("Не преложен комментарий", status=400)
else: else:
return Response("В настоящее время, мы уже проверяем одно из ваших домашних заданий. <br> Как " return Response("Ошибка прав доступа", status=403)
"только оно будет успешно сдано - вы сможете продолжить.", status=403)
elif not comment is None: except ProgressLesson.DoesNotExist:
pv.comment_tokens.append(comment) return Response('Урок не проходится этим пользователем', status=403)
pv.save() if pv.status == ProgressLesson.STATUSES.done:
# TODO: Ассинхроннаязадача для celery
add_next_lesson(p)
return Response(SecureProgressSerializer(pv.progress).data, status=200) return Response(ProgressSerializer(p).data, status=200)
except Progress.DoesNotExist: except Progress.DoesNotExist:
return Response('Не найден прогресс по заданным параметрам', status=404) return Response('Не найден прогресс по заданным параметрам', status=404)
@ -275,74 +249,36 @@ class UploadCourseProgressUserView(APIView):
return Response(status=403) return Response(status=403)
class FindProgressView(APIView): class UserGuardView(APIView):
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
@staticmethod @staticmethod
def get(request): def get(request, pk, page):
if not request.user.is_staff:
return Response("Только сотрудники персонала могут запрашивать инфо по прогрессу", status=403)
key = request.GET.get('key', None)
count = int(request.GET.get('count', '10'))
if key:
res = get_user_model().objects.filter(
Q(id__contains=key) | Q(email__contains=key.lower()) | Q(first_name__contains=key) |
Q(last_name__contains=key) | Q(account__phone__contains=key)
)
else:
res = get_user_model().objects.all()
res = res[:(count if len(res) > count else len(res))]
return Response([UserProgressSearchSerializer(i).data for i in res], status=200)
class FreezeProgressView(APIView):
renderer_classes = (JSONRenderer,)
permission_classes = (IsAuthenticated,)
@staticmethod
def post(request):
if not request.user.is_staff:
return Response("Только сотрудники персонала могут вносить изменение в прогресс", status=403)
key = request.JSON.get('id', None)
is_freeze = request.JSON.get('is_freeze', False)
try: try:
p = Progress.objects.get(id=key) user = get_user_model().objects.get(out_key=pk)
p.is_freeze = is_freeze except get_user_model().DoesNotExist:
p.save() return Response("User doesn't exist", status=404)
except Progress.DoesNotExist:
return Response("не найден прогресс по заданному id", status=404)
return Response(status=204) is_i = request.user == user
res_403 = Response('Permission denied', status=403)
res_204 = Response(status=204)
if is_i and not request.user.groups.filter(name='teachers').exists() and page == 'homeworks':
return res_403
class ChangeTeacherView(APIView): if is_i and not \
renderer_classes = (JSONRenderer,) request.user.groups.filter(name__in=['students', 'managers', 'lead_managers']).exists() \
permission_classes = (IsAuthenticated,) and page == 'payment':
return res_403
@staticmethod if is_i:
def post(request): return res_204
if not request.user.is_staff:
return Response("Только сотрудники персонала могут вносить изменение в прогресс", status=403)
key = request.JSON.get('id', None)
teacher_email = request.JSON.get('teacher_email', False)
try: if page == 'profile' and (request.user.is_superuser or request.user.is_staff):
p = Progress.objects.get(id=key) return res_204
try:
teacher = get_user_model().objects.get(email=teacher_email.lower())
except get_user_model().DoesNotExist:
return Response("Нет пользователя c таким email", status=404)
p.teacher = teacher
p.progresslesson_set.filter(status=ProgressLesson.STATUSES.wait).update(checker=teacher)
p.save()
except Progress.DoesNotExist:
return Response("не найден прогресс по заданному id", status=404)
return Response(status=204) return res_403
class SetProgress(APIView): class SetProgress(APIView):
@ -356,7 +292,6 @@ class SetProgress(APIView):
course_slug = request.JSON.get('course_slug', None) course_slug = request.JSON.get('course_slug', None)
topic_sort = int(request.JSON.get('topic', 1)) topic_sort = int(request.JSON.get('topic', 1))
lesson_sort = int(request.JSON.get('lesson', 1)) lesson_sort = int(request.JSON.get('lesson', 1))
only_watch = request.JSON.get('only_watch', False)
force = request.JSON.get('force', False) force = request.JSON.get('force', False)
if course_slug is None: if course_slug is None:
@ -384,9 +319,6 @@ class SetProgress(APIView):
teacher = get_user_model().objects.get(out_key=course.get_teacher()) teacher = get_user_model().objects.get(out_key=course.get_teacher())
progress = Progress.objects.create(course_token=course.token, user=student, teacher=teacher) progress = Progress.objects.create(course_token=course.token, user=student, teacher=teacher)
progress.only_watch = only_watch
progress.save()
token_list = [] token_list = []
lesson_list = [] lesson_list = []
for topic_idx, topic in enumerate(course.topic_set.all()): for topic_idx, topic in enumerate(course.topic_set.all()):
@ -467,63 +399,16 @@ def get_teachers_pay(request):
response['Content-Disposition'] = 'attachment; filename="%s.csv"' % file_name response['Content-Disposition'] = 'attachment; filename="%s.csv"' % file_name
writer = csv.writer(response) writer = csv.writer(response)
writer.writerow(['Почта студента', 'Имя студента', 'Тема', 'Курс', 'Дата', 'Время',]) writer.writerow(['Почта студента', 'Имя студента', 'Курс', 'Дата', 'Время',])
for i in progress_lessons.order_by('-finish_date'): for i in progress_lessons.order_by('-finish_date'):
course_api = CourseParamsApi(i.progress.course_token) course_api = CourseParamsApi(i.progress.course_token)
writer.writerow([ writer.writerow([
i.progress.user.email, i.progress.user.email,
i.progress.user.get_full_name(), i.progress.user.get_full_name(),
#TODO очередной костыль
Lesson.objects.get(token=i.lesson_token).topic.title,
course_api.get_slug_and_title()['title'], course_api.get_slug_and_title()['title'],
i.finish_date.date(), i.finish_date.date(),
i.finish_date.time(), i.finish_date.time(),
]) ])
return response return response
class ProgressToken(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def get(request):
token = request.GET.get('token', None)
if token is None:
return Response("bad request", 400)
payload = jwt.decode(token, settings.COURSE_PROGRESS_SECRET_KEY, algorithms=['HS256'])
try:
user = get_user_model().objects.get(email=payload['email'].lower())
except get_user_model().DoesNotExist:
user = get_user_model().objects.create_student(email=payload['email'].lower())
user.is_active = True
user.save()
try:
course = Course.objects.get(token=payload['course_token'])
except get_user_model().DoesNotExist:
return Response("Course doesn't exist", status=404)
teacher = get_user_model().objects.get(out_key=course.get_teacher())
try:
p = Progress.objects.create(
user=user,
course_token=payload['course_token'],
teacher=teacher,
exp_date=timezone.now() + relativedelta(days=int(payload['period'])),
)
ProgressLesson.objects.create(
progress=p,
lesson_token=course.get_first_lesson().token,
checker=p.user,
)
except IntegrityError:
pass
auth.login(request, user)
return redirect('/')

@ -1,6 +1,3 @@
import base64
import json
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
@ -47,19 +44,12 @@ class CommentView(APIView):
@staticmethod @staticmethod
def get(request): def get(request):
base64_tokens = request.GET.get('base64_tokens', None) token = request.GET.get('token', None)
if not base64_tokens: if not token:
return Response("Attribute token not set", status=400) return Response("Attribute token not set", status=400)
tokens = json.loads(base64.b64decode(base64_tokens).decode('utf-8')) try:
comments = [] return Response(CommentSerializer(Comment.objects.get(token=token)).data, status=200)
except Comment.DoesNotExist:
for token in tokens: return Response("Comment not found", status=404)
try:
comment = Comment.objects.get(token=token)
comments.append(comment)
except Comment.DoesNotExist:
pass
return Response([CommentSerializer(comment).data for comment in comments], status=200)

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

@ -1,7 +0,0 @@
<div> Успешный платёж </div>
<div> Пользователь: {{ user_email }} </div>
<div> Продавец: {{ opener_full_name }} </div>
<div> Курс: {{ course_title }} </div>
<div> Дата продажи: {{ date }} </div>
<div> Дата оплаты: {{ finish_date }} </div>
<div> Сумма: {{ price }} руб. </div>

@ -1,57 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Тестовый платёж</title>
</head>
<body>
<div>
Номер карты: 4444 4444 4444 4448 <br>
Действует до: любой год и месяц в будущем <br>
Код CVV: 000 <br>
Больше инфы
<a href="https://tech.yandex.ru/money/doc/payment-solution/examples/examples-test-data-docpage/" target="_blank">
В доке
</a><br>
Отправление формы срабатывает только раз затем нужно будет заменить orderNumber
</div>
<br><br><br>
<form action="https://demomoney.yandex.ru/eshop.xml" method="post" target="_blank">
<table>
<tr>
<td>shopId</td>
<td><input name="shopId" value="{{ shop_id }}" type="text"></td>
</tr>
<tr>
<td>scid</td>
<td><input name="scid" value="{{ sc_id }}" type="text"></td>
</tr>
<tr>
<td>sum</td>
<td><input name="sum" value="100.50" type="text"></td>
</tr>
<tr>
<td>customerNumber</td>
<td><input name="customerNumber" value="abc000" type="text"/></td>
</tr>
<tr>
<td>orderNumber</td>
<td><input name="orderNumber" value="{{ order_number }}" type="text"/></td>
</tr>
<tr>
<td>cps_phone</td>
<td><input name="cps_phone" value="79110000000" type="text"/></td>
</tr>
<tr>
<td>cps_email</td>
<td><input name="cps_email" value="{{ user_email }}" type="text"/></td>
</tr>
<tr>
<td>rebillingOn</td>
<td><input name="rebillingOn" value="false" type="text"/></td>
</tr>
</table>
<input type="submit" value="Заплатить"/>
</form>
</body>
</html>
Loading…
Cancel
Save