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. 21
      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() MAIN_PAGE_TOP_IMAGE = serializers.SerializerMethodField()
# SCHOOL_LOGO_IMAGE = serializers.ImageField(required=False, allow_null=True) # SCHOOL_LOGO_IMAGE = serializers.ImageField(required=False, allow_null=True)
# MAIN_PAGE_TOP_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: class Meta:
model = Config model = Config
@ -37,6 +39,8 @@ class ConfigSerializer(serializers.ModelSerializer):
'INSTAGRAM_PROFILE_URL', 'INSTAGRAM_PROFILE_URL',
'SCHOOL_LOGO_IMAGE', 'SCHOOL_LOGO_IMAGE',
'MAIN_PAGE_TOP_IMAGE', 'MAIN_PAGE_TOP_IMAGE',
'REFERRER_BONUS',
'REFERRAL_BONUS',
) )
def get_SCHOOL_LOGO_IMAGE(self, config): 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.notification.utils import send_email
from apps.config.models import Config from apps.config.models import Config
from apps.user.models import Referral
from .forms import LearnerRegistrationForm from .forms import LearnerRegistrationForm
from .tokens import verification_email_token from .tokens import verification_email_token
@ -31,6 +32,7 @@ class LearnerRegistrationView(FormView):
template_name = "auth/registration-learner.html" template_name = "auth/registration-learner.html"
def form_valid(self, form): def form_valid(self, form):
config = Config.load()
first_name = form.cleaned_data['first_name'] first_name = form.cleaned_data['first_name']
last_name = form.cleaned_data['last_name'] last_name = form.cleaned_data['last_name']
email = form.cleaned_data['email'].lower() email = form.cleaned_data['email'].lower()
@ -52,15 +54,20 @@ class LearnerRegistrationView(FormView):
user.set_password(password) user.set_password(password)
user.save() 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) login(self.request, user)
# fixme: change email text # fixme: change email text
# fixme: async send email # fixme: async send email
config = Config.load() http_referer = urlsplit(self.request.META.get('HTTP_REFERER'))
refferer = urlsplit(self.request.META.get('HTTP_REFERER')) http_referer = str(http_referer[0]) + '://' + str(http_referer[1])
refferer = str(refferer[0]) + '://' + str(refferer[1])
token = verification_email_token.make_token(user) 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) send_email('Вы успешно прошли регистрацию', email, "notification/email/verification_email.html", url=url, config=config)
return JsonResponse({"success": True}, status=201) return JsonResponse({"success": True}, status=201)
@ -81,6 +88,7 @@ class LoginView(FormView):
def form_valid(self, form): def form_valid(self, form):
login(self.request, form.get_user()) login(self.request, form.get_user())
self.request.session['referrer'] = None
return JsonResponse({"success": True}) return JsonResponse({"success": True})
def form_invalid(self, form): def form_invalid(self, form):
@ -97,6 +105,7 @@ class VerificationEmailView(View):
user.is_email_proved = True user.is_email_proved = True
user.save() user.save()
login(request, user) login(request, user)
self.request.session['referrer'] = None
return redirect(reverse_lazy('lilcity:success-verification-email')) return redirect(reverse_lazy('lilcity:success-verification-email'))
else: else:
return JsonResponse({"success": False}, status=400) return JsonResponse({"success": False}, status=400)
@ -186,6 +195,14 @@ class FacebookLoginOrRegistration(View):
user.photo.save(fname, photo, save=True) user.photo.save(fname, photo, save=True)
user.save() 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) login(requests, user=user)
return JsonResponse({"success": True}) return JsonResponse({"success": True})
else: else:
@ -194,4 +211,5 @@ class FacebookLoginOrRegistration(View):
fname = str(fb_id) + '.jpg' fname = str(fb_id) + '.jpg'
user.photo.save(fname, photo, save=True) user.photo.save(fname, photo, save=True)
login(requests, user=user) login(requests, user=user)
self.request.session['referrer'] = None
return JsonResponse({"success": True}) 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) SERVICE_DISCOUNT = models.IntegerField(default=1000)
SCHOOL_LOGO_IMAGE = models.ImageField(null=True, blank=True) SCHOOL_LOGO_IMAGE = models.ImageField(null=True, blank=True)
MAIN_PAGE_TOP_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): def save(self, *args, **kwargs):
self.pk = 1 self.pk = 1
@ -45,5 +47,7 @@ class Config(models.Model):
'SERVICE_DISCOUNT': '', 'SERVICE_DISCOUNT': '',
'SCHOOL_LOGO_IMAGE': '', 'SCHOOL_LOGO_IMAGE': '',
'MAIN_PAGE_TOP_IMAGE': '', 'MAIN_PAGE_TOP_IMAGE': '',
'REFERRER_BONUS': '',
'REFERRAL_BONUS': '',
} }
return obj 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 import arrow
from django.db.models import Func, F from django.db.models import Func, F
@ -102,6 +103,7 @@ class Payment(PolymorphicModel):
roistat_visit = models.PositiveIntegerField('Номер визита Roistat', null=True, editable=False) roistat_visit = models.PositiveIntegerField('Номер визита Roistat', null=True, editable=False)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=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() objects = PaymentManger()
@ -120,18 +122,23 @@ class Payment(PolymorphicModel):
return result.datetime return result.datetime
@classmethod @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_start = date_start or now().date()
date_end = Payment.add_months(date_start, 1) date_end = Payment.add_months(date_start, 1)
if course_payment: if isinstance(payment, CoursePayment):
course = course_payment.course course = payment.course
user = course_payment.user user = payment.user
if school_payment: if isinstance(payment, SchoolPayment):
user = school_payment.user user = payment.user
weekdays = school_payment.weekdays weekdays = payment.weekdays
date_start = school_payment.date_start date_start = payment.date_start
discount = 0
price = 0 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: if course:
price = course.price price = course.price
else: else:
@ -140,7 +147,6 @@ class Payment(PolymorphicModel):
user=user, user=user,
date_start__lte=date_start, date_start__lte=date_start,
date_end__gte=date_start, date_end__gte=date_start,
add_days=False,
status__in=[ status__in=[
Pingback.PINGBACK_TYPE_REGULAR, Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL, Pingback.PINGBACK_TYPE_GOODWILL,
@ -150,8 +156,8 @@ class Payment(PolymorphicModel):
school_schedules_purchased = school_payments.annotate( school_schedules_purchased = school_payments.annotate(
joined_weekdays=Func(F('weekdays'), function='unnest', ) joined_weekdays=Func(F('weekdays'), function='unnest', )
).values_list('joined_weekdays', flat=True).distinct() ).values_list('joined_weekdays', flat=True).distinct()
weekdays = set(map(int, weekdays)) - set(school_schedules_purchased) weekdays = list(set(map(int, weekdays)) - set(school_schedules_purchased))
prev_school_payment = school_payments.last() prev_school_payment = school_payments.filter(add_days=False).last()
add_days = bool(prev_school_payment) add_days = bool(prev_school_payment)
else: else:
add_days = False add_days = False
@ -169,15 +175,20 @@ class Payment(PolymorphicModel):
price = school_schedules.aggregate( price = school_schedules.aggregate(
models.Sum('month_price'), models.Sum('month_price'),
).get('month_price__sum', 0) ).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 discount = config.SERVICE_DISCOUNT
amount = price - discount amount = price - discount
referral_bonus = round(amount * referral_bonus / 100)
referrer_bonus = round(amount * referrer_bonus / 100)
return { return {
'price': price, 'price': price,
'amount': amount, 'amount': amount,
'referral_bonus': referral_bonus,
'referrer_bonus': referrer_bonus,
'discount': discount, 'discount': discount,
'date_start': date_start, 'date_start': date_start,
'date_end': date_end, 'date_end': date_end,
'weekdays': weekdays,
} }
def calc_commission(self): def calc_commission(self):
@ -199,6 +210,36 @@ class Payment(PolymorphicModel):
Pingback.PINGBACK_TYPE_RISK_REVIEWED_DECLINED, 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): class CoursePayment(Payment):
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='Курс', related_name='payments') course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='Курс', related_name='payments')
@ -207,22 +248,6 @@ class CoursePayment(Payment):
verbose_name = 'Платеж за курс' verbose_name = 'Платеж за курс'
verbose_name_plural = 'Платежи за курсы' 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): class SchoolPayment(Payment):
weekdays = ArrayField(models.IntegerField(), size=7, verbose_name='Дни недели') weekdays = ArrayField(models.IntegerField(), size=7, verbose_name='Дни недели')
@ -241,12 +266,17 @@ class SchoolPayment(Payment):
]) ])
return days 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 @property
def date_end_humanize(self): def date_end_humanize(self):
return arrow.get(self.date_end, settings.TIME_ZONE).humanize(locale='ru') 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.school.models import SchoolSchedule
from apps.payment.tasks import transaction_to_mixpanel, product_payment_to_mixpanel, transaction_to_roistat 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') logger = logging.getLogger('django')
@ -100,17 +100,18 @@ class SchoolBuyView(TemplateView):
host = urlsplit(self.request.META.get('HTTP_REFERER')) host = urlsplit(self.request.META.get('HTTP_REFERER'))
host = str(host[0]) + '://' + str(host[1]) host = str(host[0]) + '://' + str(host[1])
weekdays = set(request.GET.getlist('weekdays', [])) weekdays = set(request.GET.getlist('weekdays', []))
use_bonuses = request.GET.get('use_bonuses')
roistat_visit = request.COOKIES.get('roistat_visit', None) roistat_visit = request.COOKIES.get('roistat_visit', None)
date_start = request.GET.get('date_start') date_start = request.GET.get('date_start')
date_start = date_start and datetime.datetime.strptime(date_start, '%Y-%m-%d') or now().date() date_start = date_start and datetime.datetime.strptime(date_start, '%Y-%m-%d') or now().date()
if not weekdays: if not weekdays:
messages.error(request, 'Выберите несколько дней недели.') messages.error(request, 'Выберите несколько дней недели.')
return redirect('school:summer-school') return redirect('school:school')
try: try:
weekdays = [int(weekday) for weekday in weekdays] weekdays = [int(weekday) for weekday in weekdays]
except ValueError: except ValueError:
messages.error(request, 'Ошибка выбора дней недели.') messages.error(request, 'Ошибка выбора дней недели.')
return redirect('school:summer-school') return redirect('school:school')
prev_school_payment = SchoolPayment.objects.filter( prev_school_payment = SchoolPayment.objects.filter(
user=request.user, user=request.user,
date_start__lte=date_start, date_start__lte=date_start,
@ -132,6 +133,7 @@ class SchoolBuyView(TemplateView):
add_days=True, add_days=True,
roistat_visit=roistat_visit, roistat_visit=roistat_visit,
) )
# Если произойдет ошибка и оплату бонусами повторят еще раз на те же дни, то вернет ошибку
if school_payment.amount <= 0: if school_payment.amount <= 0:
messages.error(request, 'Выбранные дни отсутствуют в оставшемся периоде подписки') messages.error(request, 'Выбранные дни отсутствуют в оставшемся периоде подписки')
return redirect(reverse_lazy('school:school')) return redirect(reverse_lazy('school:school'))
@ -143,6 +145,19 @@ class SchoolBuyView(TemplateView):
date_start=date_start, date_start=date_start,
date_end=Payment.add_months(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( product = Product(
f'school_{school_payment.id}', f'school_{school_payment.id}',
school_payment.amount, school_payment.amount,
@ -213,6 +228,7 @@ class PaymentwallCallbackView(View):
product_type_name, product_type_name,
) )
if product_type_name == 'course': if product_type_name == 'course':
properties = { properties = {
'payment_id': payment.id, '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 json import dumps
from django.utils.functional import cached_property
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
@ -14,7 +15,6 @@ from django.urls import reverse
from api.v1 import serializers from api.v1 import serializers
from apps.notification.utils import send_email from apps.notification.utils import send_email
from apps.user.tasks import user_to_mixpanel from apps.user.tasks import user_to_mixpanel
@ -100,7 +100,7 @@ class User(AbstractUser):
user_data = dumps(user_data, ensure_ascii=False) user_data = dumps(user_data, ensure_ascii=False)
return user_data return user_data
@property @cached_property
def balance(self): def balance(self):
income = self.balances.filter( income = self.balances.filter(
type=0, type=0,
@ -118,6 +118,10 @@ class User(AbstractUser):
return income_amount - income_commission - payout_amount 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) @receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs): 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) email = models.EmailField(_('email address'), unique=True)
categories = models.ManyToManyField(SubscriptionCategory) categories = models.ManyToManyField(SubscriptionCategory)
mailchimp_status = models.PositiveSmallIntegerField(choices=MAILCHIMP_STATUS_CHOICES, default=ERROR) 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 %} {% extends "templates/lilcity/index.html" %} {% load static %} {% block content %}
<div class="section section_gray section_menu"> {% include "../blocks/profile-menu.html" with active="notifications" %}
<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>
{% if not user.is_email_proved %} {% if not user.is_email_proved %}
<div class="section section_confirm"> <div class="section section_confirm">
<div class="section__center center center_xs"> <div class="section__center center center_xs">

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

@ -3,15 +3,7 @@
{% load settings %} {% load settings %}
{% load thumbnail %} {% load thumbnail %}
{% block content %} {% block content %}
<div class="section section_gray section_menu"> {% include "../blocks/profile-menu.html" with active="profile" %}
<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>
{% if not user.is_email_proved %} {% if not user.is_email_proved %}
<div class="section section_confirm"> <div class="section section_confirm">
<div class="section__center center center_xs"> <div class="section__center center center_xs">

@ -4,18 +4,16 @@ from uuid import uuid4
from os.path import splitext from os.path import splitext
from datetime import timedelta from datetime import timedelta
from paymentwall import Pingback from paymentwall import Pingback
import short_url
from django.conf import settings from django.conf import settings
from django.contrib.auth import login from django.contrib.auth import login
from django.core.exceptions import ValidationError from django.shortcuts import reverse, redirect
from django.shortcuts import render, reverse, redirect
from django.views import View from django.views import View
from django.views.generic import DetailView, UpdateView, TemplateView, FormView from django.views.generic import DetailView, UpdateView, TemplateView, FormView
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.hashers import check_password, make_password
from django.http import Http404
from django.db.models import F, Func, Sum, Min, Max from django.db.models import F, Func, Sum, Min, Max
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -147,7 +145,7 @@ class NotificationEditView(TemplateView):
request.user.email_subscription.categories.set( request.user.email_subscription.categories.set(
SubscriptionCategory.objects.filter(id__in=categories) 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): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
@ -184,7 +182,7 @@ class PaymentHistoryView(FormView):
return self.form_invalid(form) return self.form_invalid(form)
def get_success_url(self): 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 return success_url
@ -293,3 +291,17 @@ class AuthorRequestView(FormView):
'facebook': self.request.user.facebook, 'facebook': self.request.user.facebook,
} }
return super().get_context_data(**kwargs) 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.db.models import Func, F
from django.utils.timezone import now from django.utils.timezone import now
from paymentwall.pingback import Pingback 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.config.models import Config
from apps.content.models import Baner from apps.content.models import Baner
from apps.payment.models import SchoolPayment from apps.payment.models import SchoolPayment
User = get_user_model()
def config(request): def config(request):
return {"config": Config.load()} return {"config": Config.load()}
@ -39,3 +42,17 @@ def school_purchased(request):
'is_school_purchased': False, 'is_school_purchased': False,
'school_schedules_purchased': [], '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.config',
'project.context_processors.baner', 'project.context_processors.baner',
'project.context_processors.school_purchased', 'project.context_processors.school_purchased',
'project.context_processors.referrer',
'project.context_processors.settings',
'django.template.context_processors.debug', 'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',

@ -23,5 +23,10 @@
courses: "{% url 'courses' %}", courses: "{% url 'courses' %}",
userProfileEdit: "{% url 'user-edit-profile' %}", 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> </script>

@ -10,13 +10,13 @@
<div class="auth js-auth"> <div class="auth js-auth">
<div class="auth__login js-auth-login"> <div class="auth__login js-auth-login">
<div class="auth__nav"> <div class="auth__nav">
<a class="auth__type js-auth-type active" href="#">Войти</a> <a class="auth__type js-auth-type_login js-auth-type" href="#">Войти</a>
<a class="auth__type js-auth-type" href="#">РЕГИСТРАЦИЯ</a> <a class="auth__type js-auth-type_registration js-auth-type" href="#">РЕГИСТРАЦИЯ</a>
</div> </div>
<div class="auth__body"> <div class="auth__body">
<form id="learner-auth-form" method="post" action="{% url 'lilcity:login' %}"> <form id="learner-auth-form" method="post" action="{% url 'lilcity:login' %}">
{% csrf_token %} {% 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 class="auth__enter js-auth-enter">
<div id="learner-auth-field__username" class="auth__field field learner-auth-form__field"> <div id="learner-auth-field__username" class="auth__field field learner-auth-form__field">
<div class="field__label">ПОЧТА</div> <div class="field__label">ПОЧТА</div>
@ -63,7 +63,15 @@
</form> </form>
<form id="learner-registration-form" method="post" action="{% url 'lilcity:registration-learner' %}"> <form id="learner-registration-form" method="post" action="{% url 'lilcity:registration-learner' %}">
{% csrf_token %} {% 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 class="auth__fieldset">
<div id="learner-registration-field__first-name" class="auth__field field learner-registration-form__field"> <div id="learner-registration-field__first-name" class="auth__field field learner-registration-form__field">
<div class="field__label">ИМЯ</div> <div class="field__label">ИМЯ</div>

@ -70,10 +70,21 @@
<div class="order__label">ШКОЛА</div> <div class="order__label">ШКОЛА</div>
<div class="order__dates"></div> <div class="order__dates"></div>
<div class="order__days"></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>
<div class="order__foot"> <div class="order__foot">
<div class="order__subtitle">Итого, за месяц:</div> <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> </div>
</div> </div>

@ -1,4 +1,5 @@
{% load static %} {% load thumbnail %} {% load static %} {% load thumbnail %}
{% load rupluralize from plural %}
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<div class="header__login"> <div class="header__login">
<a class="header__ava ava" href="{% if request.user_agent.is_touch_capable %}#{% else %}{% url 'user-profile' %}{% endif %}"> <a class="header__ava ava" href="{% if request.user_agent.is_touch_capable %}#{% else %}{% url 'user-profile' %}{% endif %}">
@ -9,22 +10,26 @@
{% endthumbnail %} {% endthumbnail %}
</a> </a>
<div class="header__drop"> <div class="header__drop">
{# FIXME #}
{% if request.user.role >= request.user.AUTHOR_ROLE %} {% if request.user.role >= request.user.AUTHOR_ROLE %}
<a class="header__link header__link_border" href="{% url 'user-edit-payments' request.user.id %}"> <a class="header__link header__link_border" href="{% url 'user-edit-payments' %}">
{{ request.user.balance }} руб. {{ request.user.balance }} руб.
</a> </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 %} {% 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 %} {% endif %}
<a class="header__link" href="{% url 'user-profile' %}"> <a class="header__link" href="{% url 'user-profile' %}">
<div class="header__title">ПРОФИЛЬ</div> <div class="header__title">ПРОФИЛЬ</div>

@ -151,6 +151,7 @@
{% include "templates/blocks/popup_subscribe.html" %} {% include "templates/blocks/popup_subscribe.html" %}
</div> </div>
{% include 'templates/blocks/lil_store_js.html' %} {% include 'templates/blocks/lil_store_js.html' %}
<script type="text/javascript" src={% static "app.js" %}></script> <script type="text/javascript" src={% static "app.js" %}></script>
<script> <script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }}); 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" %}'); (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> </script>
{% block foot %}{% endblock foot %} {% block foot %}{% endblock foot %}
{% block body_js %}{% endblock body_js %}
</body> </body>
</html> </html>

@ -30,7 +30,7 @@ from apps.user.views import (
ProfileEditView, NotificationEditView, ProfileEditView, NotificationEditView,
PaymentHistoryView, resend_email_verify, PaymentHistoryView, resend_email_verify,
SubscribeView, ProfileView, SubscribeView, ProfileView,
) BonusHistoryView)
from apps.payment.views import ( from apps.payment.views import (
CourseBuySuccessView, CourseBuyView, CourseBuySuccessView, CourseBuyView,
PaymentwallCallbackView, SchoolBuySuccessView, PaymentwallCallbackView, SchoolBuySuccessView,
@ -71,8 +71,9 @@ urlpatterns = [
path('user/profile/edit', ProfileEditView.as_view(), name='user-edit-profile'), path('user/profile/edit', ProfileEditView.as_view(), name='user-edit-profile'),
path('user/<int:pk>/', UserView.as_view(), name='user'), path('user/<int:pk>/', UserView.as_view(), name='user'),
path('user/<str:slug>/', 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/notifications', NotificationEditView.as_view(), name='user-edit-notifications'),
path('user/<int:pk>/payments', PaymentHistoryView.as_view(), name='user-edit-payments'), 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('user/resend-email-verify', resend_email_verify, name='resend-email-verify'),
path('subscribe', SubscribeView.as_view(), name='subscribe'), path('subscribe', SubscribeView.as_view(), name='subscribe'),
path('subscribe/success', TemplateView.as_view(template_name='templates/lilcity/subscribe_success.html'), name='subscribe-success'), 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 from datetime import datetime, timedelta
import short_url
from django.db.models import Min, Func, F from django.db.models import Min, Func, F
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.views.generic import TemplateView from django.views.generic import TemplateView
@ -21,6 +22,8 @@ class IndexView(TemplateView):
template_name = 'templates/lilcity/main.html' template_name = 'templates/lilcity/main.html'
def get_context_data(self): def get_context_data(self):
referrer = self.request.GET.get('referrer')
is_referral_url = bool(referrer)
context = super().get_context_data() context = super().get_context_data()
now_time = now() now_time = now()
@ -64,7 +67,19 @@ class IndexView(TemplateView):
school_payment_exists = False school_payment_exists = False
school_schedules_purchased = [] 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({ context.update({
'referrer': referrer,
'is_referral_url': is_referral_url,
'online': online, 'online': online,
'online_coming_soon': online_coming_soon, 'online_coming_soon': online_coming_soon,
'school_schedule': school_schedule, 'school_schedule': school_schedule,

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

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

@ -1,13 +1,17 @@
import $ from 'jquery'; import $ from 'jquery';
import {showNotification} from "./notification"
import Inputmask from "inputmask"; import Inputmask from "inputmask";
import SmoothScroll from 'smooth-scroll/dist/js/smooth-scroll'; import SmoothScroll from 'smooth-scroll/dist/js/smooth-scroll';
import baguetteBox from 'baguettebox.js' import baguetteBox from 'baguettebox.js'
import createHistory from 'history/createBrowserHistory'
window.$ = window.jQuery = jQuery = $; window.$ = window.jQuery = jQuery = $;
window.Inputmask = Inputmask; window.Inputmask = Inputmask;
window.baguetteBox = baguetteBox; window.baguetteBox = baguetteBox;
$(document).ready(function () { $(document).ready(function () {
const $popupAuth = $('.js-popup-auth');
let authPopupActiveTab = 'login'
baguetteBox.run('.gallery'); baguetteBox.run('.gallery');
// Добавляем заголовок X-CSRFToken для всех AJAX запросов JQuery. // Добавляем заголовок X-CSRFToken для всех AJAX запросов JQuery.
$.ajaxSetup({ $.ajaxSetup({
@ -40,6 +44,23 @@ $(document).ready(function () {
$('.banner').slideUp(); $('.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() { function updateHeader() {

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

@ -41,4 +41,8 @@ export const main = () => {
}); });
changeSlug(); 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 padding: 20px 20px 0
&__total &__total
margin-left: auto margin-left: auto
position: relative
del del
color: #a0a0a0 color: #a0a0a0
& .loading-loader
display: none
margin-left: -40px
&--loading .loading-loader
display: block
.lock .lock
padding: 50px 60px 40px padding: 50px 60px 40px
@ -3301,31 +3307,41 @@ a.grey-link
&__cell &__cell
padding: 0 10px padding: 0 10px
font-size: 13px 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 color: $green
&__pending &_pending
color: $gray color: $gray
&__error &_error
color: $pink color: $pink
&:first-child .transactions_bonuses .transactions
+fb &__product
font-size: 12px flex: 0 0 60%;
text-transform: uppercase +m
letter-spacing: 2px flex: 1 0 40%;
flex: 0 0 60% &__amount
+m flex: 0 0 10%;
flex: 0 0 75% &__user
&:nth-child(2), flex: 0 0 20%;
&: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%
.empty .empty
max-width: 300px max-width: 300px

Loading…
Cancel
Save