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

remotes/origin/hotfix/LIL-691
gzbender 7 years ago
commit 9e055651e3
  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. 14
      apps/course/migrations/0045_merge_20180926_0200.py
  7. 4
      apps/course/templates/course/_items.html
  8. 4
      apps/course/templates/course/course.html
  9. 2
      apps/course/templates/course/course_only_lessons.html
  10. 2
      apps/course/templates/course/lesson.html
  11. 23
      apps/payment/migrations/0020_userbonus.py
  12. 27
      apps/payment/migrations/0021_auto_20180903_2257.py
  13. 19
      apps/payment/migrations/0022_auto_20180904_0106.py
  14. 19
      apps/payment/migrations/0023_payment_bonus.py
  15. 100
      apps/payment/models.py
  16. 22
      apps/payment/views.py
  17. 41
      apps/school/views.py
  18. 1
      apps/user/forms.py
  19. 31
      apps/user/migrations/0024_referral.py
  20. 18
      apps/user/migrations/0024_user_slug.py
  21. 14
      apps/user/migrations/0025_merge_20180927_2353.py
  22. 35
      apps/user/models.py
  23. 14
      apps/user/templates/blocks/profile-menu.html
  24. 71
      apps/user/templates/user/bonus-history.html
  25. 10
      apps/user/templates/user/notification-settings.html
  26. 36
      apps/user/templates/user/payment-history.html
  27. 46
      apps/user/templates/user/profile-settings.html
  28. 27
      apps/user/views.py
  29. 19
      project/context_processors.py
  30. 3
      project/settings.py
  31. 8
      project/templates/blocks/lil_store_js.html
  32. 16
      project/templates/blocks/popup_auth.html
  33. 13
      project/templates/blocks/popup_buy.html
  34. 2
      project/templates/blocks/teachers.html
  35. 36
      project/templates/blocks/user_menu.html
  36. 2
      project/templates/lilcity/index.html
  37. 8
      project/urls.py
  38. 15
      project/views.py
  39. 1
      requirements.txt
  40. 12
      web/src/js/app.js
  41. 4
      web/src/js/modules/auth.js
  42. 23
      web/src/js/modules/common.js
  43. 42
      web/src/js/modules/popup.js
  44. 58
      web/src/js/modules/profile.js
  45. 5
      web/src/js/utils.js
  46. 64
      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,14 @@
# Generated by Django 2.0.6 on 2018-09-26 02:00
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('course', '0044_course_age'),
('course', '0044_livelessoncomment'),
]
operations = [
]

@ -57,7 +57,7 @@
<div class="courses__content">{{ course.short_description | safe | linebreaks | truncatechars_html:300 }}
</div>
<div class="courses__user user">
<a href="{% if course.author %}{% url 'user' course.author.id %}{% endif %}">
<a href="{% if course.author %}{{ course.author.url }}{% endif %}">
{% if course.author.photo %}
<div class="user__ava ava">
<img class="ava__pic" src="{{ course.author.photo.url }}"/>
@ -69,7 +69,7 @@
{% endif %}
</a>
<div class="user__info">
<a href="{% if course.author %}{% url 'user' course.author.id %}{% endif %}" class="link--black">
<a href="{% if course.author %}{{ course.author.url }}{% endif %}" class="link--black">
<div class="user__name">{{ course.author.get_full_name }}</div>
</a>
<div class="user__meta">

@ -74,7 +74,7 @@
</div>
<div class="course__title title">{{ course.title }}</div>
<div class="course__content">{{ course.short_description | safe | linebreaks }}</div>
<a href="{% url 'user' course.author.id %}">
<a href="{{ course.author.url }}">
<div class="course__user user">
{% if course.author.photo %}
<div class="user__ava ava">
@ -274,7 +274,7 @@
</div>
<div class="course__title title">{{ course.title }}</div>
<div class="course__content">{{ course.short_description | safe | linebreaks }}</div>
<a href="{% url 'user' course.author.id %}">
<a href="{{ course.author.url }}">
<div class="course__user user user_white">
{% if course.author.photo %}
<div class="user__ava ava">

@ -72,7 +72,7 @@
</div>
<div class="course__title title">{{ course.title }}</div>
<div class="course__content">{{ course.short_description | safe | linebreaks }}</div>
<a href="{% url 'user' course.author.id %}">
<a href="{{ course.author.url }}">
<div class="course__user user">
{% if course.author.photo %}
<div class="user__ava ava">

@ -34,7 +34,7 @@
<div>
<div class="lesson__subtitle subtitle">{{ lesson.title }}</div>
<div class="lesson__content">{{ lesson.short_description | safe | linebreaks }}</div>
<a href="{% url 'user' lesson.author.id %}">
<a href="{{ lesson.author.url }}">
<div class="lesson__user user">
{% if lesson.author.photo %}
<div class="user__ava ava">

@ -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,

@ -63,30 +63,23 @@ class LiveLessonsDetailView(DetailView):
template_name = 'school/livelesson_detail.html'
def get(self, request, pk=None):
response = super().get(request, pk=pk)
# ??? где проверка?
#try:
# school_payment = SchoolPayment.objects.get(
# user=request.user,
# add_days=False,
# date_start__lte=now(),
# date_end__gte=now(),
# status__in=[
# Pingback.PINGBACK_TYPE_REGULAR,
# Pingback.PINGBACK_TYPE_GOODWILL,
# Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
# ],
# )
#except SchoolPayment.DoesNotExist:
# school_payment = None
#if request.user.role not in [User.ADMIN_ROLE, User.TEACHER_ROLE] and not (
# request.user.role == User.USER_ROLE and
# school_payment and
# school_payment.is_deliverable()
#):
# raise Http404
return response
self.object = self.get_object()
is_purchased = SchoolPayment.objects.filter(
user=request.user,
date_start__lte=now(),
date_end__gte=now(),
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
weekdays__contains=[self.object.date.weekday() + 1],
).exists()
if not is_purchased and request.user.role not in [User.ADMIN_ROLE, User.TEACHER_ROLE]:
raise Http404
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
@method_decorator([login_required, is_admin_or_teacher], name='dispatch')

@ -36,6 +36,7 @@ class UserEditForm(forms.ModelForm):
'first_name',
'last_name',
'email',
'slug',
'phone',
'city',
'country',

@ -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': 'Рефералы',
},
),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-09-26 13:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0023_user_trial_lesson'),
]
operations = [
migrations.AddField(
model_name='user',
name='slug',
field=models.SlugField(allow_unicode=True, blank=True, max_length=100, null=True, unique=True),
),
]

@ -0,0 +1,14 @@
# Generated by Django 2.0.6 on 2018-09-27 23:53
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('user', '0024_referral'),
('user', '0024_user_slug'),
]
operations = [
]

@ -1,4 +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
@ -9,10 +11,10 @@ from django.contrib.auth.models import AbstractUser, UserManager as BaseUserMana
from django.contrib.postgres import fields as pgfields
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
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
@ -76,6 +78,10 @@ class User(AbstractUser):
photo = models.ImageField('Фото', null=True, blank=True, upload_to='users')
show_in_mainpage = models.BooleanField('Показывать на главной странице', default=False)
trial_lesson = models.URLField(default='', null=True, blank=True)
slug = models.SlugField(
allow_unicode=True, null=True, blank=True,
max_length=100, unique=True, db_index=True,
)
objects = UserManager()
@ -85,12 +91,16 @@ class User(AbstractUser):
class Meta(AbstractUser.Meta):
ordering = ('-date_joined',)
@property
def url(self):
return reverse('user', args=[self.slug or self.id])
def serialized(self):
user_data = serializers.user.UserSerializer(instance=self).data
user_data = dumps(user_data, ensure_ascii=False)
return user_data
@property
@cached_property
def balance(self):
income = self.balances.filter(
type=0,
@ -108,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):
@ -261,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 %}

@ -1,13 +1,9 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% 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>
{% extends "templates/lilcity/index.html" %}
{% load static %}
{% load settings %}
{% load thumbnail %}
{% block content %}
{% 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">
@ -44,7 +40,7 @@
{% empty %}
<img id="photo" class="ava__pic" src="{% static 'img/user_default.jpg' %}" width="120px" height="120px" />
{% endthumbnail %}
<input name="photo" class="ava__input" type="file" accept='image/*' onchange='openFile(event)'>
<input id="user-photo-upload" name="photo" class="ava__input" type="file" accept='image/*'>
<div class="ava__icon">
<svg class="icon icon-photo">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-photo"></use>
@ -55,7 +51,7 @@
<div class="form__field field{% if form.first_name.errors %} error{% endif %}">
<div class="field__label">ИМЯ</div>
<div class="field__wrap">
<input name='first_name' class="field__input" type="text" placeholder="Имя" value="{{ user.first_name }}">
<input name='first_name' id="user-first-name" class="field__input" type="text" placeholder="Имя" value="{{ user.first_name }}">
</div>
{% for error in form.first_name.errors %}
<div class="field__error">{{ error }}</div>
@ -64,7 +60,7 @@
<div class="form__field field{% if form.last_name.errors %} error{% endif %}">
<div class="field__label">ФАМИЛИЯ</div>
<div class="field__wrap">
<input name='last_name' class="field__input" type="text" placeholder="Фамилия" value="{{ user.last_name }}">
<input name='last_name' id="user-last-name" class="field__input" type="text" placeholder="Фамилия" value="{{ user.last_name }}">
</div>
{% for error in form.last_name.errors %}
<div class="field__error">{{ error }}</div>
@ -89,6 +85,17 @@
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
<div class="form__field field{% if form.slug.errors %} error{% endif %}">
<div class="field__label">Ссылка</div>
<div class="field__wrap field__wrap__appended">
<div class="field__text">{% setting 'MAIN_HOST' %}/user/</div>
<input name='slug' id="user-slug" class="field__input field__input__appended" type="text"
value="{{ user.slug|default:'' }}" data-current-slug="{{ user.slug|default:'' }}">
</div>
{% for error in form.slug.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
<div class="form__fieldset">
<div class="form__field field{% if form.city.errors %} error{% endif %}">
<div class="field__label">ГОРОД</div>
@ -257,19 +264,6 @@
</div>
</div>
</div>
<script>
var openFile = function(file) {
var input = file.target;
var reader = new FileReader();
reader.onload = function(){
var dataURL = reader.result;
var output = document.getElementById('photo');
output.src = dataURL;
};
reader.readAsDataURL(input.files[0]);
};
</script>
{% endblock content %}

@ -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
@ -99,6 +97,7 @@ class ProfileView(TemplateView):
class UserView(DetailView):
model = User
template_name = 'user/author_profile.html'
query_pk_and_slug = True
def get_context_data(self, object):
context = super().get_context_data()
@ -146,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)
@ -183,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
@ -292,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}

@ -31,6 +31,7 @@ SECRET_KEY = os.getenv('SECRET_KEY', 'jelm*91lj(_-o20+6^a+bgv!4s6e_efry^#+f#=1ak
DEBUG = os.getenv('DEBUG', False)
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '*').split(',')
MAIN_HOST = os.getenv('MAIN_HOST', 'lil.school')
# Application definition
@ -95,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',

@ -20,7 +20,13 @@
},
components: {},
urls: {
courses: "{% url 'courses' %}"
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>

@ -18,7 +18,7 @@
<div class="teachers__wrap">
<div class="teachers__title">
<div class="teachers__title-name">
<a href="{% url 'user' teacher.id %}">{{ teacher.get_full_name }}</a>{% if teacher.instagram_hashtag %},
<a href="{{ teacher.url }}">{{ teacher.get_full_name }}</a>{% if teacher.instagram_hashtag %},
<a href='https://www.instagram.com/explore/tags/{{ teacher.instagram_hashtag }}/' target="_blank">
{{ teacher.instagram_hashtag }}
</a>

@ -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,29 @@
{% 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 %}
{% comment %}
FIXME
<!--<a class="header__link header__link_border" href="{% url 'user-bonuses' %}">-->
<!--{{ request.user.bonus|rupluralize:"бонус,бонуса,бонусов" }}-->
<!--</a>-->
{% endcomment %}
{% 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,
@ -70,8 +70,10 @@ urlpatterns = [
path('user/profile/', ProfileView.as_view(), name='user-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>/notifications', NotificationEditView.as_view(), name='user-edit-notifications'),
path('user/<int:pk>/payments', PaymentHistoryView.as_view(), name='user-edit-payments'),
path('user/<str:slug>/', UserView.as_view(), name='user'),
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

@ -17,7 +17,7 @@ import "./modules/courses";
import "./modules/comments";
import "./modules/comments";
import "./modules/password-show";
import "./modules/profile";
import {main as profileMain} from "./modules/profile";
import "./modules/notification";
import "./modules/mixpanel";
@ -55,5 +55,15 @@ const app = new Vue({
store: window.LIL_STORE,
}
},
mounted(){
if(this.urlIs('userProfileEdit')){
profileMain(this);
}
},
methods: {
urlIs(urlPatternName){
return window.location.pathname.search(this.store.urls[urlPatternName]) > -1;
},
},
components: components
});

@ -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(', ') : 'Ничего не выбрано');
}
});

@ -1,14 +1,48 @@
import $ from 'jquery';
import slugify from 'slugify';
$(document).ready(function () {
// Обработчик выбора пола
let genderInput = $('#gender')
$('div.js-select-option[data-gender-option]').on('click', function (e) {
e.preventDefault();
const currentGender = $(this).attr('data-gender');
$('[data-gender]').removeClass('active');
$(`[data-gender=${currentGender}]`).addClass('active');
genderInput.val(currentGender)
});
})
export const main = () => {
// Обработчик выбора пола
let genderInput = $('#gender')
$('div.js-select-option[data-gender-option]').on('click', function (e) {
e.preventDefault();
const currentGender = $(this).attr('data-gender');
$('[data-gender]').removeClass('active');
$(`[data-gender=${currentGender}]`).addClass('active');
genderInput.val(currentGender)
});
$('#user-photo-upload').change(file => {
const input = file.target;
const reader = new FileReader();
reader.onload = () => {
const dataURL = reader.result;
const output = document.getElementById('photo');
output.src = dataURL;
};
reader.readAsDataURL(input.files[0]);
});
const $slug = $('#user-slug');
const changeSlug = () => {
const fName = $('#user-first-name').val();
const lName = $('#user-last-name').val();
$slug.val(slugify(`${fName} ${lName}`));
}
if(! $slug.data('current-slug')){
$('#user-first-name').change(changeSlug);
$('#user-last-name').change(changeSlug);
$slug.change(() => {
$('#user-first-name').unbind('change', changeSlug);
$('#user-last-name').unbind('change', 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)];
}

@ -2167,6 +2167,10 @@ a.grey-link
width: 100px
&__append
text-transform: uppercase
&__text
height: 36px
font-size: 18px
padding-top: 6px
&__input,
&__textarea
width: 100%
@ -3091,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
@ -3297,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