Merge branch 'master' of gitlab.com:lilcity/backend into feature/LIL-684

remotes/origin/feature/LIL-711
gzbender 7 years ago
commit abbfc01d8f
  1. 9
      api/v1/serializers/content.py
  2. 19
      api/v1/serializers/course.py
  3. 21
      api/v1/views.py
  4. 1
      apps/course/templates/course/_items.html
  5. 64
      apps/course/templates/course/course.html
  6. 42
      apps/course/templates/course/course_only_lessons.html
  7. 3
      apps/course/templates/course/lesson.html
  8. 15
      apps/notification/tasks.py
  9. 83
      apps/notification/templates/notification/email/certificate.html
  10. 112
      apps/notification/templates/notification/email/gift_certificate.html
  11. 23
      apps/notification/utils.py
  12. 7
      apps/payment/admin.py
  13. 34
      apps/payment/migrations/0025_giftcertificate_usergiftcertificate.py
  14. 39
      apps/payment/migrations/0026_auto_20181101_1546.py
  15. 22
      apps/payment/migrations/0027_auto_20181109_1402.py
  16. 21
      apps/payment/migrations/0028_add_gift_certificates.py
  17. 38
      apps/payment/models.py
  18. 17
      apps/payment/templates/payment/gift_certificate.html
  19. 15
      apps/payment/templates/payment/gift_certificate_get.html
  20. 36
      apps/payment/templates/payment/gift_certificate_item.html
  21. 4
      apps/payment/templates/payment/gift_certificate_items.html
  22. 13
      apps/payment/templates/payment/gift_certificate_payment_success.html
  23. 35
      apps/payment/templates/payment/gift_certificates.html
  24. 11
      apps/payment/templates/payment/payment_success.html
  25. 101
      apps/payment/views.py
  26. 12
      apps/school/templates/school/livelesson_detail.html
  27. 27
      apps/school/templates/school/livelesson_detail_unauth.html
  28. 3
      apps/school/templates/summer/promo.html
  29. 30
      apps/school/views.py
  30. 13
      apps/user/templates/user/profile.html
  31. 3
      apps/user/views.py
  32. 2
      docker/.env.example
  33. 2
      docker/.env.review
  34. 4
      project/settings.py
  35. 6
      project/templates/blocks/baner.html
  36. 4
      project/templates/blocks/header.html
  37. 1
      project/templates/blocks/lil_store_js.html
  38. 24
      project/templates/blocks/popup_enter_gift_code.html
  39. 31
      project/templates/blocks/popup_gift_certificate.html
  40. 9
      project/templates/blocks/promo.html
  41. 10
      project/templates/lilcity/index.html
  42. 9
      project/urls.py
  43. 13
      project/views.py
  44. 1
      requirements.txt
  45. BIN
      web/src/img/clock.png
  46. BIN
      web/src/img/done.png
  47. BIN
      web/src/img/fb.png
  48. BIN
      web/src/img/gift-certificates/1000.jpg
  49. BIN
      web/src/img/gift-certificates/10000.jpg
  50. BIN
      web/src/img/gift-certificates/2000.jpg
  51. BIN
      web/src/img/gift-certificates/3000.jpg
  52. BIN
      web/src/img/gift-certificates/5000.jpg
  53. BIN
      web/src/img/instagram.png
  54. BIN
      web/src/img/twitter.png
  55. 23
      web/src/js/modules/popup.js
  56. 104
      web/src/sass/_common.sass
  57. 9
      web/webpack.config.js

@ -1,5 +1,6 @@
from urllib.parse import urlparse, urlunparse
from rest_framework import serializers
from django.conf import settings
from apps.content.models import (
Baner, Content, Image, Text, ImageText, Video,
@ -46,12 +47,8 @@ class BanerSerializer(serializers.ModelSerializer):
)
def get_image(self, baner):
request = self.context.get('request')
http_host = request.META.get('HTTP_ORIGIN')
if http_host and baner.image:
domain = urlparse(http_host).netloc.split(':')[0]
baner_url = 'http://' + domain + baner.image.url
return baner_url
if baner.image:
return 'http://' + settings.MAIN_HOST + '/' + baner.image.url
else:
return None

@ -1,9 +1,8 @@
from django.contrib.auth import get_user_model
from ipware import get_client_ip
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from django.contrib.auth import get_user_model
from drf_dynamic_fields import DynamicFieldsMixin
from apps.course.models import (
Category, Course,
@ -13,17 +12,9 @@ from apps.course.models import (
LiveLessonComment)
from .content import (
ImageObjectSerializer, ContentSerializer, ContentCreateSerializer,
GallerySerializer, GalleryImageSerializer,
)
from apps.content.models import (
Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject,
ContestWork)
from .user import UserSerializer
GallerySerializer, )
from .mixins import DispatchContentMixin, DispatchGalleryMixin, DispatchMaterialMixin
from .user import UserSerializer
User = get_user_model()
@ -269,7 +260,7 @@ class LessonSerializer(serializers.ModelSerializer):
)
class CourseSerializer(serializers.ModelSerializer):
class CourseSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
author = UserSerializer()
category = CategorySerializer()
materials = MaterialSerializer(many=True)

@ -19,7 +19,7 @@ from .serializers.course import (
MaterialSerializer, MaterialCreateSerializer,
LessonSerializer, LessonCreateSerializer,
LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer,
LiveLessonCommentSerializer)
LiveLessonCommentSerializer,)
from .serializers.content import (
BanerSerializer,
ImageSerializer, ImageCreateSerializer,
@ -37,8 +37,7 @@ from .serializers.school import (
)
from .serializers.payment import (
AuthorBalanceSerializer, AuthorBalanceCreateSerializer,
PaymentSerializer, CoursePaymentSerializer,
SchoolPaymentSerializer,
PaymentSerializer,
CoursePaymentCreateSerializer, SchoolPaymentCreateSerializer)
from .serializers.user import (
AuthorRequestSerializer,
@ -49,7 +48,7 @@ from .serializers.contest import (
)
from .permissions import (
IsAdmin, IsAdminOrIsSelf,
IsAdmin,
IsAuthorOrAdmin, IsAuthorObjectOrAdmin,
IsTeacherOrAdmin,
)
@ -242,6 +241,20 @@ class CourseViewSet(ExtendedModelViewSet):
# 'delete': IsAdmin,
# }
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
if request.query_params.get('page'):
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
else:
return Response({'results': []})
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@list_route(methods=['get'])
def draft(self, request):
drafts = Course.objects.filter(author=request.user, status=Course.DRAFT)

@ -73,7 +73,6 @@
<div class="user__name">{{ course.author.get_full_name }}</div>
</a>
<div class="user__meta">
<div class="user__date">{{ course.created_at_humanize }}</div>
{% data_liked user course as liked %}
<a class="user__likes likes{% if liked %} active{% endif %}"
{% if not user.is_authenticated %}data-popup=".js-popup-auth"{% endif %}

@ -8,9 +8,18 @@
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ course.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% if course.cover and course.cover.image %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %}
{% endif %}
{% block ogimage %}
http://{{request.META.HTTP_HOST}}{% if course.cover and course.cover.image %}{{ course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}
{% endblock ogimage %}
{% block ogimage-width %}
{% if course.cover and course.cover.image %}{{ course.cover.image.width }}{% else %}1024{% endif %}
{% endblock ogimage-width %}
{% block ogimage-height %}
{% if course.cover and course.cover.image %}{{ course.cover.image.height }}{% else %}512{% endif %}
{% endblock ogimage-height %}
{% block ogdescription %}{{ course.short_description | striptags }}{% endblock ogdescription %}
{% block content %}
@ -29,17 +38,22 @@
<a class="go__btn btn btn_light-gray" href="{% url 'course_edit' course.id %}">Редактировать</a>
{% endif %}
{% if course.author != request.user and not paid and course.price %}
<a href="#"
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}
{% if not pending %}
data-course-buy
data-popup=".js-popup-course-buy"
{% endif %}
{% else %}
data-popup=".js-popup-auth"
<div>
<a href="#"
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}
{% if not pending %}
data-course-buy
data-popup=".js-popup-course-buy"
{% endif %}
{% else %}
data-popup=".js-popup-auth"
{% endif %}
>{% if pending %}ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ ОПЛАТЫ{% else %}КУПИТЬ КУРС{% endif %}</a>
{% if not paid %}
<a class="main__btn btn btn_stroke-black" href="{% url 'gift-certificates' %}">Подарить другу</a>
{% endif %}
>{% if pending %}ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ ОПЛАТЫ{% else %}КУПИТЬ КУРС{% endif %}</a>
</div>
{% endif %}
</div>
<div
@ -86,22 +100,19 @@
{% endif %}
<div class="user__info">
<div class="user__name">{{ course.author.get_full_name }}</div>
<div class="user__meta">
<div class="user__date">{{ course.created_at_humanize }}</div>
</div>
</div>
</div>
</a>
<div class="course__metas">
<div class="course__meta meta">
<div class="meta__item">
<a class="meta__item" title="Продолжительность курса">
<div class="meta__icon">
<svg class="icon icon-time">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-time"></use>
</svg>
</div>
<div class="meta__title">{{ course.duration | rupluralize:"день,дня,дней" }}</div>
</div>
</a>
{% if course.price %}
<div class="meta__item">
<div class="meta__icon">
@ -112,8 +123,6 @@
<div class="meta__title">{{ course.price|floatformat:"-2" }}₽</div>
</div>
{% endif %}
</div>
<div class="course__meta meta">
<div class="meta__item">
<div class="meta__icon">
<svg class="icon icon-showcase">
@ -281,30 +290,19 @@
{% endif %}
<div class="user__info">
<div class="user__name">{{ course.author.get_full_name }}</div>
<div class="user__meta">
<div class="user__date">{{ course.created_at_humanize }}</div>
</div>
</div>
</div>
</a>
<div class="course__info">
<div class="course__meta meta meta_white">
<div class="meta__item">
<a class="meta__item" title="Продолжительность курса">
<div class="meta__icon">
<svg class="icon icon-time">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-time"></use>
</svg>
</div>
<div class="meta__title">{{ course.duration | rupluralize:"день,дня,дней" }}</div>
</div>
<div class="meta__item">
<div class="meta__icon">
<svg class="icon icon-date">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-date"></use>
</svg>
</div>
<div class="meta__title">{{ course.created_at | date:"d F Yг." }}</div>
</div>
</a>
{% if course.price %}
<div class="meta__item">
<div class="meta__icon">

@ -29,17 +29,22 @@
<a class="go__btn btn" href="{% url 'course_edit' course.id %}">Редактировать</a>
{% endif %}
{% if not paid and course.price and not has_full_access %}
<a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}
{% if not pending %}
href="{% url 'course-checkout' course.id %}"
{% endif %}
{% else %}
data-popup=".js-popup-auth"
href="#"
<div>
<a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}
{% if not pending %}
href="{% url 'course-checkout' course.id %}"
{% endif %}
{% else %}
data-popup=".js-popup-auth"
href="#"
{% endif %}
>{% if pending %}ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ ОПЛАТЫ{% else %}КУПИТЬ КУРС{% endif %}</a>
{% if not paid %}
<a class="main__btn btn btn_stroke-black" href="{% url 'gift-certificates' %}">Подарить другу</a>
{% endif %}
>{% if pending %}ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ ОПЛАТЫ{% else %}КУПИТЬ КУРС{% endif %}</a>
</div>
{% endif %}
</div>
<div
@ -85,30 +90,19 @@
{% endif %}
<div class="user__info">
<div class="user__name">{{ course.author.get_full_name }}</div>
<div class="user__meta">
<div class="user__date">{{ course.created_at_humanize }}</div>
</div>
</div>
</div>
</a>
<div class="course__metas">
<div class="course__meta meta">
<div class="meta__item">
<a class="meta__item" title="Продолжительность курса">
<div class="meta__icon">
<svg class="icon icon-time">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-time"></use>
</svg>
</div>
<div class="meta__title">{{ course.duration | rupluralize:"день,дня,дней" }}</div>
</div>
<div class="meta__item">
<div class="meta__icon">
<svg class="icon icon-date">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-date"></use>
</svg>
</div>
<div class="meta__title">{{ course.created_at | date:"d F Yг." }}</div>
</div>
</a>
{% if course.price %}
<div class="meta__item">
<div class="meta__icon">
@ -119,8 +113,6 @@
<div class="meta__title">{{ course.price|floatformat:"-2" }}₽</div>
</div>
{% endif %}
</div>
<div class="course__meta meta">
<div class="meta__item">
<div class="meta__icon">
<svg class="icon icon-showcase">

@ -47,9 +47,6 @@
{% endif %}
<div class="user__info">
<div class="user__name">{{ lesson.author.get_full_name }}</div>
<div class="user__meta">
<div class="user__date">{{ lesson.created_at_humanize }}</div>
</div>
</div>
</div>
</a>

@ -8,7 +8,7 @@ from django.db.models import Max
from apps.notification.models import UserNotification
from apps.notification.utils import send_email
from apps.payment.models import SchoolPayment, CoursePayment, Payment
from apps.payment.models import SchoolPayment, CoursePayment, Payment, UserGiftCertificate
from project.celery import app
from project.utils.db import format_sql, execute_sql
from project.sengrid import get_sendgrid_client
@ -33,7 +33,7 @@ def send_certificates(email=None, date=None, dry_run=False):
file.close()
return
date = datetime.strptime(date, '%d-%m-%Y') if date else now().date()
date = datetime.strptime(date, '%d-%m-%Y').date() if date else now().date()
today = now().date()
users = set(list(SchoolPayment.objects.filter(date_end=date, add_days=False).values_list('user_id', flat=True)))
user_notifications_qs = UserNotification.objects.filter(user_id__in=users)
@ -42,6 +42,7 @@ def send_certificates(email=None, date=None, dry_run=False):
'user_id', flat=True).distinct()
for user_id in users:
if user_id in notified_users:
print('skip', user_id)
continue
un = user_notifications.get(user_id, UserNotification(user_id=user_id))
print(un.user.email)
@ -54,7 +55,7 @@ def send_certificates(email=None, date=None, dry_run=False):
file = open(staticfiles_storage.path(path_pattern % un.certificate_number), 'rb')
try:
send_email('Грамота от Lil School', un.user.email, 'notification/email/certificate.html',
attachments=[(file.name, file.read(), 'image/jpeg')])
attachments=[(file.name, file.read(), 'image/jpeg')], user_notification=un)
except:
print('Not OK')
continue
@ -98,3 +99,11 @@ def sendgrid_update_recipients():
sg = get_sendgrid_client()
response = sg.client.contactdb.recipients.patch(request_body=data)
print(response.body)
@app.task
def send_gift_certificate(user_gift_certificate):
user_gift_certificate = UserGiftCertificate.objects.get(id=user_gift_certificate)
send_email('Подарочный сертификат от Lil School', user_gift_certificate.user.email, 'notification/email/gift_certificate.html',
inline_images=[('twitter_icon', 'img/twitter.png'), ('fb_icon', 'img/fb.png'), ('instagram_icon', 'img/instagram.png'),],
user_gift_certificate=user_gift_certificate, gift_certificate=user_gift_certificate.gift_certificate)

@ -1,28 +1,71 @@
{% extends "notification/email/_base.html" %}
{% block content %}
<p style="margin: 0 0 20px">Дорогие родители!</p>
<p style="margin: 0 0 20px">Привет!</p>
<div style="margin-bottom: 10px;">
<p>Это письмо адресовано вашим детям - ученикам Lil School.
Если они еще не умеют читать, прочитайте им наше послание, громко и с выражением.<br>
Спасибо!
{% if not user_notification or user_notification.certificate_number == 1 %}
<p>
Поздравляем! Вы прошли месяц обучения в Lil School.<br>
К письму прикреплена грамота. Распечатайте её и вручите вашим детям.<br>
Ждём вас в следующем месяце на наших творческих занятиях!
</p>
{% endif %}
{% if user_notification and user_notification.certificate_number == 2 %}
<p>
Вы помните, что каждый месяц вам приходит грамота за прекрасную учебу в нашей творческой школе?<br>
Скачивайте. Распечатывайте. И соберите свою коллекцию!
</p>
{% endif %}
{% if user_notification and user_notification.certificate_number == 3 %}
<p>
Вам понравился наш творческий месяц?<br>
В письме вы найдёте грамоту, она для вашей семьи.<br>
Как здорово, что у нас есть такие ученики!<br>
Ждём вас в следующем месяце.
</p>
{% endif %}
{% if user_notification and user_notification.certificate_number == 4 %}
<p>
Прошёл целый месяц обучения на платформе Lil School - месяц творчества, креатива и невероятных идей.<br>
Во вложении вас ждёт грамота.<br>
До встречи на занятиях!
</p>
{% endif %}
{% if user_notification and user_notification.certificate_number == 5 %}
<p>
Ваша грамота за успехи в учебе в Lil School ждёт вас во вложении.<br>
Скорее распечатайте её!<br>
Вам есть чем гордится!<br>
До встречи в следующем месяце!
</p>
{% endif %}
{% if user_notification and user_notification.certificate_number == 6 %}
<p>
Как здорово вы потрудились на занятиях в этом месяце!<br>
И наша грамота уже ждёт вас!<br>
Спасибо за творчество и креатив.<br>
Ждём вас в следующем месяце!
</p>
{% endif %}
{% if user_notification and user_notification.certificate_number == 7 %}
<p>
Какой классный месяц у нас был! Вместе мы очень здорово и креативно потрудились.<br>
Во вложении вас ждёт заслуженная грамота!<br>
До встречи на уроках!
</p>
{% endif %}
{% if user_notification and user_notification.certificate_number == 8 %}
<p>
Месяц творчества и креатива пролетел как один миг! А как много работ мы вместе сделали!<br>
Вы - большие молодцы.<br>
Во вложении ваш ждёт грамота!<br>
До встречи на занятиях.
</p>
{% endif %}
</div>
<div style="margin-bottom: 10px; font-style: italic;">
Привет, друг!<br>
Вот и прошёл месяц обучения на платформе «Lil School» - месяц удивительных, творческих приключений и открытий.
Ты так много узнал и столько всего нарисовал!
Как же здорово у тебя все получается!
</div>
<div style="margin-bottom: 10px; font-style: italic;">
Скорее смотри что прикреплено к письму. Да это же ГРАМОТА! Ее можно распечатать и повесить
на видное место в твоей комнате.
Можно показать ее друзьям вместе со всеми работами, над которыми ты так трудился.
</div>
<div style="margin-bottom: 10px; font-style: italic;">
Поздравляем! Мы ждём тебя в новом месяце в рядах наших учеников.
</div>
<div style="margin-bottom: 10px; font-style: italic;">
Команда «Lil School».
<div style="margin-bottom: 10px;">
<p>
Команда «Lil School».
</p>
</div>
{% endblock content %}

@ -0,0 +1,112 @@
{% extends "notification/email/_base.html" %}
{% load settings %}
{% block content %}
<p style="text-align: center;font-size: 2em;font-weight: bold;line-height: 1.09375;">Поздравляем с успешной
покупкой!</p>
<div style="margin-bottom:30px;text-align: center;">
<p>Вы получаете <strong>{{ gift_certificate.price }}</strong> лиликов на счет! <strong>1 LIL = 1 руб.</strong><br>
Накапливайте монеты и тратьте их на оплату школы и курсов.
</p>
</div>
<div style="margin-bottom: 30px;width: 100%;">
<div style="
margin: 0 auto;
width: 300px;
">
<div style="
width: 300px;
height: 300px;
background-color: #FEB9B9;
"></div>
<table style="
width: 100%;
font-size: 12px;
font-weight: bold;
">
<tbody>
<tr>
<td style="
letter-spacing: 2px;
text-transform: uppercase;
">Подарочный сертификат
</td>
<td style="
text-align: right;
">{{ gift_certificate.price }} р.
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div style="margin-bottom:10px;text-align: center;">Чтобы воспользоваться сертификатом, перейдите по ссылке</div>
<div style="margin-bottom: 30px;text-align: center;">
<a href="https://{% setting 'MAIN_HOST' %}{% url 'index' %}?gift-certificate={{ user_gift_certificate.code }}" style="
color: #FF9393;
font-weight: bold;
">
{% setting 'MAIN_HOST' %}{% url 'index' %}?gift-certificate={{ user_gift_certificate.code }}</a>
</div>
<div style="margin-bottom:10px;text-align: center;">
Или воспользуйтесь сертификатом, введя уникальный код на в разделе
вашего профиля на сайте <a href="https://lil.school" style="
color: #FF9393;
">lil.school</a>
</div>
<div style="
text-align: center;
font-size: 1.4em;
font-weight: bold;
margin-bottom: 10px;
">Ваш код
</div>
<div style="
text-align: center;
font-size: 1.4em;
font-weight: bold;
margin-bottom: 30px;
">
<div style="
display: inline-block;
border: 1px solid black;
padding: 5px 40px;
border-radius: 3px;
">{{ user_gift_certificate.code }}
</div>
</div>
<div style="
margin-bottom: 10px;
text-align: center;
">
Вы так же можете отправить это письмо, ссылку или код вашему другу, чтобы подарить ему этот сертификат.
</div>
<div style="
margin-bottom: 10px;
text-align: center;
font-size: 1.4em;
font-weight: bold;
">
Присоединяйтесь к нам!
</div>
<div style="
margin-bottom: 10px;
text-align: center;">
{% if config.SERVICE_TWITTER_URL %}
<a href="{{ config.SERVICE_TWITTER_URL }}">
<img src="cid:{{ twitter_icon }}" />
</a>
{% endif %}
{% if config.SERVICE_FB_URL %}
<a href="{{ config.SERVICE_FB_URL }}">
<img src="cid:{{ fb_icon }}" />
</a>
{% endif %}
{% if config.SERVICE_INSTAGRAM_URL %}
<a href="{{ config.SERVICE_INSTAGRAM_URL }}">
<img src="cid:{{ instagram_icon }}" />
</a>
{% endif %}
</div>
{% endblock content %}

@ -1,16 +1,27 @@
from django.contrib.staticfiles.storage import staticfiles_storage
from twilio.rest import Client
from django.core.mail import EmailMessage
from django.core.mail import EmailMessage, EmailMultiAlternatives
from anymail.message import attach_inline_image_file
from django.conf import settings
from django.template.loader import get_template
from project.celery import app
@app.task
def send_email(subject, to_email, template_name, attachments=[], **kwargs):
html = get_template(template_name).render(kwargs)
email = EmailMessage(subject, html, to=[to_email], attachments=attachments)
email.content_subtype = 'html'
def send_email(subject, to_email, template_name, attachments=[], inline_images=[], **kwargs):
if inline_images:
email = EmailMultiAlternatives(subject, '', to=[to_email], attachments=attachments)
context = kwargs
for name, path in inline_images:
cid = attach_inline_image_file(email, staticfiles_storage.path(path))
context[name] = cid
html = get_template(template_name).render(context)
email.attach_alternative(html, "text/html")
else:
html = get_template(template_name).render(kwargs)
email = EmailMessage(subject, html, to=[to_email], attachments=attachments)
email.content_subtype = 'html'
email.send()

@ -5,7 +5,7 @@ from polymorphic.admin import (
PolymorphicChildModelFilter,
)
from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment
from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, GiftCertificate
@admin.register(AuthorBalance)
@ -61,3 +61,8 @@ class PaymentAdmin(PolymorphicParentModelAdmin):
CoursePayment,
SchoolPayment,
)
@admin.register(GiftCertificate)
class GiftCertificateAdmin(admin.ModelAdmin):
pass

@ -0,0 +1,34 @@
# Generated by Django 2.0.6 on 2018-10-29 14:36
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('payment', '0024_auto_20181002_0338'),
]
operations = [
migrations.CreateModel(
name='GiftCertificate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('price', models.DecimalField(decimal_places=2, default=0, editable=False, max_digits=8)),
],
),
migrations.CreateModel(
name='UserGiftCertificate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('recipient', models.EmailField(max_length=254, blank=True, null=True,)),
('bonuses_sent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='payment.UserBonus')),
('gift_certificate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='payment.GiftCertificate')),
('payment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='payment.Payment')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gift_certificates', to=settings.AUTH_USER_MODEL)),
],
),
]

@ -0,0 +1,39 @@
# Generated by Django 2.0.6 on 2018-11-01 15:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('payment', '0025_giftcertificate_usergiftcertificate'),
]
operations = [
migrations.CreateModel(
name='GiftCertificatePayment',
fields=[
('payment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='payment.Payment')),
],
options={
'verbose_name': 'Платеж за подарочный сертификат',
'verbose_name_plural': 'Платежи за подарочные сертификаты',
},
bases=('payment.payment',),
),
migrations.AlterModelOptions(
name='giftcertificate',
options={'ordering': ('price',)},
),
migrations.AlterField(
model_name='giftcertificate',
name='price',
field=models.DecimalField(decimal_places=2, default=0, max_digits=8),
),
migrations.AddField(
model_name='giftcertificatepayment',
name='gift_certificate',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='payment.GiftCertificate', verbose_name='Подарочный сертификат'),
),
]

@ -0,0 +1,22 @@
# Generated by Django 2.0.6 on 2018-11-09 14:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payment', '0026_auto_20181101_1546'),
]
operations = [
migrations.RemoveField(
model_name='usergiftcertificate',
name='recipient',
),
migrations.AddField(
model_name='giftcertificate',
name='cover',
field=models.CharField(blank=True, default='', max_length=255),
),
]

@ -0,0 +1,21 @@
# Generated by Django 2.0.6 on 2018-11-09 14:03
from django.db import migrations
from django.contrib.staticfiles.storage import staticfiles_storage
def add_gift_certificates(apps, schema_editor):
GiftCertificate = apps.get_model('payment', 'GiftCertificate')
for price in [1000, 2000, 3000, 5000, 10000]:
GiftCertificate.objects.create(price=price,
cover=staticfiles_storage.url('img/gift-certificates/%d.jpg' % price))
class Migration(migrations.Migration):
dependencies = [
('payment', '0027_auto_20181109_1402'),
]
operations = [
migrations.RunPython(add_gift_certificates),
]

@ -1,5 +1,6 @@
from decimal import Decimal
import arrow
import short_url
from django.db.models import Func, F
from paymentwall import Pingback
@ -13,6 +14,7 @@ from django.core.validators import RegexValidator
from django.utils.timezone import now
from django.conf import settings
from apps.content.models import ImageObject
from project.utils import weekdays_in_date_range
from apps.course.models import Course
@ -147,6 +149,8 @@ class Payment(PolymorphicModel):
referrer_bonus = user.referral.referrer_bonus
if payment and payment.is_paid():
price = payment.amount
elif isinstance(payment, GiftCertificatePayment):
price = payment.gift_certificate.price
elif course:
price = course.price
else:
@ -244,6 +248,12 @@ class Payment(PolymorphicModel):
else:
author_balance.amount = self.amount
author_balance.save()
if isinstance(self, GiftCertificatePayment) and self.is_paid():
ugs, created = UserGiftCertificate.objects.get_or_create(user=self.user, gift_certificate=self.gift_certificate,
payment=self)
if created:
from apps.notification.tasks import send_gift_certificate
send_gift_certificate(ugs.id)
# Если юзер реферал и нет платежа, где применялась скидка
if hasattr(self.user, 'referral') and not self.user.referral.payment and self.is_paid():
# Платеж - как сигнал, что скидка применилась
@ -283,6 +293,15 @@ class SchoolPayment(Payment):
return arrow.get(self.date_end, settings.TIME_ZONE).humanize(locale='ru')
class GiftCertificatePayment(Payment):
gift_certificate = models.ForeignKey('GiftCertificate', on_delete=models.CASCADE,
verbose_name='Подарочный сертификат', related_name='payments')
class Meta:
verbose_name = 'Платеж за подарочный сертификат'
verbose_name_plural = 'Платежи за подарочные сертификаты'
class UserBonus(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='bonuses')
amount = models.DecimalField(max_digits=8, decimal_places=2, default=0, editable=False)
@ -292,3 +311,22 @@ class UserBonus(models.Model):
class Meta:
ordering = ('created_at',)
class GiftCertificate(models.Model):
price = models.DecimalField(max_digits=8, decimal_places=2, default=0)
cover = models.CharField(max_length=255, blank=True, default='')
class Meta:
ordering = ('price',)
class UserGiftCertificate(models.Model):
gift_certificate = models.ForeignKey(GiftCertificate, on_delete=models.CASCADE,)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gift_certificates')
payment = models.ForeignKey(Payment, on_delete=models.SET_NULL, null=True)
bonuses_sent = models.ForeignKey(UserBonus, on_delete=models.CASCADE, blank=True, null=True)
@property
def code(self):
return short_url.encode_url(self.id) if self.id else None

@ -0,0 +1,17 @@
{% extends "templates/lilcity/index.html" %} {% load static %}
{% block content %}
<div class="section">
<div class="section__center center">
<div class="title title_center">Вам подарок!</div>
<div class="text">
Пользователь ХХХ дарит вам сертификат на сумму ХХХ руб
</div>
<div class="center">
<a href="{% url '' %}" class="btn">
получить подарок
</a>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,15 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% block content %}
<div class="section">
<div class="section__center center center_xs">
<div class="done">
<div class="done__title title">Бонусы зачислены на ваш счет!</div>
<div>Вы можете оплатить с их помощью курс или онлайн-школу</div>
<div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="{% url 'school:school' %}"
style="margin-bottom: 10px;">Записаться в школу</a>
<a class="done__btn btn btn_md btn_stroke" href="{% url 'courses' %}">Купить курсы</a>
</div>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,36 @@
{% load thumbnail %}
{% load static %}
{% load data_liked from data_liked %}
<div class="gift-certificates__item">
<div class="gift-certificates__preview {{ theme_color }}">
<img class="gift-certificates__cover" src="{{ gift_certificate.cover }}" />
</div>
<div class="gift-certificates__details">
<span class="gift-certificates__title theme {{ theme_color }}">
{% if user_gift_certificate and not user_gift_certificate.bonuses_sent %}
<a href="{% url 'gift-certificate-get' user_gift_certificate.code %}">подарочный сертификат</a>
{% else %}подарочный сертификат{% endif %}</span>
<div class="gift-certificates__price">{{ gift_certificate.price|floatformat:"-2" }}₽</div>
</div>
{% if user_gift_certificate %}
{% if user_gift_certificate.bonuses_sent %}
<div class="gift-certificates__status">
<img class="icon" src="{% static 'img/done.png' %}" />
Получено
</div>
{% else %}
<div class="gift-certificates__status">
<img class="icon" style="margin-top: -1px; height: auto;" src="{% static 'img/clock.png' %}" />
Ожидает получения
</div>
{% endif %}
{% else %}
<a class="gift-certificates__buy-btn btn"
{% if request.user.is_authenticated %}
href="{% url 'gift-certificate-checkout' gift_certificate.id %}"
{% else %}
href="#" data-popup=".js-popup-auth"
{% endif %}>Купить сертификат</a>
{% endif %}
</div>

@ -0,0 +1,4 @@
{% for gift_certificate in gift_certificates %}
{% cycle 'theme_pink2' 'theme_cyan' 'theme_violet2' as theme_color silent %}
{% include "payment/gift_certificate_item.html" %}
{% endfor %}

@ -0,0 +1,13 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% block content %}
<div class="section">
<div class="section__center center center_xs">
<div class="done">
<div class="done__title title">Вы успешно приобрели подарочный сертификат!</div>
<div>Мы отправили письмо с сертификатом на вашу почту.</div>
<div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="{% url 'index' %}">СПАСИБО</a>
</div>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,35 @@
{% extends "templates/lilcity/index.html" %} {% load static %}
{% block content %}
<div
class="main main_default"
{% if config.MAIN_PAGE_TOP_IMAGE %}
style="background-image: url({{ config.MAIN_PAGE_TOP_IMAGE.url }});"
{% else %}
style="background-image: url({% static 'img/bg-1.jpg' %});"
{% endif %}
>
<div class="main__center center" style="margin-top: 110px;">
<div class="main__title">
<span class="main__bold">Подарочные сертификаты</span>
</div>
</div>
</div>
<div class="section section_courses">
<div class="section__center center">
<div class="text text_courses">Подарочный сертификат Lil City - отличный презент на любой праздник.<br>
А также сертификат можно подарить без повода - развитие <br>творческого потенциала всегда уместно и актуально.
При покупке подарочного сертификата мы высылаем письмо,<br>
где будет уникальный код и уникальная ссылка. Письмо или ссылку<br>
на него вы можете переслать другому. Сертификатом можно<br>
воспользоваться, перейдя по ссылке.
<img src="/static/img/curve-1.svg" class="text__curve text__curve_one">
</div>
<div class="title title_center">Сертификаты</div>
<div class="gift-certificates">
{% include "payment/gift_certificate_items.html" %}
</div>
</div>
</div>
{% endblock content %}

@ -7,10 +7,17 @@
<div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="{% url 'school:school' %}">ПЕРЕЙТИ К ШКОЛЕ</a>
</div>
{% else %}
{% endif %}
{% if course %}
<div class="done__title title">Вы успешно приобрели курс!</div>
<div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="/">ПЕРЕЙТИ К ГЛАВНОЙ</a>
<a class="done__btn btn btn_md btn_stroke" href="{% url 'course' course.id %}">ПЕРЕЙТИ К КУРСУ</a>
</div>
{% endif %}
{% if gift_certificate %}
<div class="done__title title">Вы успешно приобрели подарочный сертификат!</div>
<div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="{% url 'index' %}">ПЕРЕЙТИ НА ГЛАВНУЮ</a>
</div>
{% endif %}
</div>

@ -1,5 +1,5 @@
from decimal import Decimal
import short_url
import arrow
import json
import logging
@ -12,9 +12,9 @@ import calendar
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.http import HttpResponse, Http404
from django.shortcuts import redirect, get_object_or_404
from django.views.generic import View, TemplateView
from django.views.generic import View, TemplateView, DetailView
from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
@ -27,7 +27,8 @@ from apps.course.models import Course
from apps.school.models import SchoolSchedule
from apps.payment.tasks import transaction_to_mixpanel, product_payment_to_mixpanel, transaction_to_roistat
from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserBonus
from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserBonus, GiftCertificate, \
GiftCertificatePayment, UserGiftCertificate
logger = logging.getLogger('django')
@ -216,6 +217,8 @@ class PaymentwallCallbackView(View):
product_payment_class = CoursePayment
elif product_type_name == 'school':
product_payment_class = SchoolPayment
elif product_type_name == 'gift_certificate':
product_payment_class = GiftCertificatePayment
else:
return HttpResponse(status=403)
@ -264,6 +267,15 @@ class PaymentwallCallbackView(View):
'created_at': payment.created_at,
'update_at': payment.update_at,
}
elif product_type_name == 'gift_certificate':
properties = {
'payment_id': payment.id,
'amount': payment.amount,
'status': payment.status,
'gift_certificate': payment.gift_certificate.id,
'created_at': payment.created_at,
'update_at': payment.update_at,
}
payment.save()
product_payment_to_mixpanel.delay(
@ -297,3 +309,84 @@ class PaymentwallCallbackView(View):
else:
raise DisallowedPingbackHost
return HttpResponse(status=403)
class GiftCertificatesView(TemplateView):
model = GiftCertificate
template_name = 'payment/gift_certificates.html'
def get(self, request, *args, **kwargs):
gift_certificates = GiftCertificate.objects.all()
context = self.get_context_data(**kwargs)
context['gift_certificates'] = gift_certificates
return self.render_to_response(context)
@method_decorator(login_required, name='dispatch')
class GiftCertificateBuyView(TemplateView):
model = GiftCertificate
template_name = 'payment/paymentwall_widget.html'
def get(self, request, pk, *args, **kwargs):
gift_certificate = get_object_or_404(GiftCertificate, pk=pk)
roistat_visit = request.COOKIES.get('roistat_visit', None)
gift_certificate_payment = GiftCertificatePayment.objects.create(
user=request.user,
gift_certificate=gift_certificate,
roistat_visit=roistat_visit,)
context = self.get_context_data(**kwargs)
product = Product(
f'gift_certificate_{gift_certificate_payment.id}',
gift_certificate_payment.amount,
'RUB',
'Подарочный сертификат',
)
host = urlsplit(self.request.META.get('HTTP_REFERER'))
host = str(host[0]) + '://' + str(host[1])
widget = Widget(
str(request.user.id),
'p1_1',
[product],
extra_params={
'lang': 'ru',
'evaluation': 1,
'demo': 1,
'test_mode': 1,
'success_url': host + str(reverse_lazy('gift-certificate-payment-success', args=[gift_certificate_payment.id])),
'failure_url': host + str(reverse_lazy('payment-error')),
}
)
context['widget'] = widget.get_html_code()
return self.render_to_response(context)
@method_decorator(login_required, name='dispatch')
class GiftCertificateBuySuccessView(TemplateView):
template_name = 'payment/gift_certificate_payment_success.html'
def get(self, request, payment_id=None, *args, **kwargs):
try:
GiftCertificatePayment.objects.get(id=payment_id)
except:
raise Http404()
return self.render_to_response(context={'gift_certificate': True})
@method_decorator(login_required, name='dispatch')
class GiftCertificateGetView(TemplateView):
template_name = 'payment/gift_certificate_get.html'
def get(self, request, slug, *args, **kwargs):
try:
ugs = get_object_or_404(UserGiftCertificate, pk=short_url.decode_url(slug))
except:
raise Http404()
if UserBonus.objects.filter(payment=ugs.payment).exists():
raise Http404()
bonuses = UserBonus.objects.create(user=request.user, amount=ugs.gift_certificate.price,
payment=ugs.payment)
ugs.bonuses_sent = bonuses
ugs.save()
context = self.get_context_data(**kwargs)
return self.render_to_response(context)

@ -5,9 +5,15 @@
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ livelesson.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% if livelesson.cover and livelesson.cover.image %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{{ livelesson.cover.image.url }}{% endblock ogimage %}
{% endif %}
{% block ogimage %}
{% if livelesson.cover and livelesson.cover.image %}http://{{request.META.HTTP_HOST}}{{ livelesson.cover.image.url }}{% else %}{{ block.super }}{% endif %}
{% endblock ogimage %}
{% block ogimage-width %}
{% if livelesson.cover and livelesson.cover.image %}{{ livelesson.cover.image.width }}{% else %}{{ block.super }}{% endif %}
{% endblock ogimage-width %}
{% block ogimage-height %}
{% if livelesson.cover and livelesson.cover.image %}{{ livelesson.cover.image.height }}{% else %}{{ block.super }}{% endif %}
{% endblock ogimage-height %}
{% block content %}
<div class="section" style="margin-bottom:0;padding-bottom:0">

@ -0,0 +1,27 @@
{% extends "templates/lilcity/index.html" %}
{% load static %}
{% block title %}{{ livelesson.title }} - {{ block.super }}{% endblock title %}
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ livelesson.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% block ogimage %}
{% if livelesson.cover and livelesson.cover.image %}http://{{request.META.HTTP_HOST}}{{ livelesson.cover.image.url }}{% else %}{{ block.super }}{% endif %}
{% endblock ogimage %}
{% block ogimage-width %}
{% if livelesson.cover and livelesson.cover.image %}{{ livelesson.cover.image.width }}{% else %}{{ block.super }}{% endif %}
{% endblock ogimage-width %}
{% block ogimage-height %}
{% if livelesson.cover and livelesson.cover.image %}{{ livelesson.cover.image.height }}{% else %}{{ block.super }}{% endif %}
{% endblock ogimage-height %}
{% block content %}
<div class="section">
<div class="section__center center center_sm">
<div class="title">
Чтобы посмотреть урок необходимо авторизоваться
</div>
<button class="btn" data-popup=".js-popup-auth">Войти</button>
</div>
</div>
{% endblock content %}

@ -20,6 +20,9 @@
{% if is_purchased %}ваша подписка истекает {{ subscription_ends_humanize }}<br/>перейти к оплате{% endif %}
</a>
{% if not is_purchased and not is_purchased_future %}
<a class="main__btn btn btn_stroke-black" href="{% url 'gift-certificates' %}">Подарить другу</a>
{% endif %}
</div>
<div class="main__school school school_main">
<div class="school__col">

@ -57,26 +57,28 @@ class LiveLessonsView(ListView):
return queryset
@method_decorator(login_required, name='dispatch')
class LiveLessonsDetailView(DetailView):
model = LiveLesson
template_name = 'school/livelesson_detail.html'
def get(self, request, pk=None):
self.object = self.get_object()
is_purchased = SchoolPayment.objects.filter(
user=request.user,
date_start__lte=now(),
date_end__gte=now() - timedelta(days=7),
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
weekdays__contains=[self.object.date.weekday() + 1],
).exists()
if not is_purchased and request.user.role not in [User.ADMIN_ROLE, User.TEACHER_ROLE]:
raise Http404
if request.user.is_authenticated:
is_purchased = SchoolPayment.objects.filter(
user=request.user,
date_start__lte=now(),
date_end__gte=now() - timedelta(days=7),
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
weekdays__contains=[self.object.date.weekday() + 1],
).exists()
if not is_purchased and request.user.role not in [User.ADMIN_ROLE, User.TEACHER_ROLE]:
raise Http404
else:
self.template_name = 'school/livelesson_detail_unauth.html'
context = self.get_context_data(object=self.object)
return self.render_to_response(context)

@ -2,7 +2,10 @@
<div class="section">
<div class="section__center center">
<div class="profile">
<a class="profile__btn profile__btn_edit btn" href="{% url 'user-edit-profile' %}">Редактировать</a>
<div class="profile__btns">
<a class="profile__btn profile__btn_edit btn" href="{% url 'user-edit-profile' %}">Редактировать</a>
<a data-popup=".js-popup-enter-gift-code" href="#" class="profile__btn btn btn_stroke-black">Ввести код</a>
</div>
<div class="profile__ava ava">
{% thumbnail user.photo "120x120" crop="center" as im %}
<img class="ava__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
@ -91,10 +94,16 @@
<a class="btn" href="{% url 'school:school' %}">Перейти в онлайн-школу</a>
</div>
{% endif %}
{% if user_gift_certificates.exists %}
{% for ugs in user_gift_certificates %}
{% cycle 'theme_pink2' 'theme_cyan' 'theme_violet2' as theme_color silent %}
{% include "payment/gift_certificate_item.html" with gift_certificate=ugs.gift_certificate user_gift_certificate=ugs %}
{% endfor %}
{% endif %}
{% if paid.exists %}
{% include "course/course_items.html" with course_items=paid %}
{% endif %}
{% if not is_school_purchased and not paid.exists %}
{% if not is_school_purchased and not paid.exists and not user_gift_certificates.exists %}
<div class="center center_xs">
<div class="done">
<div class="done__title">Вы пока ничего не приобрели...</div>

@ -24,7 +24,7 @@ from apps.config.models import Config
from apps.course.models import Course
from apps.notification.utils import send_email
from apps.school.models import SchoolSchedule
from apps.payment.models import AuthorBalance, CoursePayment, SchoolPayment, Payment
from apps.payment.models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserGiftCertificate
from apps.user.models import AuthorRequest, EmailSubscription, SubscriptionCategory
from .forms import AuthorRequesForm, UserEditForm, WithdrawalForm
@ -59,6 +59,7 @@ class ProfileView(TemplateView):
author=self.object,
)
context['is_author'] = context['published'] or self.request.user.role == User.AUTHOR_ROLE
context['user_gift_certificates'] = UserGiftCertificate.objects.filter(user=self.request.user)
context['paid'] = Course.objects.filter(
payments__in=CoursePayment.objects.filter(
user=self.object,

@ -9,7 +9,7 @@ POSTGRES_PASSWORD=GPVs/E/{5&qe
DJANGO_SETTINGS_MODULE=project.settings
DATABASE_SERVICE_HOST=db
SECRET_KEY=jelm*91lj(_-o20+6^a+bgv!4s6e_efry^#+f#=1ak&s1xr-2j
MAILGUN_API_KEY=key-ec6af2d43d031d59bff6b1c8fb9390cb
MAILGUN_API_KEY=key-ec6af2d43d031d59bff6b1c8fb9390c
MAILGUN_SENDER_DOMAIN=mail.9ev.ru
DEFAULT_FROM_EMAIL=postmaster@mail.9ev.ru
TWILIO_ACCOUNT=ACdf4a96b776cc764bc3ec0f0e136ba550

@ -9,7 +9,7 @@ POSTGRES_PASSWORD=GPVs/E/{5&qe
DJANGO_SETTINGS_MODULE=project.settings
DATABASE_SERVICE_HOST=db
SECRET_KEY=jelm*91lj(_-o20+6^a+bgv!4s6e_efry^#+f#=1ak&s1xr-2j
MAILGUN_API_KEY=key-ec6af2d43d031d59bff6b1c8fb9390cb
MAILGUN_API_KEY=key-ec6af2d43d031d59bff6b1c8fb9390c
MAILGUN_SENDER_DOMAIN=mail.9ev.ru
DEFAULT_FROM_EMAIL=postmaster@mail.9ev.ru
TWILIO_ACCOUNT=ACdf4a96b776cc764bc3ec0f0e136ba550

@ -185,8 +185,8 @@ LOGIN_URL = '/'
# https://github.com/anymail/django-anymail
ANYMAIL = {
'MAILGUN_API_KEY': os.getenv('MAILGUN_API_KEY', 'key-ec6af2d43d031d59bff6b1c8fb9390cb'),
'MAILGUN_SENDER_DOMAIN': os.getenv('MAILGUN_SENDER_DOMAIN', 'mail.9ev.ru'),
'MAILGUN_API_KEY': os.getenv('MAILGUN_API_KEY', ''),
'MAILGUN_SENDER_DOMAIN': os.getenv('MAILGUN_SENDER_DOMAIN', ''),
}
EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend'
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'postmaster@mail.9ev.ru')

@ -1,11 +1,13 @@
{% if baner %}
<div class="banner" style="/* background-image: url(); */ display: none;" data-banner="{{baner.id}}">
{% if request.user_agent.is_mobile %}
<a href="{{ baner.url }}">
<a href="{{ baner.url }}" class="banner__image-wrap">
<img class="banner__image" src="{{ baner.image.url }}" />
</a>
{% else %}
<img class="banner__image" src="{{ baner.image.url }}" />
<div class="banner__image-wrap">
<img class="banner__image" src="{{ baner.image.url }}" />
</div>
<div class="banner__content">
<span class="banner__text">{{ baner.text }}</span>
<a href="{{ baner.url }}" class="banner__link">{{ baner.button_text }}</a>

@ -41,6 +41,10 @@
<div class="header__group">
<a class="header__section" href="https://blog.lil.school">БЛОГ</a>
</div>
<div class="header__group">
<a class="header__section {% active_link 'gift-certificates' %}"
href="{% url 'gift-certificates' %}">СЕРТИФИКАТЫ</a>
</div>
</nav>
</div>
{% include 'templates/blocks/user_menu.html' %}

@ -28,6 +28,7 @@
referrer: '{{ referrer.id|default:'' }}',
referrerName: '{% if referrer %}{{ referrer.get_full_name }}{% endif %}',
isReferralUrl: {{ is_referral_url|yesno:"true,false" }},
isGiftCertificateUrl: {{ is_gift_certificate_url|yesno:"true,false" }},
},
};
</script>

@ -0,0 +1,24 @@
{% load static %}
<div class="popup js-popup-enter-gift-code">
<div class="popup__wrap popup__wrap_sm js-popup-wrap">
<button class="popup__close js-popup-close">
<svg class="icon icon-close">
<use xlink:href={% static "img/sprite.svg" %}#icon-close></use>
</svg>
</button>
<div class="popup__body">
<div class="enter-gift-code" style="padding:20px 20px 25px;">
<div class="subtitle2">Введите код из подарочного сертификата</div>
<div class="field">
<div class="field__label">КОД</div>
<div class="field__wrap"><input class="field__input enter-gift-code__code" type="text"
placeholder="" tabindex="1"></div>
<div class="enter-gift-code__error field__error"></div>
</div>
<div style="text-align: center;">
<a href="#" class="enter-gift-code__btn btn btn_light" tabindex="3">Подтвердить</a>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,31 @@
{% load static %}
<div class="popup js-popup-gift-certificate">
<div class="popup__wrap popup__wrap_sm js-popup-wrap">
<button class="popup__close js-popup-close">
<svg class="icon icon-close">
<use xlink:href={% static "img/sprite.svg" %}#icon-close></use>
</svg>
</button>
<div class="popup__body" style="padding: 20px;">
{% if user_gift_certificate.user.id != request.user.id %}
<div class="subtitle2">{{ user_gift_certificate.user.get_full_name }}
подарил{% if user_gift_certificate.user.gender == 'f' %}а{% endif %} вам Сертификат!</div>
{% else %}
<div class="subtitle2">Получите подарочный сертификат на сумму {{ gift_certificate.price|floatformat:"-2" }}₽!</div>
{% endif %}
<div class="gift-certificates__item" style="margin: 0;">
<div class="gift-certificates__preview theme_pink2"></div>
<div class="gift-certificates__details">
<span class="gift-certificates__title theme theme_pink2">подарочный сертификат</span>
<div class="gift-certificates__price">{{ gift_certificate.price|floatformat:"-2" }}₽</div>
</div>
<a class="gift-certificates__buy-btn btn"
{% if request.user.is_authenticated %}
href="{% url 'gift-certificate-get' user_gift_certificate.code %}"
{% else %}
href="#" data-popup=".js-popup-auth" data-auth-next-url="{% url 'gift-certificate-get' user_gift_certificate.code %}"
{% endif %}>Получить сертификат</a>
</div>
</div>
</div>
</div>

@ -26,6 +26,10 @@
{% endif %}
class="main__btn btn"
>{% if not school_schedule.weekday in school_schedules_purchased %}Получить доступ{% else %}Смотреть урок{% endif %}</a>
{% if not is_purchased and not is_purchased_future %}
<a class="main__btn btn btn_stroke-black" href="{% url 'gift-certificates' %}">Подарить другу</a>
{% endif %}
</div>
{% elif user.is_authenticated and online_coming_soon and school_schedule and school_schedule.start_at_humanize %}
<div class="">
@ -45,6 +49,10 @@
{% endif %}
class="main__btn btn"
>{% if not school_schedule.weekday in school_schedules_purchased %}Получить доступ{% else %}Смотреть урок{% endif %}</a>
{% if not is_purchased and not is_purchased_future %}
<a class="main__btn btn btn_stroke-black" href="{% url 'gift-certificates' %}">Подарить другу</a>
{% endif %}
</div>
{% else %}
<div class="main__subtitle">
@ -59,6 +67,7 @@
>
купить доступ от {{ min_school_price }} руб./месяц
</a>
<a class="main__btn btn btn_stroke-black" href="{% url 'gift-certificates' %}">Подарить другу</a>
{% else %}
<a class="main__btn btn btn_white" href="{% url 'school:school' %}">Подробнее</a>
{% endif %}

@ -18,9 +18,9 @@
<meta property="og:title" content="{% block ogtitle %}Онлайн-курсы Lil School{% endblock ogtitle %}">
{% comment %} <meta property="og:type" content="article"> {% endcomment %}
<meta property="og:url" content="{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}">
<meta property="og:image" content="http://lil.school/static/img/video-1.jpg">
<meta property="og:image:width" content="597" />
<meta property="og:image:height" content="318" />
<meta property="og:image" content="{% block ogimage %}http://{% setting 'MAIN_HOST' %}{% static 'img/video-1.jpg' %}{% endblock ogimage %}">
<meta property="og:image:width" content="{% block ogimage-width %}597{% endblock ogimage-width %}" />
<meta property="og:image:height" content="{% block ogimage-height %}318{% endblock ogimage-height %}" />
<meta property="og:description" content="{% block ogdescription %}Lil School — первая образовательная онлайн-платформа креативного мышления для детей{% endblock ogdescription %}">
<meta property="og:site_name" content="Онлайн-курсы Lil School">
<meta property="og:locale" content="ru_RU">
@ -145,6 +145,10 @@
{% if course %}
{% include "templates/blocks/popup_course_buy.html" %}
{% endif %}
{% if is_gift_certificate_url %}
{% include "templates/blocks/popup_gift_certificate.html" %}
{% endif %}
{% include "templates/blocks/popup_enter_gift_code.html" %}
{% include "templates/blocks/popup_course_lock.html" %}
{% include "templates/blocks/popup_subscribe.html" %}
{% include "templates/blocks/popup_capture_email.html" %}

@ -34,8 +34,8 @@ from apps.user.views import (
from apps.payment.views import (
CourseBuySuccessView, CourseBuyView,
PaymentwallCallbackView, SchoolBuySuccessView,
SchoolBuyView,
)
SchoolBuyView, GiftCertificatesView, GiftCertificateBuyView,
GiftCertificateBuySuccessView, GiftCertificateGetView)
from .views import AboutView, IndexView, SchoolSchedulesView
@ -91,6 +91,11 @@ urlpatterns = [
path('contest/<str:slug>/', ContestView.as_view(), name='contest'),
path('contest-work/<int:pk>/', ContestWorkView.as_view(), name='contest_work'),
path('contest-work/<int:contest_work_id>/comment', contest_work_comment, name='contest_work_comment'),
path('gift-certificates', GiftCertificatesView.as_view(), name='gift-certificates'),
path('gift-certificate/<int:pk>/checkout', GiftCertificateBuyView.as_view(), name='gift-certificate-checkout'),
path('payments/gift-certificate/<int:payment_id>/success', GiftCertificateBuySuccessView.as_view(),
name='gift-certificate-payment-success'),
path('gift-certificate/<str:slug>/get', GiftCertificateGetView.as_view(), name='gift-certificate-get'),
]

@ -9,7 +9,7 @@ from paymentwall.pingback import Pingback
from apps.course.models import Course
from apps.school.models import SchoolSchedule
from apps.payment.models import SchoolPayment
from apps.payment.models import SchoolPayment, UserGiftCertificate
User = get_user_model()
@ -23,6 +23,7 @@ class IndexView(TemplateView):
def get_context_data(self):
referrer = self.request.GET.get('referrer')
user_gift_certificate = self.request.GET.get('gift-certificate')
is_referral_url = bool(referrer)
context = super().get_context_data()
@ -77,7 +78,17 @@ class IndexView(TemplateView):
else:
referrer = None
if user_gift_certificate:
try:
user_gift_certificate = short_url.decode_url(user_gift_certificate)
user_gift_certificate = UserGiftCertificate.objects.get(pk=user_gift_certificate, bonuses_sent__isnull=True)
except:
user_gift_certificate = None
context.update({
'gift_certificate': user_gift_certificate.gift_certificate if user_gift_certificate else None,
'user_gift_certificate': user_gift_certificate,
'is_gift_certificate_url': bool(user_gift_certificate),
'referrer': referrer,
'is_referral_url': is_referral_url,
'online': online,

@ -33,3 +33,4 @@ django-imagekit
pusher==2.0.1
short_url
sendgrid
drf_dynamic_fields

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -245,6 +245,21 @@ $(document).ready(function () {
}
popup.data('next-url', nextUrl);
}
if( data === '.js-popup-enter-gift-code') {
const $giftCode = popup.find('.enter-gift-code__code');
const $giftError = popup.find('.enter-gift-code__error');
$giftCode.val('');
$giftError.text('');
popup.find('.enter-gift-code__btn').one('click', () => {
const code = $giftCode.val();
if(! code){
$giftError.text('Укажите код');
return false;
}
window.location.href = `/gift-certificate/${code}/get`;
});
}
});
$('.js-popup-close').on('click', function(e){
@ -271,7 +286,13 @@ $(document).ready(function () {
}
});
function showPopup(popupName){
if(window.LIL_STORE.flags.isGiftCertificateUrl){
popup = $('.js-popup-gift-certificate');
showPopup();
}
function showPopup(){
if(! popup && popupName){
popup = $(popupName);
}

@ -26,12 +26,15 @@
$pink: #FF9393
$pink-light: #FDF8F9
$pink2: #FEB9B9
$purple: #B995D9
$green: #8ECFC0
$green-light: #5BD700
$cyan: #B6DFD6
$gray: #A7A7A7
$blue: #4A90E2
$viol: #B995D9
$viol2: #A186BD
$bg: #f8f8f8
$border: #E6E6E6
$cl: #191919
@ -206,6 +209,10 @@ button
&_pink
background: #ff9393
color: white
&_stroke-black
background: none
border: 1px solid $cl
color: $cl
&_md
padding: 18px 24px 17px
+m
@ -1063,6 +1070,7 @@ a[name]
flex: 0 0 40px
&__info
flex: 0 0 calc(100% - 50px)
display: flex
&__name,
&__date
+fb
@ -1070,9 +1078,8 @@ a[name]
text-transform: uppercase
letter-spacing: 0.5px
line-height: 1.1
&__name
margin-bottom: 5px
&__meta
flex: 1
display: flex
align-items: center
&__date
@ -1635,6 +1642,12 @@ a.grey-link
color: $green
&_violet
color: $viol
&_violet2
color: $viol2
&_pink2
color: $pink2
&_cyan
color: $cyan
&_lg
font-size: 20px
letter-spacing: 4px
@ -2546,6 +2559,7 @@ a.grey-link
&__item
display: flex
align-items: center
color: inherit
&__item
//&:not(:last-child)
margin-right: 40px
@ -3202,12 +3216,16 @@ a.grey-link
background: transparent
border: 1px solid $gray
color: $gray
&_edit
position: absolute
top: 0
right: 0
+m
display: none !important
margin-bottom: 10px
width: 100%
&__btns
position: absolute
top: 0
right: 0
display: flex
flex-direction: column
+m
display: none !important
&__row
display: flex
margin-bottom: 25px
@ -3987,7 +4005,7 @@ a.grey-link
padding-right: 20px
flex: 0 0 90px
&--content
flex: 0 0 calc(100% - 254px)
flex: 1
+m
flex: 0 0 calc(100% - 114px)
&--toggle
@ -4306,6 +4324,13 @@ a
background: white
+m
height: auto
&__image-wrap
background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(159,159,159,0) 72%, rgba(221,221,221,0.65) 100%)
background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(159,159,159,0) 72%,rgba(221,221,221,0.65) 100%)
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(159,159,159,0) 72%,rgba(221,221,221,0.65) 100%)
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#a6dddddd',GradientType=0 )
width: 100%
height: 100%
&__image
height: 100%
+m
@ -4410,3 +4435,64 @@ a
&__lil-coin-img
margin-bottom: -5px
margin-right: 4px
.gift-certificates
display: flex
margin: 0 -10px
flex-wrap: wrap
+m
display: block
margin: 0
&__item
display: block
margin: 0 10px 60px
color: $cl
flex: 0 0 calc(33.33% - 20px)
+t
margin-bottom: 50px !important
+m
margin: 0 0 30px
&__preview
display: block
position: relative
margin-bottom: 15px
border-radius: 2px
color: $cl
overflow: hidden
width: 300px
height: 200px
+t
margin-bottom: 10px
&__cover
object-fit: cover;
width: 100%;
&__details
display: flex
margin-bottom: 10px
&__price
margin-left: auto
+fb
font-size: 12px
letter-spacing: 2px
color: $cl
&__title
text-transform: uppercase
&__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
&__buy-btn
width: 100%
&__preview.theme_pink2
background: $pink2
&__preview.theme_cyan
background: $cyan
&__preview.theme_violet2
background: $viol2

@ -14,6 +14,7 @@ module.exports = {
sprite: glob('./src/icons/*.svg'),
images: glob('./src/img/*.*'),
imagesCertificates: glob('./src/img/user-certificates/*'),
imagesGiftCertificates: glob('./src/img/gift-certificates/*'),
fonts: glob('./src/fonts/*')
},
output: {
@ -85,7 +86,8 @@ module.exports = {
},
{
test: /\.(png|gif|jpg|svg)$/,
exclude: [path.resolve(__dirname, 'src/icons'), path.resolve(__dirname, 'src/img/user-certificates')],
exclude: [path.resolve(__dirname, 'src/icons'), path.resolve(__dirname, 'src/img/user-certificates'),
path.resolve(__dirname, 'src/img/gift-certificates')],
loader: 'file-loader?name=[name].[ext]&outputPath=./img/'
},
{
@ -93,6 +95,11 @@ module.exports = {
include: path.resolve(__dirname, 'src/img/user-certificates'),
loader: 'file-loader?name=[name].[ext]&outputPath=./img/user-certificates/'
},
{
test: /\.(png|jpg)$/,
include: path.resolve(__dirname, 'src/img/gift-certificates'),
loader: 'file-loader?name=[name].[ext]&outputPath=./img/gift-certificates/'
},
{
test: /\.(ttf|otf|eot|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader?name=[name].[ext]'

Loading…
Cancel
Save