From 86214d8e273a0e63cf1a73182e746786c0e87310 Mon Sep 17 00:00:00 2001
From: gzbender
Date: Thu, 26 Sep 2019 09:20:06 +0300
Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB?=
=?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=B1=20=D0=BE=D0=BA=D0=BE?=
=?UTF-8?q?=D0=BD=D1=87=D0=B0=D0=BD=D0=B8=D0=B8=20=D0=B4=D0=BE=D1=81=D1=82?=
=?UTF-8?q?=D1=83=D0=BF=D0=B0=20=D0=BA=20=D0=BA=D1=83=D1=80=D1=81=D1=83?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../migrations/0031_auto_20190925_1723.py | 24 ++++++++++
apps/course/templates/course/course.html | 6 +--
apps/course/views.py | 3 +-
.../send_course_access_expire_email.py | 19 ++++++++
.../migrations/0003_coursenotification.py | 26 ++++++++++
apps/notification/models.py | 7 ++-
apps/notification/tasks.py | 29 +++++++++++-
.../notification/email/buy_email.html | 22 +++++----
.../email/course_access_expire.html | 8 ++++
.../migrations/0039_auto_20190925_1723.py | 24 ++++++++++
apps/payment/models.py | 4 ++
.../payment/gift_certificate_item.html | 2 +-
apps/payment/views.py | 4 +-
.../migrations/0035_auto_20190925_1723.py | 23 +++++++++
project/settings.py | 5 ++
web/src/sass/_common.sass | 47 ++++++++++---------
16 files changed, 213 insertions(+), 40 deletions(-)
create mode 100644 apps/content/migrations/0031_auto_20190925_1723.py
create mode 100644 apps/notification/management/commands/send_course_access_expire_email.py
create mode 100644 apps/notification/migrations/0003_coursenotification.py
create mode 100644 apps/notification/templates/notification/email/course_access_expire.html
create mode 100644 apps/payment/migrations/0039_auto_20190925_1723.py
create mode 100644 apps/user/migrations/0035_auto_20190925_1723.py
diff --git a/apps/content/migrations/0031_auto_20190925_1723.py b/apps/content/migrations/0031_auto_20190925_1723.py
new file mode 100644
index 00000000..71067608
--- /dev/null
+++ b/apps/content/migrations/0031_auto_20190925_1723.py
@@ -0,0 +1,24 @@
+# Generated by Django 2.0.7 on 2019-09-25 17:23
+
+from django.db import migrations
+import project.utils.db
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('content', '0030_auto_20190809_0133'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='banner',
+ name='image',
+ field=project.utils.db.SafeImageField(upload_to=''),
+ ),
+ migrations.AlterField(
+ model_name='imageobject',
+ name='image',
+ field=project.utils.db.SafeImageField(upload_to='content/imageobject', verbose_name='Изображение'),
+ ),
+ ]
diff --git a/apps/course/templates/course/course.html b/apps/course/templates/course/course.html
index db228e96..adc2de97 100644
--- a/apps/course/templates/course/course.html
+++ b/apps/course/templates/course/course.html
@@ -111,13 +111,13 @@
diff --git a/apps/course/views.py b/apps/course/views.py
index d4bd0298..aed63b75 100644
--- a/apps/course/views.py
+++ b/apps/course/views.py
@@ -246,9 +246,8 @@ class CourseView(DetailView):
])
payment = payments.filter(access_expire__gte=now().date()).order_by('-access_expire').first()
context['payment'] = payment
- context['access_duration'] = ((payment.access_expire - now().date()).days + 1) if payment else self.object.access_duration
context['paid'] = bool(payment)
- context['can_buy_again'] = bool(self.object.price) and (context['access_duration'] <= 7 if payment else
+ context['can_buy_again'] = bool(self.object.price) and (payment.access_duration <= 7 if payment else
payments.filter(access_expire__lt=now().date()).exists())
context['pending'] = self.object.payments.filter(
user=self.request.user,
diff --git a/apps/notification/management/commands/send_course_access_expire_email.py b/apps/notification/management/commands/send_course_access_expire_email.py
new file mode 100644
index 00000000..3e358383
--- /dev/null
+++ b/apps/notification/management/commands/send_course_access_expire_email.py
@@ -0,0 +1,19 @@
+from django.core.management.base import BaseCommand
+
+from apps.notification.tasks import send_course_access_expire_email
+
+
+class Command(BaseCommand):
+ help = 'Send course access expire email'
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '--days',
+ dest='days',
+ type=int,
+ help='Days before access expired',
+ )
+
+ def handle(self, *args, **options):
+ send_course_access_expire_email(options.get('days'))
+
diff --git a/apps/notification/migrations/0003_coursenotification.py b/apps/notification/migrations/0003_coursenotification.py
new file mode 100644
index 00000000..e48a63c7
--- /dev/null
+++ b/apps/notification/migrations/0003_coursenotification.py
@@ -0,0 +1,26 @@
+# Generated by Django 2.0.7 on 2019-09-25 17:23
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('course', '0050_auto_20190818_1043'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('notification', '0002_usernotification_camp_certificate_last_email'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CourseNotification',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('access_expire_last_email', models.DateTimeField(blank=True, null=True)),
+ ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.Course')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/apps/notification/models.py b/apps/notification/models.py
index c60c91cc..6770745c 100644
--- a/apps/notification/models.py
+++ b/apps/notification/models.py
@@ -1,7 +1,6 @@
from django.db import models
from django.contrib.auth import get_user_model
-
User = get_user_model()
@@ -10,3 +9,9 @@ class UserNotification(models.Model):
certificate_number = models.SmallIntegerField(blank=True, null=True)
certificate_last_email = models.DateTimeField(blank=True, null=True)
camp_certificate_last_email = models.DateTimeField(blank=True, null=True)
+
+
+class CourseNotification(models.Model):
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ course = models.ForeignKey('course.Course', on_delete=models.CASCADE)
+ access_expire_last_email = models.DateTimeField(blank=True, null=True)
diff --git a/apps/notification/tasks.py b/apps/notification/tasks.py
index 791ed307..759ba298 100644
--- a/apps/notification/tasks.py
+++ b/apps/notification/tasks.py
@@ -12,7 +12,7 @@ from django.utils.timezone import now
from django.conf import settings
from django.utils.text import slugify
-from apps.notification.models import UserNotification
+from apps.notification.models import UserNotification, CourseNotification
from apps.notification.utils import send_email
from apps.payment.models import SchoolPayment, CoursePayment, Payment, UserGiftCertificate, UserBonus, DrawingCampPayment
from project.celery import app
@@ -245,3 +245,30 @@ def send_camp_certificates(email=None, dry_run=False, certificate_number=None):
for fn in file_names:
if os.path.isfile(fn):
os.remove(fn)
+
+
+@app.task
+def send_course_access_expire_email(days=None):
+ if days is None:
+ days = 30
+ payments = CoursePayment.objects.paid().filter(access_expire=now() + timedelta(days - 1), created_at__lte=now() - timedelta(10))
+ for payment in payments:
+ cn, created = CourseNotification.objects.get_or_create(course=payment.course, user=payment.user)
+ if not created and cn.access_expire_last_email and cn.access_expire_last_email.date() >= now().date() - timedelta(days):
+ continue
+ print(payment.user.email)
+ try:
+ #payment.user.email
+ send_email('Доступ к курсу скоро закончится', 'gzbender74@gmail.com', 'notification/email/course_access_expire.html',
+ username=payment.user.get_full_name(), course_title=payment.course.title,
+ access_duration=payment.access_duration)
+ except:
+ print('Not OK')
+ continue
+ else:
+ cn.access_expire_last_email = now()
+ cn.save()
+ user_courses = dict(CoursePayment.objects.paid().filter(access_expire__lt=now().date()).values_list('user_id', 'course_id'))
+ for cn in CourseNotification.objects.filter(course_id__in=user_courses.values(), user_id__in=user_courses.keys()):
+ if user_courses.get(cn.user_id) == cn.course_id:
+ cn.delete()
diff --git a/apps/notification/templates/notification/email/buy_email.html b/apps/notification/templates/notification/email/buy_email.html
index 5733ed7a..fb4393df 100644
--- a/apps/notification/templates/notification/email/buy_email.html
+++ b/apps/notification/templates/notification/email/buy_email.html
@@ -1,21 +1,23 @@
{% extends "notification/email/_base.html" %}
-{% load settings %}
+{% load rupluralize from plural %}
{% block content %}
{% if product_type == 'course' %}
- Добрый день! Спасибо за покупку знаний в «Lil School»!
- Где искать уроки?
- После оплаты курс появится в вашем личном кабинете на платформе.
-
- https://{% setting 'MAIN_HOST' %}{{ url }}
+ Добрый день, {{ username }}!
+ Вы приобрели видеокурс «{{ course_title }}». Спасибо за покупку!
+ Обратите внимание, что доступ к курсу действует {{ access_duration | rupluralize:'день,дня,дней' }}. Этого должно хватить, чтобы пройти его.
+ Если вы не успеете, то по окончании этого срока система автоматически предложит вам продлить доступ к курсу за 50% от его стоимости./p>
+
Найти курс можно в вашем личном кабинете на платформе.
+
+ https://{{ settings.MAIN_HOST }}{{ url }}
Все ваши покупки будут храниться там в рамках срока доступа к курсу.
{% endif %}
{% if product_type == 'school' %}
Добрый день! Спасибо за покупку знаний в «Lil School»!
Где искать уроки?
После оплаты уроки появятся в вашем личном кабинете на платформе.
-
- https://{% setting 'MAIN_HOST' %}{% url 'school:school' %}
+
+ https://{{ settings.MAIN_HOST }}{% url 'school:school' %}
В онлайн-школе урок хранится неделю. Ровно до следующего урока.
{% endif %}
{% if product_type == 'drawing_camp' %}
@@ -76,8 +78,8 @@
Обязательно делитесь своими впечатлениями и работами, отмечая их хэштегом #lil_summer. Спасибо, что вы с нами!
{% else %}
- Рисовальный лагерь ждет вас по ссылке
- https://{% setting 'MAIN_HOST' %}{% url 'school:drawing-camp' %}
+ Рисовальный лагерь ждет вас по ссылке
+ https://{{ settings.MAIN_HOST }}{% url 'school:drawing-camp' %}
{% endif %}
{% endif %}
diff --git a/apps/notification/templates/notification/email/course_access_expire.html b/apps/notification/templates/notification/email/course_access_expire.html
new file mode 100644
index 00000000..0c8f22f3
--- /dev/null
+++ b/apps/notification/templates/notification/email/course_access_expire.html
@@ -0,0 +1,8 @@
+{% load rupluralize from plural %}
+Добрый день, {{ username }}!
+
+Доступ к видеокурсу «{{ course_title }}», который вы приобретали на платформе lil.school,
+ заканчивается через {{ access_duration|rupluralize:'день,дня,дней' }}. Если вы ещё не успели пройти этот курс, самое время это сделать.
+ По окончании этого срока система автоматически предложит вам продлить доступ к курсу за 50% от его стоимости.
+
+Команда «Lil School».
diff --git a/apps/payment/migrations/0039_auto_20190925_1723.py b/apps/payment/migrations/0039_auto_20190925_1723.py
new file mode 100644
index 00000000..d04deda6
--- /dev/null
+++ b/apps/payment/migrations/0039_auto_20190925_1723.py
@@ -0,0 +1,24 @@
+# Generated by Django 2.0.7 on 2019-09-25 17:23
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('payment', '0038_auto_20190814_1506'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='userbonus',
+ name='payment',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='payment.Payment'),
+ ),
+ migrations.AlterField(
+ model_name='userbonus',
+ name='referral',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='user.Referral'),
+ ),
+ ]
diff --git a/apps/payment/models.py b/apps/payment/models.py
index 47af6711..86f40dfc 100644
--- a/apps/payment/models.py
+++ b/apps/payment/models.py
@@ -325,6 +325,10 @@ class CoursePayment(Payment):
verbose_name = 'Платеж за курс'
verbose_name_plural = 'Платежи за курсы'
+ @property
+ def access_duration(self):
+ return (self.access_expire - now().date()).days + 1
+
class SchoolPayment(Payment):
weekdays = ArrayField(models.IntegerField(), size=7, verbose_name='Дни недели')
diff --git a/apps/payment/templates/payment/gift_certificate_item.html b/apps/payment/templates/payment/gift_certificate_item.html
index 7d4d3d8f..f9e92e8d 100644
--- a/apps/payment/templates/payment/gift_certificate_item.html
+++ b/apps/payment/templates/payment/gift_certificate_item.html
@@ -4,7 +4,7 @@
-

+
diff --git a/apps/payment/views.py b/apps/payment/views.py
index d9a4edc3..c57c005f 100644
--- a/apps/payment/views.py
+++ b/apps/payment/views.py
@@ -385,7 +385,9 @@ class PaymentwallCallbackView(View):
if product_type_name == 'course':
send_email.delay('Спасибо за покупку!', payment.user.email, 'notification/email/buy_email.html',
- product_type=product_type_name, url=payment.course.url)
+ product_type=product_type_name, url=payment.course.url,
+ username=payment.user.get_full_name(), course_title=payment.course.title,
+ access_duration=payment.access_duration)
elif product_type_name != 'school':
send_email.delay('Спасибо за покупку!', payment.user.email, 'notification/email/buy_email.html',
product_type=product_type_name)
diff --git a/apps/user/migrations/0035_auto_20190925_1723.py b/apps/user/migrations/0035_auto_20190925_1723.py
new file mode 100644
index 00000000..063c816f
--- /dev/null
+++ b/apps/user/migrations/0035_auto_20190925_1723.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.0.7 on 2019-09-25 17:23
+
+from django.db import migrations
+import project.utils.db
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('user', '0034_auto_20190612_1852'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='child',
+ options={'ordering': ('id',)},
+ ),
+ migrations.AlterField(
+ model_name='user',
+ name='photo',
+ field=project.utils.db.SafeImageField(blank=True, null=True, upload_to='users', verbose_name='Фото'),
+ ),
+ ]
diff --git a/project/settings.py b/project/settings.py
index a56c9463..c06e3a55 100644
--- a/project/settings.py
+++ b/project/settings.py
@@ -357,6 +357,11 @@ CELERY_BEAT_SCHEDULE = {
'schedule': crontab(0, 9, day_of_month='1', month_of_year='9'),
'kwargs': {'certificate_number': 3},
},
+ 'send_course_access_expire_email': {
+ 'task': 'apps.notification.tasks.send_course_access_expire_email',
+ 'schedule': crontab(minute=0, hour=10),
+ 'args': (),
+ },
}
try:
diff --git a/web/src/sass/_common.sass b/web/src/sass/_common.sass
index bb6a4d07..82a81309 100755
--- a/web/src/sass/_common.sass
+++ b/web/src/sass/_common.sass
@@ -2831,11 +2831,10 @@ a.grey-link
display: flex
align-items: center
color: inherit
- &__item
- //&:not(:last-child)
margin-right: 40px
+t
margin-right: 20px
+ margin-bottom: 10px
&__icon
margin-right: 10px
font-size: 0
@@ -2923,6 +2922,7 @@ a.grey-link
margin-bottom: 15px
&__metas &__meta
+m
+ flex-wrap: wrap
margin-bottom: 20px
&__actions
display: flex
@@ -4857,37 +4857,40 @@ a
.gift-certificates
display: flex
- margin: 0 -10px
+ margin: 0 -20px
flex-wrap: wrap
+m
display: block
margin: 0
&__item
display: block
- margin: 0 10px 60px
+ margin: 0 20px 75px
color: $cl
- flex: 0 0 calc(33.33% - 20px)
+ flex: 0 0 288px
+t
margin-bottom: 50px !important
+m
- margin: 0 0 30px
+ margin: 0 5px 30px
+ flex: 0 0 calc(50% - 15px)
&__preview
display: block
position: relative
margin-bottom: 15px
- border-radius: 2px
+ border-radius: 10px
color: $cl
overflow: hidden
- width: 300px
+ width: 288px
height: 200px
+t
margin-bottom: 10px
+ &:hover
+ box-shadow: 0 2px 14px 0 rgba(0, 0, 0, 0.15)
+m
- margin-left: auto
- margin-right: auto
+ height: 120px
+ width: auto
&__cover
- object-fit: cover;
- width: 100%;
+ object-fit: cover
+ width: 100%
&__details
display: flex
margin-bottom: 10px
@@ -4899,16 +4902,18 @@ a
color: $cl
&__title
text-transform: uppercase
+ flex: 1
+ +m
+ font-size: 10px
&__status
- font-family: 'ProximaNova-Bold', serif
- font-size: 12px
- letter-spacing: 2px
- text-transform: uppercase
- & .icon
- width: 16px
- display: inline-block
- height: 16px
- margin-bottom: -4px
+ display: block
+ margin-bottom: 10px
+ font-size: 18px
+ color: $cl
+ +t
+ line-height: 1.33
+ +m
+ font-size: 13px
&__buy-btn
width: 100%
&__preview.theme_pink2