Merge branch 'feature/lil-583' into 'master'

Feature/lil 583

See merge request lilcity/backend!101
remotes/origin/hotfix/LIL-691
cfwme 7 years ago
commit 4db2d1acfd
  1. 4
      api/v1/serializers/config.py
  2. 26
      apps/auth/views.py
  3. 23
      apps/config/migrations/0009_auto_20180729_0503.py
  4. 23
      apps/config/migrations/0010_auto_20180820_0853.py
  5. 4
      apps/config/models.py
  6. 23
      apps/payment/migrations/0020_userbonus.py
  7. 27
      apps/payment/migrations/0021_auto_20180903_2257.py
  8. 19
      apps/payment/migrations/0022_auto_20180904_0106.py
  9. 19
      apps/payment/migrations/0023_payment_bonus.py
  10. 100
      apps/payment/models.py
  11. 22
      apps/payment/views.py
  12. 31
      apps/user/migrations/0024_referral.py
  13. 25
      apps/user/models.py
  14. 14
      apps/user/templates/blocks/profile-menu.html
  15. 71
      apps/user/templates/user/bonus-history.html
  16. 10
      apps/user/templates/user/notification-settings.html
  17. 36
      apps/user/templates/user/payment-history.html
  18. 10
      apps/user/templates/user/profile-settings.html
  19. 26
      apps/user/views.py
  20. 19
      project/context_processors.py
  21. 2
      project/settings.py
  22. 5
      project/templates/blocks/lil_store_js.html
  23. 16
      project/templates/blocks/popup_auth.html
  24. 13
      project/templates/blocks/popup_buy.html
  25. 33
      project/templates/blocks/user_menu.html
  26. 2
      project/templates/lilcity/index.html
  27. 7
      project/urls.py
  28. 15
      project/views.py
  29. 1
      requirements.txt
  30. 4
      web/src/js/modules/auth.js
  31. 23
      web/src/js/modules/common.js
  32. 42
      web/src/js/modules/popup.js
  33. 4
      web/src/js/modules/profile.js
  34. 5
      web/src/js/utils.js
  35. 60
      web/src/sass/_common.sass

@ -20,6 +20,8 @@ class ConfigSerializer(serializers.ModelSerializer):
MAIN_PAGE_TOP_IMAGE = serializers.SerializerMethodField()
# SCHOOL_LOGO_IMAGE = serializers.ImageField(required=False, allow_null=True)
# MAIN_PAGE_TOP_IMAGE = serializers.ImageField(required=False, allow_null=True)
REFERRER_BONUS = serializers.IntegerField()
REFERRAL_BONUS = serializers.IntegerField()
class Meta:
model = Config
@ -37,6 +39,8 @@ class ConfigSerializer(serializers.ModelSerializer):
'INSTAGRAM_PROFILE_URL',
'SCHOOL_LOGO_IMAGE',
'MAIN_PAGE_TOP_IMAGE',
'REFERRER_BONUS',
'REFERRAL_BONUS',
)
def get_SCHOOL_LOGO_IMAGE(self, config):

@ -19,6 +19,7 @@ from django.shortcuts import redirect
from apps.notification.utils import send_email
from apps.config.models import Config
from apps.user.models import Referral
from .forms import LearnerRegistrationForm
from .tokens import verification_email_token
@ -31,6 +32,7 @@ class LearnerRegistrationView(FormView):
template_name = "auth/registration-learner.html"
def form_valid(self, form):
config = Config.load()
first_name = form.cleaned_data['first_name']
last_name = form.cleaned_data['last_name']
email = form.cleaned_data['email'].lower()
@ -52,15 +54,20 @@ class LearnerRegistrationView(FormView):
user.set_password(password)
user.save()
referrer = self.request.session.get('referrer')
if referrer:
Referral.objects.create(referral=user, referrer_id=referrer, bonus=config.REFERRAL_BONUS,
referrer_bonus=config.REFERRER_BONUS)
# TODO: email admins? мб реферера уже нет, старая ссылка
self.request.session['referrer'] = None
login(self.request, user)
# fixme: change email text
# fixme: async send email
config = Config.load()
refferer = urlsplit(self.request.META.get('HTTP_REFERER'))
refferer = str(refferer[0]) + '://' + str(refferer[1])
http_referer = urlsplit(self.request.META.get('HTTP_REFERER'))
http_referer = str(http_referer[0]) + '://' + str(http_referer[1])
token = verification_email_token.make_token(user)
url = refferer + str(reverse_lazy('lilcity:verification-email', args=[token, user.id]))
url = http_referer + str(reverse_lazy('lilcity:verification-email', args=[token, user.id]))
send_email('Вы успешно прошли регистрацию', email, "notification/email/verification_email.html", url=url, config=config)
return JsonResponse({"success": True}, status=201)
@ -81,6 +88,7 @@ class LoginView(FormView):
def form_valid(self, form):
login(self.request, form.get_user())
self.request.session['referrer'] = None
return JsonResponse({"success": True})
def form_invalid(self, form):
@ -97,6 +105,7 @@ class VerificationEmailView(View):
user.is_email_proved = True
user.save()
login(request, user)
self.request.session['referrer'] = None
return redirect(reverse_lazy('lilcity:success-verification-email'))
else:
return JsonResponse({"success": False}, status=400)
@ -186,6 +195,14 @@ class FacebookLoginOrRegistration(View):
user.photo.save(fname, photo, save=True)
user.save()
referrer = self.request.session.get('referrer')
if referrer:
config = Config.load()
Referral.objects.create(referral=user, referrer_id=referrer, bonus=config.REFERRAL_BONUS,
referrer_bonus=config.REFERRER_BONUS)
# TODO: email admins? мб реферера уже нет, старая ссылка
self.request.session['referrer'] = None
login(requests, user=user)
return JsonResponse({"success": True})
else:
@ -194,4 +211,5 @@ class FacebookLoginOrRegistration(View):
fname = str(fb_id) + '.jpg'
user.photo.save(fname, photo, save=True)
login(requests, user=user)
self.request.session['referrer'] = None
return JsonResponse({"success": True})

@ -0,0 +1,23 @@
# Generated by Django 2.0.6 on 2018-07-29 05:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('config', '0008_auto_20180425_1451'),
]
operations = [
migrations.AddField(
model_name='config',
name='REFERRAL_DISCOUNT',
field=models.IntegerField(default=10),
),
migrations.AddField(
model_name='config',
name='REFERRER_CASHBACK',
field=models.IntegerField(default=10),
),
]

@ -0,0 +1,23 @@
# Generated by Django 2.0.6 on 2018-08-20 08:53
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('config', '0009_auto_20180729_0503'),
]
operations = [
migrations.RenameField(
model_name='config',
old_name='REFERRAL_DISCOUNT',
new_name='REFERRAL_BONUS',
),
migrations.RenameField(
model_name='config',
old_name='REFERRER_CASHBACK',
new_name='REFERRER_BONUS',
),
]

@ -17,6 +17,8 @@ class Config(models.Model):
SERVICE_DISCOUNT = models.IntegerField(default=1000)
SCHOOL_LOGO_IMAGE = models.ImageField(null=True, blank=True)
MAIN_PAGE_TOP_IMAGE = models.ImageField(null=True, blank=True)
REFERRER_BONUS = models.IntegerField(default=10)
REFERRAL_BONUS = models.IntegerField(default=10)
def save(self, *args, **kwargs):
self.pk = 1
@ -45,5 +47,7 @@ class Config(models.Model):
'SERVICE_DISCOUNT': '',
'SCHOOL_LOGO_IMAGE': '',
'MAIN_PAGE_TOP_IMAGE': '',
'REFERRER_BONUS': '',
'REFERRAL_BONUS': '',
}
return obj

@ -0,0 +1,23 @@
# Generated by Django 2.0.6 on 2018-09-03 22:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('payment', '0019_payment_roistat_visit'),
]
operations = [
migrations.CreateModel(
name='UserBonus',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, default=0, editable=False, max_digits=8)),
('created_at', models.DateTimeField(auto_now_add=True)),
('payment', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='payment.Payment')),
],
),
]

@ -0,0 +1,27 @@
# Generated by Django 2.0.6 on 2018-09-03 22:57
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('user', '0024_referral'),
('payment', '0020_userbonus'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='userbonus',
name='referral',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='user.Referral'),
),
migrations.AddField(
model_name='userbonus',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bonuses', to=settings.AUTH_USER_MODEL),
),
]

@ -0,0 +1,19 @@
# Generated by Django 2.0.6 on 2018-09-04 01:06
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('payment', '0021_auto_20180903_2257'),
]
operations = [
migrations.AlterField(
model_name='userbonus',
name='payment',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='payment.Payment'),
),
]

@ -0,0 +1,19 @@
# Generated by Django 2.0.6 on 2018-09-05 23:37
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('payment', '0022_auto_20180904_0106'),
]
operations = [
migrations.AddField(
model_name='payment',
name='bonus',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchase_payments', to='payment.UserBonus'),
),
]

@ -1,3 +1,4 @@
from decimal import Decimal
import arrow
from django.db.models import Func, F
@ -102,6 +103,7 @@ class Payment(PolymorphicModel):
roistat_visit = models.PositiveIntegerField('Номер визита Roistat', null=True, editable=False)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
bonus = models.ForeignKey('payment.UserBonus', null=True, on_delete=models.SET_NULL, related_name='purchase_payments')
objects = PaymentManger()
@ -120,18 +122,23 @@ class Payment(PolymorphicModel):
return result.datetime
@classmethod
def calc_amount(cls, course_payment=None, school_payment=None, user=None, course=None, date_start=None, weekdays=None):
def calc_amount(cls, payment=None, user=None, course=None, date_start=None, weekdays=None):
date_start = date_start or now().date()
date_end = Payment.add_months(date_start, 1)
if course_payment:
course = course_payment.course
user = course_payment.user
if school_payment:
user = school_payment.user
weekdays = school_payment.weekdays
date_start = school_payment.date_start
discount = 0
if isinstance(payment, CoursePayment):
course = payment.course
user = payment.user
if isinstance(payment, SchoolPayment):
user = payment.user
weekdays = payment.weekdays
date_start = payment.date_start
price = 0
referral_bonus = 0
referrer_bonus = 0
if hasattr(user, 'referral') and not user.referral.payment:
referral_bonus = user.referral.bonus
referrer_bonus = user.referral.referrer_bonus
discount = 0
if course:
price = course.price
else:
@ -140,7 +147,6 @@ class Payment(PolymorphicModel):
user=user,
date_start__lte=date_start,
date_end__gte=date_start,
add_days=False,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
@ -150,8 +156,8 @@ class Payment(PolymorphicModel):
school_schedules_purchased = school_payments.annotate(
joined_weekdays=Func(F('weekdays'), function='unnest', )
).values_list('joined_weekdays', flat=True).distinct()
weekdays = set(map(int, weekdays)) - set(school_schedules_purchased)
prev_school_payment = school_payments.last()
weekdays = list(set(map(int, weekdays)) - set(school_schedules_purchased))
prev_school_payment = school_payments.filter(add_days=False).last()
add_days = bool(prev_school_payment)
else:
add_days = False
@ -169,15 +175,20 @@ class Payment(PolymorphicModel):
price = school_schedules.aggregate(
models.Sum('month_price'),
).get('month_price__sum', 0)
if not (school_payment and school_payment.id) and price >= config.SERVICE_DISCOUNT_MIN_AMOUNT:
if not (payment and payment.id) and price >= config.SERVICE_DISCOUNT_MIN_AMOUNT:
discount = config.SERVICE_DISCOUNT
amount = price - discount
referral_bonus = round(amount * referral_bonus / 100)
referrer_bonus = round(amount * referrer_bonus / 100)
return {
'price': price,
'amount': amount,
'referral_bonus': referral_bonus,
'referrer_bonus': referrer_bonus,
'discount': discount,
'date_start': date_start,
'date_end': date_end,
'weekdays': weekdays,
}
def calc_commission(self):
@ -199,6 +210,36 @@ class Payment(PolymorphicModel):
Pingback.PINGBACK_TYPE_RISK_REVIEWED_DECLINED,
]
def save(self, *args, **kwargs):
paid = self.status in [Pingback.PINGBACK_TYPE_REGULAR, Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,]
amount_data = Payment.calc_amount(payment=self)
print('amount_data', amount_data)
if self.status is None and not self.bonus:
print(123)
self.amount = amount_data.get('amount')
if isinstance(self, SchoolPayment):
self.weekdays = amount_data.get('weekdays')
super().save(*args, **kwargs)
if isinstance(self, CoursePayment) and paid:
author_balance = getattr(self, 'authorbalance', None)
if not author_balance :
AuthorBalance.objects.create(
author=self.course.author,
amount=self.amount,
payment=self,
)
else:
author_balance.amount = self.amount
author_balance.save()
# Если юзер реферал и нет платежа, где применялась скидка
if hasattr(self.user, 'referral') and not self.user.referral.payment and paid:
# Платеж - как сигнал, что скидка применилась
self.user.referral.payment = self
self.user.referral.save()
# Отправляем кэшбэк
self.user.referral.send_bonuses(amount_data.get('referral_bonus'), amount_data.get('referrer_bonus'))
class CoursePayment(Payment):
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='Курс', related_name='payments')
@ -207,22 +248,6 @@ class CoursePayment(Payment):
verbose_name = 'Платеж за курс'
verbose_name_plural = 'Платежи за курсы'
def save(self, *args, **kwargs):
if self.status is None:
amount_data = Payment.calc_amount(course_payment=self)
self.amount = amount_data.get('amount')
super().save(*args, **kwargs)
author_balance = getattr(self, 'authorbalance', None)
if not author_balance:
AuthorBalance.objects.create(
author=self.course.author,
amount=self.amount,
payment=self,
)
else:
author_balance.amount = self.amount
author_balance.save()
class SchoolPayment(Payment):
weekdays = ArrayField(models.IntegerField(), size=7, verbose_name='Дни недели')
@ -241,12 +266,17 @@ class SchoolPayment(Payment):
])
return days
def save(self, *args, **kwargs):
if self.status is None:
amount_data = Payment.calc_amount(school_payment=self)
self.amount = amount_data.get('amount')
super().save(*args, **kwargs)
@property
def date_end_humanize(self):
return arrow.get(self.date_end, settings.TIME_ZONE).humanize(locale='ru')
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)
payment = models.ForeignKey(Payment, on_delete=models.SET_NULL, null=True)
referral = models.ForeignKey('user.Referral', on_delete=models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created_at',)

@ -27,7 +27,7 @@ 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
from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserBonus
logger = logging.getLogger('django')
@ -100,17 +100,18 @@ class SchoolBuyView(TemplateView):
host = urlsplit(self.request.META.get('HTTP_REFERER'))
host = str(host[0]) + '://' + str(host[1])
weekdays = set(request.GET.getlist('weekdays', []))
use_bonuses = request.GET.get('use_bonuses')
roistat_visit = request.COOKIES.get('roistat_visit', None)
date_start = request.GET.get('date_start')
date_start = date_start and datetime.datetime.strptime(date_start, '%Y-%m-%d') or now().date()
if not weekdays:
messages.error(request, 'Выберите несколько дней недели.')
return redirect('school:summer-school')
return redirect('school:school')
try:
weekdays = [int(weekday) for weekday in weekdays]
except ValueError:
messages.error(request, 'Ошибка выбора дней недели.')
return redirect('school:summer-school')
return redirect('school:school')
prev_school_payment = SchoolPayment.objects.filter(
user=request.user,
date_start__lte=date_start,
@ -132,6 +133,7 @@ class SchoolBuyView(TemplateView):
add_days=True,
roistat_visit=roistat_visit,
)
# Если произойдет ошибка и оплату бонусами повторят еще раз на те же дни, то вернет ошибку
if school_payment.amount <= 0:
messages.error(request, 'Выбранные дни отсутствуют в оставшемся периоде подписки')
return redirect(reverse_lazy('school:school'))
@ -143,6 +145,19 @@ class SchoolBuyView(TemplateView):
date_start=date_start,
date_end=Payment.add_months(date_start),
)
if use_bonuses:
if request.user.bonus >= school_payment.amount:
bonus = UserBonus.objects.create(amount= -school_payment.amount, user=request.user, payment=school_payment)
school_payment.amount = 0
school_payment.status = Pingback.PINGBACK_TYPE_REGULAR
else:
bonus = UserBonus.objects.create(amount= -request.user.bonus, user=request.user,
payment=school_payment)
school_payment.amount -= request.user.bonus
school_payment.bonus = bonus
school_payment.save()
if school_payment.status == Pingback.PINGBACK_TYPE_REGULAR:
return redirect(reverse_lazy('payment-success'))
product = Product(
f'school_{school_payment.id}',
school_payment.amount,
@ -213,6 +228,7 @@ class PaymentwallCallbackView(View):
product_type_name,
)
if product_type_name == 'course':
properties = {
'payment_id': payment.id,

@ -0,0 +1,31 @@
# Generated by Django 2.0.6 on 2018-09-03 22:57
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('payment', '0020_userbonus'),
('user', '0023_user_trial_lesson'),
]
operations = [
migrations.CreateModel(
name='Referral',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bonus', models.IntegerField()),
('referrer_bonus', models.IntegerField()),
('payment', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='payment.Payment')),
('referral', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='referral', to=settings.AUTH_USER_MODEL)),
('referrer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='referrals', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Реферал',
'verbose_name_plural': 'Рефералы',
},
),
]

@ -1,5 +1,6 @@
from json import dumps
from django.utils.functional import cached_property
from rest_framework.authtoken.models import Token
from phonenumber_field.modelfields import PhoneNumberField
@ -14,7 +15,6 @@ from django.urls import reverse
from api.v1 import serializers
from apps.notification.utils import send_email
from apps.user.tasks import user_to_mixpanel
@ -100,7 +100,7 @@ class User(AbstractUser):
user_data = dumps(user_data, ensure_ascii=False)
return user_data
@property
@cached_property
def balance(self):
income = self.balances.filter(
type=0,
@ -118,6 +118,10 @@ class User(AbstractUser):
return income_amount - income_commission - payout_amount
@cached_property
def bonus(self):
return int(self.bonuses.aggregate(models.Sum('amount')).get('amount__sum') or 0)
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
@ -271,3 +275,20 @@ class EmailSubscription(models.Model):
email = models.EmailField(_('email address'), unique=True)
categories = models.ManyToManyField(SubscriptionCategory)
mailchimp_status = models.PositiveSmallIntegerField(choices=MAILCHIMP_STATUS_CHOICES, default=ERROR)
class Referral(models.Model):
referral = models.OneToOneField(User, on_delete=models.CASCADE, related_name='referral')
referrer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='referrals')
bonus = models.IntegerField()
referrer_bonus = models.IntegerField()
payment = models.OneToOneField('payment.Payment', null=True, blank=True, on_delete=models.CASCADE)
class Meta:
verbose_name = 'Реферал'
verbose_name_plural = 'Рефералы'
def send_bonuses(self, referral_bonus, referrer_bonus):
from apps.payment.models import UserBonus
UserBonus.objects.create(user=self.referral, amount=referral_bonus, payment=self.payment, referral=self)
UserBonus.objects.create(user=self.referrer, amount=referrer_bonus, payment=self.payment, referral=self)

@ -0,0 +1,14 @@
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
<div class="menu">
<a class="menu__link {% if active == 'profile' %}active{% endif %}"
href="{% url 'user-edit-profile' %}">Профиль</a>
<a class="menu__link {% if active == 'notifications' %}active{% endif %}"
href="{% url 'user-edit-notifications' %}">Уведомления</a>
<a class="menu__link {% if active == 'payments' %}active{% endif %}"
href="{% url 'user-edit-payments' %}">Платежи</a>
<!--<a class="menu__link {% if active == 'bonuses' %}active{% endif %}"-->
<!--href="{% url 'user-bonuses' %}">Бонусы</a>-->
</div>
</div>
</div>

@ -0,0 +1,71 @@
{% extends "templates/lilcity/index.html" %}
{% load static %}
{% load rupluralize from plural %}
{% block content %}
{% include "../blocks/profile-menu.html" with active="bonuses" %}
<div class="section section_gray" style="padding-bottom: 0;">
<div class="section__center center center_xs">
<div class="title title_sm">Бонусы</div>
<div class="text">У вас {{ request.user.bonus|rupluralize:"бонус,бонуса,бонусов" }}</div>
<div>
Вы можете пригласить своих друзей в наш сервис, за это
{% if config.REFERRER_BONUS == config.REFERRAL_BONUS %}
вы и они получите по {{ config.REFERRER_BONUS }}%
{% else %}
вы получите {{ config.REFERRER_BONUS }}%, а они {{ config.REFERRAL_BONUS }}%
{% endif %}
от суммы их первой покупки на бонусный счет. Приглашайте друзей, накапливайте бонусные баллы, тратьте бонусы
на приобретения доступа к школе и курсам. Оплата услуг возможна только, если вы накопили баллов на стоимость услуги.
<br>
Просто отправьте им эту ссылку, по которой они смогут зарегистрироваться:
</div>
<div class="form__field field" style="margin-bottom: 0;">
<div class="field__wrap">
<input id="referrer-url" class="field__input" type="text" value="{{ referrer_url }}" readonly="readonly">
</div>
</div>
</div>
</div>
<div class="section section_gray">
<div class="section__center center center_sm">
<div class="title title_sm">История бонусов</div>
<div class="transactions transactions_bonuses">
<div class="transactions__wrap">
<div class="transactions__row">
<div class="transactions__cell transactions__product">Продукт</div>
<div class="transactions__cell transactions__amount">Сумма покупки</div>
<div class="transactions__cell transactions__user">Реферал</div>
<div class="transactions__cell transactions__amount">Сумма бонусов</div>
</div>
{% for bonus in bonuses %}
<div class="transactions__row">
{% with payment=bonus.payment %}
{% if payment.course %}
<div class="transactions__cell transactions__product">Курс. {{ payment.course.title }}</div>
{% else %}
<div class="transactions__cell transactions__product">
{% if request.user_agent.is_mobile %}
Школа. {% if payment.date_start and payment.date_end %}{{ payment.date_start|date:"j b" }} - {{ payment.date_end|date:"j b" }}{% endif %}
{% else %}
Школа. {% if payment.date_start and payment.date_end %}{{ payment.date_start }} - {{ payment.date_end }}{% endif %}
{% endif %}
</div>
{% endif %}
<div class="transactions__cell transactions__amount">{{payment.amount }}</div>
<div class="transactions__cell transactions__user">
{% if bonus.referral %}{{ bonus.referral.referral.get_full_name }}{% endif %}
</div>
<div class="transactions__cell transactions__amount">{{ bonus.amount|floatformat }}</div>
{% endwith %}
</div>
{% empty %}
Нет истории бонусов
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock content %}

@ -1,13 +1,5 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% block content %}
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
<div class="menu">
<a class="menu__link" href="{% url 'user-edit-profile' %}">Профиль</a>
<a class="menu__link active" href="{% url 'user-edit-notifications' user.id %}">Уведомления</a>
<a class="menu__link" href="{% url 'user-edit-payments' user.id %}">Платежи</a>
</div>
</div>
</div>
{% include "../blocks/profile-menu.html" with active="notifications" %}
{% if not user.is_email_proved %}
<div class="section section_confirm">
<div class="section__center center center_xs">

@ -1,13 +1,5 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% block content %}
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
<div class="menu">
<a class="menu__link" href="{% url 'user-edit-profile' %}">Профиль</a>
<a class="menu__link" href="{% url 'user-edit-notifications' user.id %}">Уведомления</a>
<a class="menu__link active" href="{% url 'user-edit-payments' user.id %}">Платежи</a>
</div>
</div>
</div>
{% include "../blocks/profile-menu.html" with active="payments" %}
{% if not user.is_email_proved %}
<div class="section section_confirm">
<div class="section__center center center_xs">
@ -73,42 +65,42 @@
<div class="section section_gray">
<div class="section__center center center_xs">
<div class="title title_sm">История платежей</div>
<div class="transactions">
<div class="transactions transactions_payments">
<div class="transactions__wrap">
{% if request.user.payments.all.exists %}
{% for payment in request.user.payments.all %}
<div class="transactions__row">
{% if payment.course %}
<div class="transactions__cell">Курс. {{payment.course.title}}</div>
<div class="transactions__cell transactions__product">Курс. {{payment.course.title}}</div>
{% else %}
<div class="transactions__cell">
<div class="transactions__cell transactions__product">
Школа. {% if payment.date_start and payment.date_end %}{{ payment.date_start }} - {{ payment.date_end }}{% endif %}
</div>
{% endif %}
{% if payment.balance %}
<div class="transactions__cell">{{payment.balance.amount}}</div>
<div class="transactions__cell transactions__amount">{{payment.balance.amount}}</div>
{% else %}
<div class="transactions__cell">{{payment.amount}}</div>
<div class="transactions__cell transactions__amount">{{payment.amount}}</div>
{% endif %}
{% if payment.balance.type == 1 %}
<div class="transactions__cell">
<div class="transactions__cell transactions__status">
{% if payment.balance.status == 0 %}
<span class="transactions__cell__pending">Ожидается подтверждение выплаты</span>
<span class="transactions__status_pending">Ожидается подтверждение выплаты</span>
{% elif payment.balance.status == 1 %}
<span class="transactions__cell__success">Выплачено</span>
<span class="transactions__status_success">Выплачено</span>
{% else %}
<span class="transactions__cell__error">Выплата отменена</span>
<span class="transactions__status_error">Выплата отменена</span>
Причина: "{{ payment.balance.cause }}
{% endif %}
</div>
{% else %}
<div class="transactions__cell">
<div class="transactions__cell transactions__status">
{% if payment.is_deliverable %}
<span class="transactions__cell__success">Оплачено</span>
<span class="transactions__status_success">Оплачено</span>
{% elif payment.is_under_review %}
<span class="transactions__cell__pending">Ожидается подтверждение оплаты</span>
<span class="transactions__status_pending">Ожидается подтверждение оплаты</span>
{% else %}
<span class="transactions__cell__error">Ошибка оплаты</span>
<span class="transactions__status_error">Ошибка оплаты</span>
{% endif %}
</div>
{% endif %}

@ -3,15 +3,7 @@
{% load settings %}
{% load thumbnail %}
{% block content %}
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
<div class="menu">
<a class="menu__link active" href="{% url 'user-edit-profile' %}">Профиль</a>
<a class="menu__link" href="{% url 'user-edit-notifications' user.id %}">Уведомления</a>
<a class="menu__link" href="{% url 'user-edit-payments' user.id %}">Платежи</a>
</div>
</div>
</div>
{% include "../blocks/profile-menu.html" with active="profile" %}
{% if not user.is_email_proved %}
<div class="section section_confirm">
<div class="section__center center center_xs">

@ -4,18 +4,16 @@ from uuid import uuid4
from os.path import splitext
from datetime import timedelta
from paymentwall import Pingback
import short_url
from django.conf import settings
from django.contrib.auth import login
from django.core.exceptions import ValidationError
from django.shortcuts import render, reverse, redirect
from django.shortcuts import reverse, redirect
from django.views import View
from django.views.generic import DetailView, UpdateView, TemplateView, FormView
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.hashers import check_password, make_password
from django.http import Http404
from django.contrib.auth.decorators import login_required
from django.db.models import F, Func, Sum, Min, Max
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
@ -147,7 +145,7 @@ class NotificationEditView(TemplateView):
request.user.email_subscription.categories.set(
SubscriptionCategory.objects.filter(id__in=categories)
)
return redirect('user-edit-notifications', request.user.id)
return redirect('user-edit-notifications')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -184,7 +182,7 @@ class PaymentHistoryView(FormView):
return self.form_invalid(form)
def get_success_url(self):
success_url = reverse_lazy('user-edit-payments', args=[self.request.user.id])
success_url = reverse_lazy('user-edit-payments')
return success_url
@ -293,3 +291,17 @@ class AuthorRequestView(FormView):
'facebook': self.request.user.facebook,
}
return super().get_context_data(**kwargs)
@method_decorator(login_required, name='dispatch')
class BonusHistoryView(TemplateView):
model = User
template_name = 'user/bonus-history.html'
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context['bonuses'] = request.user.bonuses.all()
context['referrer_url'] = '%s%s?referrer=%s' % (
settings.MAIN_HOST, reverse('index'), short_url.encode_url(request.user.id)
)
return self.render_to_response(context)

@ -1,11 +1,14 @@
from django.db.models import Func, F
from django.utils.timezone import now
from paymentwall.pingback import Pingback
from django.contrib.auth import get_user_model
from django.conf import settings
from apps.config.models import Config
from apps.content.models import Baner
from apps.payment.models import SchoolPayment
User = get_user_model()
def config(request):
return {"config": Config.load()}
@ -39,3 +42,17 @@ def school_purchased(request):
'is_school_purchased': False,
'school_schedules_purchased': [],
}
def referrer(request):
referrer = request.session.get('referrer')
if referrer:
try:
referrer = User.objects.get(pk=referrer)
except:
referrer = None
return {'referrer': referrer}
def settings(request):
return {'settings': settings}

@ -96,6 +96,8 @@ TEMPLATES = [
'project.context_processors.config',
'project.context_processors.baner',
'project.context_processors.school_purchased',
'project.context_processors.referrer',
'project.context_processors.settings',
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',

@ -23,5 +23,10 @@
courses: "{% url 'courses' %}",
userProfileEdit: "{% url 'user-edit-profile' %}",
}
flags: {
referrer: '{{ referrer.id|default:'' }}',
referrerName: '{% if referrer %}{{ referrer.get_full_name }}{% endif %}',
isReferralUrl: {{ is_referral_url|yesno:"true,false" }},
}
};
</script>

@ -10,13 +10,13 @@
<div class="auth js-auth">
<div class="auth__login js-auth-login">
<div class="auth__nav">
<a class="auth__type js-auth-type active" href="#">Войти</a>
<a class="auth__type js-auth-type" href="#">РЕГИСТРАЦИЯ</a>
<a class="auth__type js-auth-type_login js-auth-type" href="#">Войти</a>
<a class="auth__type js-auth-type_registration js-auth-type" href="#">РЕГИСТРАЦИЯ</a>
</div>
<div class="auth__body">
<form id="learner-auth-form" method="post" action="{% url 'lilcity:login' %}">
{% csrf_token %}
<div class="auth__tab js-auth-tab" style="display: block;">
<div class="auth__tab js-auth-tab js-auth-tab_login">
<div class="auth__enter js-auth-enter">
<div id="learner-auth-field__username" class="auth__field field learner-auth-form__field">
<div class="field__label">ПОЧТА</div>
@ -63,7 +63,15 @@
</form>
<form id="learner-registration-form" method="post" action="{% url 'lilcity:registration-learner' %}">
{% csrf_token %}
<div class="auth__tab js-auth-tab">
<div class="auth__tab js-auth-tab js-auth-tab_registration">
{% if referrer %}
<input type="hidden" name="referrer" value="{{ referrer.id }}">
<div class="auth__text">
Спасибо за&nbsp;то, что&nbsp;вы&nbsp;с&nbsp;нами! Вас&nbsp;пригласил ваш&nbsp;друг {{ referrer.get_full_name }}.
При&nbsp;первой покупке {{ config.REFERRAL_BONUS }}% от&nbsp;суммы услуги будут зачислены на&nbsp;ваш бонусный счет.
Приятного обучения!
</div>
{% endif %}
<div class="auth__fieldset">
<div id="learner-registration-field__first-name" class="auth__field field learner-registration-form__field">
<div class="field__label">ИМЯ</div>

@ -70,10 +70,21 @@
<div class="order__label">ШКОЛА</div>
<div class="order__dates"></div>
<div class="order__days"></div>
{% if request.user.bonuses %}
<label class="buy__bonuses switch" style="display: none;">
<input class="switch__input" type="checkbox" data-bonuses="{{ request.user.bonus }}">
<span class="switch__content">
<span class="switch__cell">Использовать <span class="buy__bonuses-count"></span></span>
</span>
</label>
{% endif %}
</div>
<div class="order__foot">
<div class="order__subtitle">Итого, за месяц:</div>
<div class="order__total order_price_text"></div>
<div class="order__total">
<div class="loading-loader"></div>
<span class="order_price_text"></span>
</div>
</div>
</div>
</div>

@ -1,4 +1,5 @@
{% load static %} {% load thumbnail %}
{% load rupluralize from plural %}
{% if request.user.is_authenticated %}
<div class="header__login">
<a class="header__ava ava" href="{% if request.user_agent.is_touch_capable %}#{% else %}{% url 'user-profile' %}{% endif %}">
@ -9,22 +10,26 @@
{% endthumbnail %}
</a>
<div class="header__drop">
{# FIXME #}
{% if request.user.role >= request.user.AUTHOR_ROLE %}
<a class="header__link header__link_border" href="{% url 'user-edit-payments' request.user.id %}">
{{ request.user.balance }} руб.
</a>
{% if request.user.auth_token %}
{% if request.user.role >= request.user.AUTHOR_ROLE %}
<a class="header__link header__link_green" href="{% url 'course_create' %}">
<div class="header__title">ДОБАВИТЬ КУРС</div>
</a>
{% endif %}
{% if request.user.role >= request.user.TEACHER_ROLE %}
<a class="header__link" href="{% url 'school:lessons' %}">
<div class="header__title">УРОКИ ШКОЛЫ</div>
</a>
{% endif %}
<a class="header__link header__link_border" href="{% url 'user-edit-payments' %}">
{{ request.user.balance }} руб.
</a>
{% endif %}
<!--<a class="header__link header__link_border" href="{% url 'user-bonuses' %}">-->
<!--{{ request.user.bonus|rupluralize:"бонус,бонуса,бонусов" }}-->
<!--</a>-->
{% if request.user.auth_token %}
{% if request.user.role >= request.user.AUTHOR_ROLE %}
<a class="header__link header__link_green" href="{% url 'course_create' %}">
<div class="header__title">ДОБАВИТЬ КУРС</div>
</a>
{% endif %}
{% if request.user.role >= request.user.TEACHER_ROLE %}
<a class="header__link" href="{% url 'school:lessons' %}">
<div class="header__title">УРОКИ ШКОЛЫ</div>
</a>
{% endif %}
{% endif %}
<a class="header__link" href="{% url 'user-profile' %}">
<div class="header__title">ПРОФИЛЬ</div>

@ -151,6 +151,7 @@
{% include "templates/blocks/popup_subscribe.html" %}
</div>
{% include 'templates/blocks/lil_store_js.html' %}
<script type="text/javascript" src={% static "app.js" %}></script>
<script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }});
@ -161,5 +162,6 @@
(function(w, d, s, h, id) { w.roistatProjectId = id; w.roistatHost = h; var p = d.location.protocol == "https:" ? "https://" : "http://"; var u = /^.*roistat_visit=[^;]+(.*)?$/.test(d.cookie) ? "/dist/module.js" : "/api/site/1.0/"+id+"/init"; var js = d.createElement(s); js.charset="UTF-8"; js.async = 1; js.src = p+h+u; var js2 = d.getElementsByTagName(s)[0]; js2.parentNode.insertBefore(js, js2);})(window, document, 'script', 'cloud.roistat.com', '{% setting "ROISTAT_COUNTER_ID" %}');
</script>
{% block foot %}{% endblock foot %}
{% block body_js %}{% endblock body_js %}
</body>
</html>

@ -30,7 +30,7 @@ from apps.user.views import (
ProfileEditView, NotificationEditView,
PaymentHistoryView, resend_email_verify,
SubscribeView, ProfileView,
)
BonusHistoryView)
from apps.payment.views import (
CourseBuySuccessView, CourseBuyView,
PaymentwallCallbackView, SchoolBuySuccessView,
@ -71,8 +71,9 @@ urlpatterns = [
path('user/profile/edit', ProfileEditView.as_view(), name='user-edit-profile'),
path('user/<int:pk>/', UserView.as_view(), name='user'),
path('user/<str:slug>/', UserView.as_view(), name='user'),
path('user/<int:pk>/notifications', NotificationEditView.as_view(), name='user-edit-notifications'),
path('user/<int:pk>/payments', PaymentHistoryView.as_view(), name='user-edit-payments'),
path('user/notifications', NotificationEditView.as_view(), name='user-edit-notifications'),
path('user/payments', PaymentHistoryView.as_view(), name='user-edit-payments'),
path('user/bonuses', BonusHistoryView.as_view(), name='user-bonuses'),
path('user/resend-email-verify', resend_email_verify, name='resend-email-verify'),
path('subscribe', SubscribeView.as_view(), name='subscribe'),
path('subscribe/success', TemplateView.as_view(template_name='templates/lilcity/subscribe_success.html'), name='subscribe-success'),

@ -1,5 +1,6 @@
from datetime import datetime, timedelta
import short_url
from django.db.models import Min, Func, F
from django.contrib.auth import get_user_model
from django.views.generic import TemplateView
@ -21,6 +22,8 @@ class IndexView(TemplateView):
template_name = 'templates/lilcity/main.html'
def get_context_data(self):
referrer = self.request.GET.get('referrer')
is_referral_url = bool(referrer)
context = super().get_context_data()
now_time = now()
@ -64,7 +67,19 @@ class IndexView(TemplateView):
school_payment_exists = False
school_schedules_purchased = []
if referrer and not self.request.user.is_authenticated:
try:
referrer = short_url.decode_url(referrer)
referrer = User.objects.get(pk=referrer)
except:
referrer = None
self.request.session['referrer'] = referrer and referrer.id
else:
referrer = None
context.update({
'referrer': referrer,
'is_referral_url': is_referral_url,
'online': online,
'online_coming_soon': online_coming_soon,
'school_schedule': school_schedule,

@ -31,3 +31,4 @@ ua-parser==0.8.0
django-ipware
django-imagekit
pusher==2.0.1
short_url

@ -41,7 +41,7 @@ $(document).ready(function () {
pass.hide();
// login.fadeIn();
window.location.reload();
window.location.href = '/';
});
$('#password-reset__success-hide').on('click', function (e) {
@ -246,7 +246,7 @@ $(document).ready(function () {
window.location.href = nextUrl;
}
else{
window.location.reload();
window.location = '/';
}
} else {
registrationButton.removeClass('loading');

@ -1,13 +1,17 @@
import $ from 'jquery';
import {showNotification} from "./notification"
import Inputmask from "inputmask";
import SmoothScroll from 'smooth-scroll/dist/js/smooth-scroll';
import baguetteBox from 'baguettebox.js'
import createHistory from 'history/createBrowserHistory'
window.$ = window.jQuery = jQuery = $;
window.Inputmask = Inputmask;
window.baguetteBox = baguetteBox;
$(document).ready(function () {
const $popupAuth = $('.js-popup-auth');
let authPopupActiveTab = 'login'
baguetteBox.run('.gallery');
// Добавляем заголовок X-CSRFToken для всех AJAX запросов JQuery.
$.ajaxSetup({
@ -40,6 +44,23 @@ $(document).ready(function () {
$('.banner').slideUp();
})
});
if(window.LIL_STORE.flags.isReferralUrl){
if(window.LIL_STORE.user.id){
showNotification('error', 'Извините, вы уже зарегестрировались на нашем сервисе, получить бонусные баллы возможно только для новых пользователей');
// Создаем объект history API
const history = createHistory();
history.replace('/');
}
else if(window.LIL_STORE.flags.referrer){
authPopupActiveTab = 'registration';
setTimeout(() => {
$('.js-header-enter').click();
});
}
}
$popupAuth.find(authPopupActiveTab == 'login' ? '.js-auth-type_login' : '.js-auth-type_registration').addClass('active');
$popupAuth.find(authPopupActiveTab == 'login' ? '.js-auth-tab_login' : '.js-auth-tab_registration').show();
});
function updateHeader() {
@ -67,4 +88,4 @@ function updateHeader() {
headerElement.css('top', '');
}
}
}
}

@ -1,6 +1,7 @@
import $ from 'jquery';
import moment from 'moment';
import {api} from './api';
import {rupluralize} from '../utils';
moment.locale('ru');
@ -35,7 +36,7 @@ $(document).ready(function () {
});
let body = $('body'),
popup;
popup = $('.popup.visible.open');
body.on('click', '[data-popup]', function(e){
const $this = $(this);
@ -149,14 +150,24 @@ $(document).ready(function () {
updateCart();
});
$(document).on('change', '[data-bonuses]', function(){
updateCart();
});
function updateCart(){
var link = $('.but_btn_popup').data('link');
var $order = $('.order');
var $orderPrice = $('.order_price_text');
var $orderDates = $('.order__dates');
var dateStart = popup.data('date-start');
var days = ['', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'];
var weekdays = [];
var daysText = [];
var $bonuses = $('.buy__bonuses');
var $bonusesCheckbox = $('[data-bonuses]');
var $bonusesCount = $('.buy__bonuses-count');
var useBonuses = $bonusesCheckbox.prop('checked');
var bonuses = +$bonusesCheckbox.data('bonuses');
$('[data-day]').each(function() {
var weekday = $(this).data('day');
if($(this).is(':checked')) {
@ -166,25 +177,40 @@ $(document).ready(function () {
});
if(weekdays.length){
$order.addClass('order--loading');
$orderPrice.html('');
api.getPaymentAmount({ user: window.LIL_STORE.user.id, weekdays: weekdays, date_start: dateStart})
.then((response) => {
.then(response => {
$order.removeClass('order--loading');
var text = '';
if(response.data.price != response.data.amount) {
text = '<del>'+response.data.price+'</del> '+response.data.amount+'р.';
var amount = +response.data.amount;
if(bonuses){
$bonusesCount.text(rupluralize(bonuses > amount ? amount : bonuses, ['бонус', 'бонуса', 'бонусов']));
$bonuses.show();
if(useBonuses) {
amount = bonuses > amount ? 0 : amount - bonuses;
}
}
if(response.data.price != amount) {
text = '<del>' + response.data.price+'</del> ' + amount + 'р.';
} else {
text = response.data.amount+'p.';
text = amount + 'p.';
}
$orderPrice.html(text);
$orderDates.text(moment(response.data.date_start).format('D MMM') + ' - ' + moment(response.data.date_end).format('D MMM'));
$('.but_btn_popup').attr('href', link+'?'+decodeURIComponent($.param({
weekdays: weekdays,
date_start: moment(response.data.date_start).format('YYYY-MM-DD')
weekdays: weekdays,
date_start: moment(response.data.date_start).format('YYYY-MM-DD'),
use_bonuses: useBonuses || '',
}, true)));
}).catch(() => {
$order.removeClass('order--loading');
});
}
else {
$orderPrice.html('0p.');
$bonuses.hide();
}
$('.order__days').html(daysText.length ? daysText.join(', ') : 'Ничего не выбрано');
}
});

@ -41,4 +41,8 @@ export const main = () => {
});
changeSlug();
}
$('#referrer-url').select().click(function(){
$(this).select();
});
}

@ -0,0 +1,5 @@
export const rupluralize = (value, args) => {
let digit = Math.trunc(value) + '';
digit = digit[digit.length - 1];
return value + ' ' + args[digit == '1' ? 0 : ('234'.search(digit) > -1 ? 1 : 2)];
}

@ -3095,8 +3095,14 @@ a.grey-link
padding: 20px 20px 0
&__total
margin-left: auto
position: relative
del
color: #a0a0a0
& .loading-loader
display: none
margin-left: -40px
&--loading .loading-loader
display: block
.lock
padding: 50px 60px 40px
@ -3301,31 +3307,41 @@ a.grey-link
&__cell
padding: 0 10px
font-size: 13px
&__success
&__product
+fb
font-size: 12px
text-transform: uppercase
letter-spacing: 2px
flex: 0 0 60%
+m
flex: 0 0 75%
&__amount
flex: 0 0 20%
text-align: right
+m
flex: 0 0 25%
&__status
flex: 0 0 20%
text-align: right
color: $green-light
+m
flex: 0 0 100%
&_success
color: $green
&__pending
&_pending
color: $gray
&__error
&_error
color: $pink
&:first-child
+fb
font-size: 12px
text-transform: uppercase
letter-spacing: 2px
flex: 0 0 60%
+m
flex: 0 0 75%
&:nth-child(2),
&:last-child
flex: 0 0 20%
text-align: right
&:nth-child(2)
+m
flex: 0 0 25%
&:last-child
color: $green-light
+m
flex: 0 0 100%
.transactions_bonuses .transactions
&__product
flex: 0 0 60%;
+m
flex: 1 0 40%;
&__amount
flex: 0 0 10%;
&__user
flex: 0 0 20%;
.empty
max-width: 300px

Loading…
Cancel
Save