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

remotes/origin/hotfix/LIL-691
gzbender 7 years ago
commit ba3f95362e
  1. 15
      api/v1/serializers/contest.py
  2. 21
      api/v1/serializers/course.py
  3. 5
      api/v1/serializers/school.py
  4. 1
      api/v1/serializers/user.py
  5. 24
      api/v1/views.py
  6. 8
      apps/content/models.py
  7. 86
      apps/content/templates/content/blocks/video.html
  8. 4
      apps/content/templates/content/contest.html
  9. 2
      apps/content/templates/content/contest_work.html
  10. 8
      apps/content/views.py
  11. 29
      apps/course/migrations/0043_auto_20180824_2132.py
  12. 6
      apps/course/models.py
  13. 20
      apps/school/migrations/0019_schoolschedule_cover.py
  14. 29
      apps/school/migrations/0020_auto_20180824_2132.py
  15. 18
      apps/school/migrations/0021_schoolschedule_trial_lesson.py
  16. 13
      apps/school/models.py
  17. 4
      apps/school/templates/blocks/day_pay_btn.html
  18. 21
      apps/school/templates/blocks/schedule_item.html
  19. 8
      apps/school/templates/summer/promo.html
  20. 2
      apps/user/forms.py
  21. 18
      apps/user/migrations/0023_user_trial_lesson.py
  22. 1
      apps/user/models.py
  23. 13
      apps/user/templates/user/profile-settings.html
  24. 5
      apps/user/views.py
  25. 18
      project/templates/blocks/about.html
  26. 2
      project/templates/blocks/comment.html
  27. 2
      project/templates/blocks/lil_store_js.html
  28. 20
      project/templates/blocks/partners.html
  29. 12
      project/templates/blocks/popup_auth.html
  30. 12
      project/templates/blocks/popup_buy.html
  31. 10
      project/templates/blocks/popup_subscribe.html
  32. 15
      project/templates/blocks/promo.html
  33. 17
      project/templates/blocks/teachers.html
  34. 15
      project/templates/lilcity/index.html
  35. 2
      project/urls.py
  36. 1
      web/package.json
  37. 8
      web/src/components/ContestWorks.vue
  38. 9
      web/src/components/blocks/Likes.vue
  39. 2
      web/src/js/app.js
  40. 28
      web/src/js/modules/auth.js
  41. 67
      web/src/js/modules/popup.js
  42. 42
      web/src/sass/_common.sass

@ -1,3 +1,4 @@
from django.contrib.auth import get_user_model
from rest_framework import serializers
from api.v1.serializers.content import ContentSerializer, ContentCreateSerializer, ImageObjectSerializer
@ -5,6 +6,8 @@ from api.v1.serializers.mixins import DispatchContentMixin
from apps.content.models import (Contest, ContestWork)
User = get_user_model()
class ContestSerializer(serializers.ModelSerializer):
cover = ImageObjectSerializer()
@ -12,7 +15,8 @@ class ContestSerializer(serializers.ModelSerializer):
class Meta:
model = Contest
fields = '__all__'
fields = ['title', 'description', 'slug', 'cover',
'date_start', 'date_end', 'active', 'content', 'finished']
class ContestCreateSerializer(DispatchContentMixin, serializers.ModelSerializer):
@ -52,11 +56,14 @@ class ContestWorkSerializer(serializers.ModelSerializer):
'created_at', 'likes', 'user_liked', 'img_width', 'img_height']
def get_likes(self, instance):
return instance.likes.count()
return instance.likes.filter(user__is_active=True).count()
def get_user_liked(self, instance):
user = self.context['request'].user
return instance.likes.filter(user=user).exists() if user.is_authenticated else False
# FIXME
user = self.context['request'].query_params.get('current_user')
if user:
user = User.objects.get(pk=user)
return instance.likes.filter(user=user).exists() if user else False
class ContestWorkCreateSerializer(serializers.ModelSerializer):

@ -71,14 +71,10 @@ class LikeSerializer(serializers.ModelSerializer):
class LikeCreateSerializer(serializers.ModelSerializer):
OBJ_TYPE_CONTEST_WORK = 'contest_work'
obj_type = serializers.CharField(required=True)
obj_id = serializers.IntegerField(required=True)
class Meta:
model = Like
fields = ['user', 'obj_type', 'obj_id']
fields = ['user']
def create(self, validated_data):
# FIXME
@ -86,20 +82,9 @@ class LikeCreateSerializer(serializers.ModelSerializer):
user = validated_data.get('user')
else:
user = self.context['request'].user
if not user: # FIXME and user.is_authenticated):
return Like()
obj_type = validated_data.pop('obj_type')
obj_id = validated_data.pop('obj_id')
client_ip, is_routable = get_client_ip(self.context['request'])
if obj_type == self.OBJ_TYPE_CONTEST_WORK:
contest_work = ContestWork.objects.get(pk=obj_id)
# FIXME in prod: fixed
if contest_work.user == user or contest_work.likes.filter(user=user).exists():
# if contest_work.likes.filter(user=user).exists():
return Like()
like = Like.objects.create(user=user, ip=client_ip)
contest_work.likes.add(like)
return like
like = Like.objects.create(user=user, ip=client_ip)
return like
def to_representation(self, instance):
return LikeSerializer(instance, context=self.context).to_representation(instance)

@ -54,6 +54,8 @@ class SchoolScheduleSerializer(serializers.ModelSerializer):
'day_discount',
'start_at',
'schoolschedule_images',
'cover',
'trial_lesson',
)
read_only_fields = (
@ -96,6 +98,7 @@ class SchoolScheduleSerializerImg(serializers.ModelSerializer):
child=GalleryImageSerializer(),
required=False,
)
cover = ImageObjectSerializer()
class Meta:
model = SchoolSchedule
@ -111,6 +114,8 @@ class SchoolScheduleSerializerImg(serializers.ModelSerializer):
'day_discount',
'start_at',
'schoolschedule_images',
'cover',
'trial_lesson',
)
read_only_fields = (

@ -42,6 +42,7 @@ class UserSerializer(serializers.ModelSerializer):
'photo',
'balance',
'show_in_mainpage',
'trial_lesson',
)
read_only_fields = (

@ -131,6 +131,8 @@ class MaterialViewSet(ExtendedModelViewSet):
class LikeViewSet(ExtendedModelViewSet):
OBJ_TYPE_CONTEST_WORK = 'contest_work'
queryset = Like.objects.select_related('user').all()
serializer_class = LikeCreateSerializer
serializer_class_map = {
@ -143,6 +145,28 @@ class LikeViewSet(ExtendedModelViewSet):
# FIXME
authentication_classes = []
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
obj_type = request.data.get('obj_type')
obj_id = request.data.get('obj_id')
user = serializer.validated_data.get('user')
if not user.is_active: # FIXME and user.is_authenticated):
return Response(status=status.HTTP_403_FORBIDDEN)
if obj_type == self.OBJ_TYPE_CONTEST_WORK:
contest_work = ContestWork.objects.get(pk=obj_id)
if contest_work.user == user:
return Response({'error': u'Нельзя голосовать за свою работу'}, status=status.HTTP_400_BAD_REQUEST)
if contest_work.likes.filter(user=user).exists():
return Response({'error': u'Вы уже голосовали за эту работу'}, status=status.HTTP_400_BAD_REQUEST)
if contest_work.contest.finished:
return Response({'error': u'Голосование закончено'}, status=status.HTTP_400_BAD_REQUEST)
instance = serializer.save()
if obj_type == self.OBJ_TYPE_CONTEST_WORK:
contest_work.likes.add(instance)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
class CategoryViewSet(ExtendedModelViewSet):
queryset = Category.objects.order_by('-id')

@ -1,7 +1,10 @@
from datetime import datetime, time
from urllib.parse import urlparse
from django.conf import settings
from django.db import models
from django.contrib.auth import get_user_model
from django.urls import reverse_lazy
from django.utils import timezone
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToCover
@ -167,6 +170,11 @@ class Contest(models.Model):
active = models.BooleanField(default=True)
# TODO? baner
@property
def finished(self):
# FIXME
return datetime(2018, 8, 29, 21) < timezone.now()
def save(self, *args, **kwargs):
if self.active:
Contest.objects.filter(active=True).update(active=False)

@ -1,43 +1,43 @@
<div class="section section_border">
<div class="section__center center center_sm">
<div class="content-block title">
{{ content.title }}
</div>
<div class="iframe__container">
{% if 'youtube.com' in content.url or 'youtu.be' in content.url %}
<iframe src="https://www.youtube.com/embed/{{ content.video_index }}" frameborder="0" allow="autoplay; encrypted-media"
allowfullscreen></iframe>
{% elif 'vimeo.com' in content.url %}
<iframe src="https://player.vimeo.com/video/{{ content.video_index }}" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen>
</iframe>
{% endif %}
</div>
</div>
</div>
<style>
@media only screen and (max-width: 639px) {
iframe {
width: 100%;
height: 240px;
}
.iframe__container {
text-align: center;
width: 100%;
padding-bottom: 56.25%;
margin-bottom: -56.25%;
}
}
@media only screen and (min-width: 640px) {
.iframe__container {
text-align: center;
}
iframe {
width: 640px;
height: 360px;
}
}
</style>
<div class="section section_border">
<div class="section__center center center_sm">
<div class="content-block title">
{{ content.title }}
</div>
<div class="content-block__video iframe__container">
{% if 'youtube.com' in content.url or 'youtu.be' in content.url %}
<iframe src="https://www.youtube.com/embed/{{ content.video_index }}" frameborder="0" allow="autoplay; encrypted-media"
allowfullscreen></iframe>
{% elif 'vimeo.com' in content.url %}
<iframe src="https://player.vimeo.com/video/{{ content.video_index }}" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen>
</iframe>
{% endif %}
</div>
</div>
</div>
<style>
@media only screen and (max-width: 639px) {
iframe {
width: 100%;
height: 240px;
}
.iframe__container {
text-align: center;
width: 100%;
padding-bottom: 56.25%;
margin-bottom: -56.25%;
}
}
@media only screen and (min-width: 640px) {
.iframe__container {
text-align: center;
}
iframe {
width: 640px;
height: 360px;
}
}
</style>

@ -12,7 +12,7 @@
{{ contest.description }}
</div>
<div class="main__actions">
{% if not contest_work_uploaded %}
{% if not contest_work_uploaded and not contest.finished %}
<a class="main__btn btn" href=""
{% if request.user.is_authenticated %}data-show-upload-contest-work
{% else %}data-popup=".js-popup-auth"{% endif %}>Загрузить свою работу</a>
@ -34,7 +34,7 @@
<div class="title title_center">Галерея</div>
</a>
<div class="text">
{% if not contest_work_uploaded %}
{% if not contest_work_uploaded and not contest.finished %}
<a class="btn" href=""
{% if request.user.is_authenticated %}data-show-upload-contest-work
{% else %}data-popup=".js-popup-auth"{% endif %}>Загрузить свою работу</a>

@ -35,7 +35,7 @@
</div>
<div class="contest-work__likes">
<likes obj-type="contest_work" obj-id="{{ contest_work.id }}"
{% if user_liked %}:user-liked="true"{% endif %} likes="{{ contest_work.likes.count }}"></likes>
{% if user_liked %}:user-liked="true"{% endif %} likes="{{ likes_count }}"></likes>
</div>
</div>
</div>

@ -14,10 +14,11 @@ from apps.course.models import ContestWorkComment
@method_decorator(login_required, name='dispatch')
class ContestEditView(TemplateView):
template_name = 'content/contest_edit.html'
query_pk_and_slug = True
def get(self, request, pk=None, lesson=None):
if pk:
self.object = get_object_or_404(Contest, pk=pk)
def get(self, request, slug=None, lesson=None):
if slug:
self.object = get_object_or_404(Contest, slug=slug)
else:
self.object = Contest()
@ -58,6 +59,7 @@ class ContestWorkView(DetailView):
context['user_liked'] = self.object.likes.filter(user=self.request.user).exists() \
if self.request.user.is_authenticated else False
context['likes_count'] = self.object.likes.filter(user__is_active=True).count()
return context

@ -0,0 +1,29 @@
# Generated by Django 2.0.6 on 2018-08-24 21:32
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('course', '0042_like_ip'),
]
operations = [
migrations.AlterField(
model_name='course',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='course_covers', to='content.ImageObject', verbose_name='Обложка курса'),
),
migrations.AlterField(
model_name='lesson',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lesson_covers', to='content.ImageObject', verbose_name='Обложка урока'),
),
migrations.AlterField(
model_name='material',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='material_covers', to='content.ImageObject', verbose_name='Обложка материала'),
),
]

@ -65,7 +65,7 @@ class Course(BaseModel, DeactivatedMixin):
)
cover = models.ForeignKey(
ImageObject, related_name='course_covers',
verbose_name='Обложка курса', on_delete=models.CASCADE,
verbose_name='Обложка курса', on_delete=models.SET_NULL,
null=True, blank=True,
)
price = models.DecimalField(
@ -190,7 +190,7 @@ class Lesson(BaseModel, DeactivatedMixin):
)
cover = models.ForeignKey(
ImageObject, related_name='lesson_covers',
verbose_name='Обложка урока', on_delete=models.CASCADE,
verbose_name='Обложка урока', on_delete=models.SET_NULL,
null=True, blank=True,
)
created_at = models.DateTimeField(auto_now_add=True)
@ -222,7 +222,7 @@ class Material(models.Model):
title = models.CharField('Название материала', max_length=100)
cover = models.ForeignKey(
ImageObject, related_name='material_covers',
verbose_name='Обложка материала', on_delete=models.CASCADE,
verbose_name='Обложка материала', on_delete=models.SET_NULL,
null=True, blank=True,
)
short_description = models.TextField('Краткое описание материала')

@ -0,0 +1,20 @@
# Generated by Django 2.0.6 on 2018-08-24 19:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0022_auto_20180815_2129'),
('school', '0018_auto_20180629_1501'),
]
operations = [
migrations.AddField(
model_name='schoolschedule',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schoolschedule_cover', to='content.ImageObject', verbose_name='Обложка дня'),
),
]

@ -0,0 +1,29 @@
# Generated by Django 2.0.6 on 2018-08-24 21:32
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('school', '0019_schoolschedule_cover'),
]
operations = [
migrations.AlterField(
model_name='livelesson',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='livelesson_covers', to='content.ImageObject', verbose_name='Обложка урока школы'),
),
migrations.AlterField(
model_name='schoolschedule',
name='cover',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='schoolschedule_cover', to='content.ImageObject', verbose_name='Обложка дня'),
),
migrations.AlterField(
model_name='schoolscheduleimage',
name='img',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='schoolschedule_images', to='content.ImageObject', verbose_name='Объект изображения'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-08-27 21:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('school', '0020_auto_20180824_2132'),
]
operations = [
migrations.AddField(
model_name='schoolschedule',
name='trial_lesson',
field=models.URLField(blank=True, default=''),
),
]

@ -38,6 +38,15 @@ class SchoolSchedule(models.Model):
'Скидка, в валюте', max_digits=8, decimal_places=2, default=0
)
start_at = models.TimeField('Начало урока', null=True)
cover = models.ForeignKey(
ImageObject,
related_name='schoolschedule_cover',
verbose_name='Обложка дня',
on_delete=models.SET_NULL,
null=True,
blank=True,
)
trial_lesson = models.URLField(default='', blank=True)
class Meta:
ordering = ('weekday',)
@ -88,7 +97,7 @@ class SchoolScheduleImage(models.Model):
ImageObject,
related_name='schoolschedule_images',
verbose_name='Объект изображения',
on_delete=models.CASCADE,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
@ -111,7 +120,7 @@ class LiveLesson(BaseModel, DeactivatedMixin):
ImageObject,
related_name='livelesson_covers',
verbose_name='Обложка урока школы',
on_delete=models.CASCADE,
on_delete=models.SET_NULL,
null=True,
blank=True,
)

@ -1,7 +1,5 @@
<a
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% endif %}
data-popup=".js-popup-buy"
class="timing__btn btn"
href="{% url 'school-checkout' %}?weekdays={{ school_schedule.weekday }}&add_days=true"
>купить</a>

@ -1,21 +1,24 @@
{% load static %} {% load thumbnail %}
<div class="timing__item{% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item js-timing-toggle {% if print %}open{% endif %} {% if purchased and not school_schedule.weekday in school_schedules_purchased %} disable{% endif %}">
<div class="timing__item{% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item js-timing-toggle {% if print %}open{% endif %} {% if is_purchased and not school_schedule.weekday in school_schedules_purchased %} disable{% endif %}">
<div class="timing__cell timing__cell--info">
<div class="timing__info">
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}">
{{ school_schedule }}
</div>
{% if purchased and live_lesson %}
{% if is_purchased and live_lesson %}
<div class="timing__date">{{ live_lesson.date }}</div>
{% endif %}
<div class="timing__time">{{ school_schedule.start_at }} (МСК)</div>
<div class="timing__buy">
{% if purchased %}
{% if school_schedule.weekday in school_schedules_purchased %}
{% if live_lesson and live_lesson.title %}
{% include './open_lesson.html' %}
{% endif %}
{% if is_purchased %}
{% if school_schedule.weekday in school_schedules_purchased and live_lesson and live_lesson.title %}
{% include './open_lesson.html' %}
{% endif %}
{% else %}
{% include './day_pay_btn.html' %}
{% endif %}
{% if school_schedule.trial_lesson %}
<a class="timing__trial-lesson js-video-modal" href="#" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a>
{% endif %}
</div>
</div>
@ -32,7 +35,9 @@
{% thumbnail live_lesson.cover.image "70x70" crop="center" as im %}
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
{% empty %}
<img class="timing__pic" src="{% static 'img/no_cover.png' %}" style="width: 70px; height: 70px;" />
<img class="timing__pic"
src="{% if school_schedule.cover %}{{ school_schedule.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}"
style="width: 70px; height: 70px;" />
{% endthumbnail %}
</div>
</div>

@ -5,16 +5,12 @@
<span class="main__bold">Lil School</span> — первая образовательная онлайн-платформа креативного мышления для детей
</div>
<div class="main__subtitle">
Присоединяйтесь к школе с 1 сентября
Старт школы - 1 сентября
</div>
<div class="main__actions">
<a
{% if not is_purchased_future %}
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% else %}
data-popup=".js-popup-buy"
{% endif %}
{% endif %}
class="main__btn btn"
href="#"
@ -50,7 +46,7 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-subjects"></use>
</svg>
</div>
<div class="school__title">12 уроков</div>
<div class="school__title">7 дисциплин</div>
<div class="school__text">В разных техниках</div>
</div>
<div class="school__col">

@ -18,6 +18,7 @@ class UserEditForm(forms.ModelForm):
# gender = forms.ChoiceField(choices=User.GENDER_CHOICES, required=False)
gender = forms.CharField(required=False)
# about = forms.CharField()
trial_lesson = forms.URLField(required=False)
old_password = forms.CharField(required=False)
new_password1 = forms.CharField(required=False)
new_password2 = forms.CharField(required=False)
@ -41,6 +42,7 @@ class UserEditForm(forms.ModelForm):
'birthday',
'gender',
'about',
'trial_lesson',
'old_password',
'new_password1',
'new_password2',

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-08-22 12:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0022_user_instagram_hashtag'),
]
operations = [
migrations.AddField(
model_name='user',
name='trial_lesson',
field=models.URLField(blank=True, default='', null=True),
),
]

@ -74,6 +74,7 @@ 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)
objects = UserManager()

@ -150,7 +150,18 @@
{% for error in form.about.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
</div>
{% if is_teacher %}
<div class="form__field field{% if form.trial_lesson.errors %} error{% endif %}">
<div class="field__label">Пробный урок</div>
<div class="field__wrap">
<input name='trial_lesson' class="field__input" type="url" value="{{ user.trial_lesson }}">
</div>
{% for error in form.trial_lesson.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
{% endif %}
</div>
<div class="form__group">
<div class="form__title">Пароль</div>

@ -211,6 +211,11 @@ class ProfileEditView(UpdateView):
self.object = self.get_object()
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['is_teacher'] = self.object.role == User.TEACHER_ROLE
return context
def post(self, request, *args, **kwargs):
# it's magic *-*-*-*-*
if 'photo' in request.FILES:

@ -30,7 +30,7 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-subjects"></use>
</svg>
</div>
<div class="school__title">12 уроков</div>
<div class="school__title">7 дисциплин</div>
<div class="school__text">В разных техниках</div>
</div>
<div class="school__col">
@ -43,20 +43,6 @@
<div class="school__text">Хранится 7 дней</div>
</div>
</div>
<div class="letsgo">
{% if not is_purchased and not is_purchased_future %}
<a
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% else %}
data-popup=".js-popup-buy"
{% endif %}
class="main__btn btn"
href="#"
>
купить доступ от {{ min_school_price }} руб./месяц
</a>
{% endif %}
</div>
<div class="letsgo"></div>
</div>
</div>

@ -1,5 +1,5 @@
{% load static %}
{% if not node.deactivated_at %}
{% if not node.deactivated_at and node.author.is_active %}
<a class="questions__anchor" id="question__{{ node.id }}"></a>
<div id="question__replyto__{{ node.id }}" class="questions__item {% if node.is_child_node %}questions__item_reply{% endif %}">
{% if node.author.photo %}

@ -4,7 +4,7 @@
staticUrl: '{% static "" %}',
accessToken: '{{ request.user.auth_token }}',
user: {
id: {{ request.user.id|default:'null' }},
id: '{{ request.user.id|default:'' }}',
}
};
</script>

@ -4,16 +4,30 @@
<a name="partners">
<div class="title title_center">Наши партнеры</div>
</a>
<div class="text">
Скидка 15% по промокоду у наших партнеров
</div>
<div class="partners">
<a target="_blank" class="partners__item" href="https://artkvartal.ru">
<img class="partners__pic" src="{% static 'img/artkvartal.png' %}">
</a>
<a target="_blank" class="partners__item" href="https://www.mann-ivanov-ferber.ru/tag/sasha-kru/">
<img class="partners__pic" src="{% static 'img/mif.jpg' %}">
</a>
<a target="_blank" class="partners__item" href="https://artkvartal.ru">
<img class="partners__pic" src="{% static 'img/artkvartal.png' %}">
</a>
<a target="_blank" class="partners__item" href="http://www.pinkbus.ru/">
<img class="partners__pic" src="{% static 'img/pinkbus.jpg' %}">
</a>
</div>
<div class="partners partners_promocodes">
<div class="partners__item">
<div class="partners__item-promocode">LIL</div>
</div>
<div class="partners__item">
<div class="partners__item-promocode">LILCITY</div>
</div>
<div class="partners__item">
<div class="partners__item-promocode">LILCITY</div>
</div>
</div>
</div>
</div>

@ -37,10 +37,10 @@
<div class="field__password-show">
<button class="password-toggle" type="button">
<svg class="icon icon-password-eye">
<use xlink:href={% static "img/sprite.svg" %}#icon-password-eye></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-password-eye"></use>
</svg>
<svg class="icon icon-password-hidden-eye">
<use xlink:href={% static "img/sprite.svg" %}#icon-password-hidden-eye></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-password-hidden-eye"></use>
</svg>
</button>
</div>
@ -53,7 +53,7 @@
<div class="auth__or">или</div>
<button type="button" class="auth__btn btn btn_fb">
<svg class="icon icon-facebook">
<use xlink:href={% static "img/sprite.svg" %}#icon-facebook></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-facebook"></use>
</svg>
<span class="btn__title">ЧЕРЕЗ FACEBOOK</span>
</button>
@ -107,10 +107,10 @@
<div class="field__password-show">
<button class="password-toggle" type="button">
<svg class="icon icon-password-eye">
<use xlink:href={% static "img/sprite.svg" %}#icon-password-eye></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-password-eye"></use>
</svg>
<svg class="icon icon-password-hidden-eye">
<use xlink:href={% static "img/sprite.svg" %}#icon-password-hidden-eye></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-password-hidden-eye"></use>
</svg>
</button>
</div>
@ -133,7 +133,7 @@
<div class="auth__or">или</div>
<button type="button" class="auth__btn btn btn_fb">
<svg class="icon icon-facebook">
<use xlink:href={% static "img/sprite.svg" %}#icon-facebook></use>
<use xlink:href="{% static 'img/sprite.svg' %}#icon-facebook"></use>
</svg>
<span class="btn__title">ЧЕРЕЗ FACEBOOK</span>
</button>

@ -37,6 +37,11 @@
{% comment %} dont delete {% endcomment %}
<span class="switch__cell"></span>
<span class="switch__cell">{{ school_schedule.title }}</span>
<span class="buy__trial-lesson switch__cell">
{% if school_schedule.trial_lesson %}
<a class="js-video-modal" data-video-url="{{ school_schedule.trial_lesson }}" href="#">Пробный урок</a>
{% endif %}
</span>
<span class="switch__cell">{{school_schedule.month_price}}р в мес.</span>
</span>
</label>
@ -58,6 +63,11 @@
{% comment %} dont delete {% endcomment %}
<span class="switch__cell"></span>
<span class="switch__cell">{{ school_schedule.title }}</span>
<span class="buy__trial-lesson switch__cell">
{% if school_schedule.trial_lesson %}
<a class="js-video-modal" data-video-url="{{ school_schedule.trial_lesson }}" href="">Пробный урок</a>
{% endif %}
</span>
<span class="switch__cell">{{school_schedule.month_price}}р в мес.</span>
</span>
</label>
@ -89,7 +99,7 @@
</div>
</div>
<div class="buy__foot">
<a class="buy__btn btn btn_md but_btn_popup" data-link="{% url 'school-checkout' %}" href="{% url 'school-checkout' %}?weekdays=1&weekdays=2">ПЕРЕЙТИ К ОПЛАТЕ</a>
<a class="buy__btn btn btn_md but_btn_popup" data-link="{% url 'school-checkout' %}">ПЕРЕЙТИ К ОПЛАТЕ</a>
</div>
</div>
</div>

@ -10,20 +10,20 @@
<div class="auth js-auth" style="padding:20px 20px 25px;">
<div class="auth__login js-auth-login">
<div class="auth__body">
<form id="learner-auth-form" method="post" action="{% url 'subscribe' %}">
<form {# COMMENTED id="learner-auth-form" #} method="post" action="{% url 'subscribe' %}">
{% csrf_token %}
<div class="auth__tab js-auth-tab" style="display: block;">
<div class="auth__enter js-auth-enter">
<div id="learner-auth-field__username" class="auth__field field learner-auth-form__field">
<div {# COMMENTED id="learner-auth-field__username" #} class="auth__field field learner-auth-form__field">
<div class="field__label">ПОЧТА</div>
<div class="field__wrap"><input id="learner-auth-form__email" class="field__input" type="email"
<div class="field__wrap"><input {# COMMENTED id="learner-auth-form__email" #} class="field__input" type="email"
name="email"
placeholder="name@website.com" tabindex="1"></div>
<div id="learner-auth-field-error__username"
class="field__error learner-auth-form__field-error"></div>
</div>
<div class="auth__foot">
<button id="learner-auth__button" class="auth__btn btn btn_light" tabindex="3">Подписаться</button>
<button {# COMMENTED id="learner-auth__button" #} class="auth__btn btn btn_light" tabindex="3">Подписаться</button>
</div>
</div>
</div>
@ -33,4 +33,4 @@
</div>
</div>
</div>
</div>
</div>

@ -46,27 +46,20 @@
</div>
{% else %}
<div class="main__subtitle">
Приглашаем вас на месяц открытых дверей в Lil School
Приглашаем вас присоединиться к онлайн-школе с 1 сентября!
</div>
<div class="main__actions">
{% comment %}
{% if not is_purchased and not is_purchased_future %}
<a
{% if not is_purchased_future %}
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% else %}
data-popup=".js-popup-buy"
{% endif %}
{% endif %}
class="main__btn btn"
href="#"
>
Получить доступ
купить доступ от {{ min_school_price }} руб./месяц
</a>
{% else %}
<a class="main__btn btn btn_white" href="{% url 'school:school' %}">Подробнее</a>
{% endif %}
{% endcomment %}
<a class="main__btn btn btn_white" href="{% url 'course' pk=50 %}">Подробнее</a>
</div>
{% endif %}
</div>

@ -17,10 +17,15 @@
</div>
<div class="teachers__wrap">
<div class="teachers__title">
<a href="{% url 'user' teacher.id %}">{{ 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>
<div class="teachers__title-name">
<a href="{% url 'user' teacher.id %}">{{ 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>
{% endif %}
</div>
{% if teacher.trial_lesson %}
<a data-video-url="{{ teacher.trial_lesson }}" href="#" class="btn btn_light js-video-modal">ПРОБНЫЙ УРОК</a>
{% endif %}
</div>
<div class="teachers__social">
@ -30,13 +35,13 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-facebook"></use>
</svg>
</a>
{% endif %} {% if teacher.instagram %}
{% endif %} {# if teacher.instagram #}
<a class="social__item" href="{{ teacher.instagram }}" target="_blank">
<svg class="icon icon-share-instagram">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-instagram"></use>
</svg>
</a>
{% endif %} {% if teacher.twitter %}
{# endif #} {% if teacher.twitter %}
<a class="social__item" href="{{ teacher.twitter }}" target="_blank">
<svg class="icon icon-share-twitter">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-twitter"></use>

@ -38,6 +38,21 @@
{% endcompress %}
<link rel="shortcut icon" type="image/png" href="{% static 'img/favicon.png' %}"/>
<!-- Start of LiveChat (www.livechatinc.com) code -->
<script type="text/javascript">
window.__lc = window.__lc || {};
window.__lc.license = 9917240;
(function() {
var lc = document.createElement('script'); lc.type = 'text/javascript'; lc.async = true;
lc.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'cdn.livechatinc.com/tracking.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(lc, s);
})();
</script>
<noscript>
<a href="https://www.livechatinc.com/chat-with/9917240/">Напишите нам</a>,
powered by <a href="https://www.livechatinc.com/?welcome" rel="noopener" target="_blank">LiveChat</a>
</noscript>
<!-- End of LiveChat code -->
<script>
var viewportmeta = document.querySelector('meta[name="viewport"]');
if (viewportmeta) {

@ -85,7 +85,7 @@ urlpatterns = [
path('school/', include(('apps.school.urls', 'school'))),
path('test', TemplateView.as_view(template_name='templates/lilcity/test.html'), name='test'),
path('contest/create', ContestEditView.as_view(), name='contest_create'),
path('contest/<int:pk>/edit', ContestEditView.as_view(), name='contest_edit'),
path('contest/<str:slug>/edit', ContestEditView.as_view(), name='contest_edit'),
path('contest/<str:slug>/', ContestView.as_view(), name='contest'),
path('contest-work/<int:pk>/', ContestWorkView.as_view(), name='contest_work'),
path('contest-work/<int:contest_work_id>/comment', contest_work_comment, name='contest_work_comment'),

@ -46,6 +46,7 @@
"inputmask": "^3.3.11",
"jquery": "^3.3.1",
"lodash.debounce": "^4.0.8",
"modal-video": "^2.4.2",
"moment": "^2.20.1",
"owl.carousel": "^2.2.0",
"slugify": "^1.2.9",

@ -61,8 +61,12 @@
methods: {
load() {
this.loading = true;
api.get(`/api/v1/contest-works/?contest=${this.contestId}&page=${this.page}`)
.then((response) => {
api.get(`/api/v1/contest-works/?contest=${this.contestId}&page=${this.page}&current_user=${this.$root.store.user.id}`, {
headers: {
'Authorization': `Token ${this.$root.store.accessToken}`,
}
})
.then(response => {
this.loading = false;
this.loaded = true;
if(this.page > 1){

@ -10,6 +10,7 @@
<script>
import {api} from "../../js/modules/api";
import {showNotification} from "../../js/modules/notification";
export default {
name: 'likes',
@ -22,6 +23,7 @@
},
methods: {
addLike(event) {
this.$emit('like');
if(this.userLikedProp){
return;
}
@ -36,12 +38,17 @@
'Authorization': `Token ${this.$root.store.accessToken}`,
}
})
.then((response) => {
.then(response => {
if (response.data && response.data.id) {
this.userLikedProp = true;
this.likesProp += 1;
this.$emit('liked');
}
})
.catch(({response}) => {
if(response.status == 400 && response.data.error){
showNotification('error', response.data.error);
}
});
}
}

@ -3,6 +3,8 @@
*/
import 'ilyabirman-likely/release/likely.js';
import 'ilyabirman-likely/release/likely.css';
import "modal-video/js/jquery-modal-video.min.js";
import "modal-video/css/modal-video.min.css";
import "./modules/common";
import "./modules/header";
import "./modules/search";

@ -5,6 +5,7 @@ import isLength from 'validator/lib/isLength';
$(document).ready(function () {
let auth = $('.js-auth'),
popup = $('.js-popup-auth'),
type = auth.find('.js-auth-type'),
tab = auth.find('.js-auth-tab'),
login = auth.find('.js-auth-login'),
@ -153,7 +154,13 @@ $(document).ready(function () {
})
.done(function (data) {
if (data.success === true) {
location.href = '/';
const nextUrl = popup.data('next-url');
if(nextUrl){
window.location.href = nextUrl;
}
else{
window.location.reload();
}
} else {
authButton.removeClass('loading');
}
@ -234,7 +241,13 @@ $(document).ready(function () {
})
.done(function (data) {
if (data.success === true) {
location.href = '/';
const nextUrl = popup.data('next-url');
if(nextUrl){
window.location.href = nextUrl;
}
else{
window.location.reload();
}
} else {
registrationButton.removeClass('loading');
}
@ -275,7 +288,7 @@ $(document).ready(function () {
if (facebookResponse) {
if (facebookResponse.status === 'connected') {
login_with_facebook(facebookResponse.authResponse.accessToken);
login_with_facebook(facebookResponse.authResponse.accessToken, popup.data('next-url'));
return;
}
}
@ -305,14 +318,19 @@ function load_facebook() {
});
}
function login_with_facebook(accessToken) {
function login_with_facebook(accessToken, nextUrl) {
$.ajax('/auth/facebook_login/', {
method: 'POST',
data: {'access_token': accessToken},
})
.done(function (data) {
if (data.success === true) {
location.href = '/';
if(nextUrl){
window.location.href = nextUrl;
}
else{
window.location.reload();
}
}
})
.fail(function (xhr) {

@ -3,6 +3,34 @@ import {api} from './api';
var selectedWeekdays = {};
$(document).ready(function () {
$(".js-video-modal").each(function(){
const $this = $(this);
const url = $this.data('videoUrl');
if(! url){
return;
}
let data;
let videoId;
let channel;
if(url.indexOf('youtu.be') > -1){
videoId = url.split('youtu.be/')[1];
channel = 'youtube';
}
if(url.indexOf('youtube.com') > -1 && url.indexOf('watch') > -1){
const m = /[?&]v=([a-zA-Z]+)(&.)?/.exec(url);
channel = 'youtube';
videoId = m && m[1];
}
if(url.indexOf('vimeo.com') > -1){
const split = url.split('/');
channel = 'vimeo';
videoId = split[split.length - 1];
}
$this.attr('data-video-id', videoId);
$this.attr('data-video-url', '');
$this.modalVideo({ channel });
});
let body = $('body'),
popup = $('.popup.visible.open');
@ -32,6 +60,24 @@ $(document).ready(function () {
});
is_extend = true;
}
if(! window.LIL_STORE.user.id) {
const $btn = popup.find('.buy__btn');
$btn.click(function(event) {
event.preventDefault();
hidePopup().then(() => {
popup = $('.js-popup-auth');
popup.data('next-url', $btn.attr('href'));
showPopup();
});
});
}
}
if( data === '.js-popup-auth') {
let nextUrl = $(this).data('auth-next-url');
if(nextUrl === 'href') {
nextUrl = $(this).attr('href');
}
popup.data('next-url', nextUrl);
}
if($(this).data('day')) {
@ -70,21 +116,28 @@ $(document).ready(function () {
function showPopup(){
body.addClass('no-scroll');
popup.addClass('open');
setTimeout(function(){
popup.addClass('visible');
}, 100);
return new Promise((resolve) => {
setTimeout(function(){
popup.addClass('visible');
resolve();
}, 100);
});
}
function hidePopup(){
body.removeClass('no-scroll');
popup.removeClass('visible');
setTimeout(function(){
popup.removeClass('open');
}, 300);
popup.data('next-url', null);
if($('#password-reset__sent').is(':visible')) {
window.location.reload();
}
return new Promise((resolve) => {
setTimeout(function(){
popup.removeClass('open');
resolve();
}, 300);
});
}
$(document).on('change', '[data-day]', function(){
@ -126,4 +179,6 @@ $(document).ready(function () {
link = link+'?'+decodeURIComponent($.param({weekdays: weekdays}, true));
$('.but_btn_popup').attr('href', link);
}
updateCart();
});

@ -1519,12 +1519,17 @@ a.grey-link
flex: 0 0 95px
&__title
margin-bottom: 5px
display: flex
+fb
font-size: 20px
letter-spacing: 2px
+t
font-size: 12px
letter-spacing: 2px
+m
display: block
&-name
flex: 1
&__social
.social__item
margin-right: 3px
@ -1533,6 +1538,8 @@ a.grey-link
&__content
font-size: 16px
line-height: (22/16)
&__wrap
flex: 1
.toggle
font-size: 14px
@ -1777,6 +1784,16 @@ a.grey-link
+m
margin: 0 5px 10px
flex: 0 0 calc(33.33% - 10px)
&_promocodes &__item
text-align: center;
&__item-promocode
border: 1px solid #333;
display: inline-block;
padding: 7px 10px;
border-radius: 3px;
font-weight: bold;
letter-spacing: 2px;
text-transform: uppercase;
&__pic
display: block
width: 100%
@ -1833,7 +1850,7 @@ a.grey-link
&:nth-child(2)
flex: 0 0 60px
&:nth-child(3)
flex: 0 0 calc(100% - 290px)
flex: 1
+t
padding: 0
flex: 0 0 calc(100% - 120px)
@ -2942,6 +2959,12 @@ a.grey-link
width: 275px
+m
width: 100%
&__trial-lesson
+m
position: absolute;
right: 0;
padding: 0;
top: 9px;
.order
padding: 2px
@ -3845,6 +3868,12 @@ a.grey-link
+m
flex: 1 0 0
text-align: right
&__trial-lesson
display: inline-block
margin-top: 10px
+m
display: block
margin-bottom: -31px
&__time
margin: 15px 0
opacity: .5
@ -3863,6 +3892,7 @@ a.grey-link
display: block
width: 100%
border-radius: 50%
object-fit: cover
&__title
margin-bottom: 5px
font-size: 24px
@ -4079,6 +4109,12 @@ a
&.pic
position: relative
width: 100%
&__video iframe
height: 360px;
width: 640px;
max-width: 100%;
max-height: 60%;
.banner
height: 140px
@ -4149,3 +4185,7 @@ a
border-left: 3px solid transparent
border-radius: 50%
animation: loading .6s infinite linear
.modal-video-close-btn
+m
right: 0

Loading…
Cancel
Save