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

remotes/origin/hotfix/LIL-691
gzbender 7 years ago
commit 2b52d9852c
  1. 71
      api/v1/serializers/course.py
  2. 98
      api/v1/serializers/mixins.py
  3. 54
      api/v1/serializers/payment.py
  4. 3
      api/v1/urls.py
  5. 109
      api/v1/views.py
  6. 26
      apps/content/templates/content/blocks/video.html
  7. 3
      apps/course/filters.py
  8. 18
      apps/course/migrations/0044_course_age.py
  9. 30
      apps/course/migrations/0044_livelessoncomment.py
  10. 32
      apps/course/models.py
  11. 2
      apps/course/templates/course/_items.html
  12. 44
      apps/course/templates/course/course.html
  13. 51
      apps/course/templates/course/course_only_lessons.html
  14. 25
      apps/course/templates/course/courses.html
  15. 6
      apps/course/templates/course/inclusion/category_items.html
  16. 50
      apps/course/templates/course/lesson.html
  17. 13
      apps/course/views.py
  18. 16
      apps/payment/models.py
  19. 35
      apps/payment/views.py
  20. 5
      apps/school/templates/blocks/online.html
  21. 12
      apps/school/templates/school/livelesson_detail.html
  22. 2
      apps/school/templates/school/livelessons_list.html
  23. 7
      apps/school/templates/summer/online.html
  24. 6
      apps/school/templates/summer/prolong_btn.html
  25. 4
      apps/school/templates/summer/promo.html
  26. 6
      apps/school/templates/summer/schedule_purchased.html
  27. 5
      apps/school/views.py
  28. 7
      apps/user/templates/user/profile.html
  29. 17
      project/context_processors.py
  30. 14
      project/pusher.py
  31. 9
      project/settings.py
  32. 2
      project/templates/blocks/about.html
  33. 118
      project/templates/blocks/gallery.html
  34. 5
      project/templates/blocks/last_courses.html
  35. 15
      project/templates/blocks/lil_store_js.html
  36. 7
      project/templates/blocks/live.html
  37. 37
      project/templates/blocks/popup_buy.html
  38. 2
      project/templates/blocks/popup_course_lock.html
  39. 2
      project/templates/blocks/promo.html
  40. 2
      project/templates/lilcity/edit_index.html
  41. 1
      project/templates/lilcity/index.html
  42. 2
      project/templates/lilcity/main.html
  43. 6
      project/urls.py
  44. 2
      project/views.py
  45. 4
      requirements.txt
  46. 71
      web/src/components/Comment.vue
  47. 53
      web/src/components/CommentForm.vue
  48. 134
      web/src/components/Comments.vue
  49. 136
      web/src/components/ContestRedactor.vue
  50. 186
      web/src/components/CourseRedactor.vue
  51. 73
      web/src/components/LessonRedactor.vue
  52. 93
      web/src/components/blocks/BlockContent.vue
  53. 20
      web/src/components/blocks/BlockImages.vue
  54. 3
      web/src/components/blocks/Image.vue
  55. 28
      web/src/components/inputs/LilSelect.vue
  56. 4
      web/src/components/redactor/VueRedactor.vue
  57. 19
      web/src/js/app.js
  58. 17
      web/src/js/contest-redactor.js
  59. 20
      web/src/js/course-redactor.js
  60. 222
      web/src/js/modules/api.js
  61. 1
      web/src/js/modules/comments.js
  62. 112
      web/src/js/modules/courses.js
  63. 68
      web/src/js/modules/popup.js
  64. 143
      web/src/sass/_common.sass

@ -10,7 +10,7 @@ from apps.course.models import (
Comment, CourseComment, LessonComment, Comment, CourseComment, LessonComment,
Material, Lesson, Material, Lesson,
Like, Like,
) LiveLessonComment)
from .content import ( from .content import (
ImageObjectSerializer, ContentSerializer, ContentCreateSerializer, ImageObjectSerializer, ContentSerializer, ContentCreateSerializer,
GallerySerializer, GalleryImageSerializer, GallerySerializer, GalleryImageSerializer,
@ -137,6 +137,7 @@ class CourseCreateSerializer(DispatchContentMixin,
'from_author', 'from_author',
'cover', 'cover',
'price', 'price',
'age',
'is_infinite', 'is_infinite',
'deferred_start_at', 'deferred_start_at',
'category', 'category',
@ -288,6 +289,7 @@ class CourseSerializer(serializers.ModelSerializer):
'from_author', 'from_author',
'cover', 'cover',
'price', 'price',
'age',
'is_infinite', 'is_infinite',
'deferred_start_at', 'deferred_start_at',
'category', 'category',
@ -327,6 +329,7 @@ class CommentSerializer(serializers.ModelSerializer):
'parent', 'parent',
'deactivated_at', 'deactivated_at',
'created_at', 'created_at',
'created_at_humanize',
'update_at', 'update_at',
) )
@ -342,6 +345,8 @@ class CommentSerializer(serializers.ModelSerializer):
return CourseCommentSerializer(instance, context=self.context).to_representation(instance) return CourseCommentSerializer(instance, context=self.context).to_representation(instance)
elif isinstance(instance, LessonComment): elif isinstance(instance, LessonComment):
return LessonCommentSerializer(instance, context=self.context).to_representation(instance) return LessonCommentSerializer(instance, context=self.context).to_representation(instance)
elif isinstance(instance, LiveLessonComment):
return LiveLessonCommentSerializer(instance, context=self.context).to_representation(instance)
class CourseCommentSerializer(serializers.ModelSerializer): class CourseCommentSerializer(serializers.ModelSerializer):
@ -374,3 +379,67 @@ class LessonCommentSerializer(serializers.ModelSerializer):
read_only_fields = CommentSerializer.Meta.read_only_fields + ( read_only_fields = CommentSerializer.Meta.read_only_fields + (
'children', 'children',
) )
class LiveLessonCommentSerializer(serializers.ModelSerializer):
author = UserSerializer()
children = CommentSerializer(many=True)
class Meta:
model = LiveLessonComment
fields = CommentSerializer.Meta.fields + (
'live_lesson',
'children',
)
read_only_fields = CommentSerializer.Meta.read_only_fields + (
'children',
)
class CommentCreateSerializer(serializers.ModelSerializer):
obj_type = serializers.CharField(required=True)
obj_id = serializers.IntegerField(required=True)
class Meta:
model = Comment
fields = (
'id',
'content',
'author',
'parent',
'deactivated_at',
'created_at',
'update_at',
'obj_type',
'obj_id',
)
read_only_fields = (
'id',
'deactivated_at',
'created_at',
'update_at',
)
def create(self, validated_data):
obj_type = validated_data.pop('obj_type', None)
obj_id = validated_data.pop('obj_id', None)
if obj_type == Comment.OBJ_TYPE_COURSE:
validated_data['course_id'] = obj_id
return CourseCommentSerializer().create(validated_data)
elif obj_type == Comment.OBJ_TYPE_LESSON:
validated_data['lesson_id'] = obj_id
return LessonCommentSerializer().create(validated_data)
elif obj_type == Comment.OBJ_TYPE_LIVE_LESSON:
validated_data['live_lesson_id'] = obj_id
return LiveLessonCommentSerializer().create(validated_data)
def to_representation(self, instance):
if isinstance(instance, CourseComment):
return CourseCommentSerializer(instance, context=self.context).to_representation(instance)
elif isinstance(instance, LessonComment):
return LessonCommentSerializer(instance, context=self.context).to_representation(instance)
elif isinstance(instance, LiveLessonComment):
return LiveLessonCommentSerializer(instance, context=self.context).to_representation(instance)

@ -32,35 +32,23 @@ class DispatchContentMixin(object):
if 'id' in cdata and cdata['id']: if 'id' in cdata and cdata['id']:
t = Text.objects.get(id=cdata.pop('id')) t = Text.objects.get(id=cdata.pop('id'))
serializer = TextCreateSerializer(t, data=cdata) serializer = TextCreateSerializer(t, data=cdata)
if serializer.is_valid():
serializer.save()
else: else:
serializer = TextCreateSerializer(data=cdata) serializer = TextCreateSerializer(data=cdata)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
elif ctype == 'image': elif ctype == 'image':
if 'id' in cdata and cdata['id']: if 'id' in cdata and cdata['id']:
image = Image.objects.get(id=cdata.pop('id')) image = Image.objects.get(id=cdata.pop('id'))
serializer = ImageCreateSerializer(image, data=cdata) serializer = ImageCreateSerializer(image, data=cdata)
if serializer.is_valid():
image = serializer.save()
else:
continue
try:
image_object = ImageObject.objects.get(id=cdata['img'])
except ImageObject.DoesNotExist:
pass
else:
image.img = image_object
image.save()
else: else:
serializer = ImageCreateSerializer(data=cdata) serializer = ImageCreateSerializer(data=cdata)
if serializer.is_valid(): if serializer.is_valid():
image = serializer.save() image = serializer.save()
else: else:
continue continue
if 'img' in cdata:
try: try:
image_object = ImageObject.objects.get(id=cdata['img']) image_object = ImageObject.objects.get(id=cdata.get('img'))
except ImageObject.DoesNotExist: except ImageObject.DoesNotExist:
pass pass
else: else:
@ -71,25 +59,15 @@ class DispatchContentMixin(object):
if 'id' in cdata and cdata['id']: if 'id' in cdata and cdata['id']:
it = ImageText.objects.get(id=cdata.pop('id')) it = ImageText.objects.get(id=cdata.pop('id'))
serializer = ImageTextCreateSerializer(it, data=cdata) serializer = ImageTextCreateSerializer(it, data=cdata)
if serializer.is_valid():
it = serializer.save()
else:
continue
try:
image_object = ImageObject.objects.get(id=cdata['img'])
except ImageObject.DoesNotExist:
pass
else:
it.img = image_object
it.save()
else: else:
serializer = ImageTextCreateSerializer(data=cdata) serializer = ImageTextCreateSerializer(data=cdata)
if serializer.is_valid(): if serializer.is_valid():
it = serializer.save() it = serializer.save()
else: else:
continue continue
if 'img' in cdata:
try: try:
image_object = ImageObject.objects.get(id=cdata['img']) image_object = ImageObject.objects.get(id=cdata.get('img'))
except ImageObject.DoesNotExist: except ImageObject.DoesNotExist:
pass pass
else: else:
@ -100,52 +78,36 @@ class DispatchContentMixin(object):
if 'id' in cdata and cdata['id']: if 'id' in cdata and cdata['id']:
v = Video.objects.get(id=cdata.pop('id')) v = Video.objects.get(id=cdata.pop('id'))
serializer = VideoCreateSerializer(v, data=cdata) serializer = VideoCreateSerializer(v, data=cdata)
if serializer.is_valid():
serializer.save()
else: else:
serializer = VideoCreateSerializer(data=cdata) serializer = VideoCreateSerializer(data=cdata)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
elif ctype == 'images': elif ctype == 'images':
if 'id' in cdata and cdata['id']: if 'id' in cdata and cdata['id']:
g = Gallery.objects.get(id=cdata['id']) g = Gallery.objects.get(id=cdata['id'])
g.position = cdata['position'] g.position = cdata['position']
g.title = cdata['title'] g.title = cdata['title']
g.uuid = cdata['uuid'] g.uuid = cdata['uuid']
setattr(g, obj_type, obj)
g.save()
if 'images' in cdata:
for image in cdata['images']:
if 'img' in image and image['img']:
if 'id' in image and image['id']:
gi = GalleryImage.objects.get(id=image['id'])
gi.img = ImageObject.objects.get(id=image['img'])
gi.save()
else:
gi = GalleryImage.objects.create(
gallery=g,
img=ImageObject.objects.get(id=image['img'])
)
else: else:
g = Gallery( g = Gallery(
position=cdata['position'], position=cdata['position'],
title=cdata['title'], title=cdata['title'],
uuid=cdata['uuid'], uuid=cdata['uuid'],
) )
setattr(g, obj_type, obj) setattr(g, obj_type, obj)
g.save() g.save()
if 'images' in cdata: if 'images' in cdata:
for image in cdata['images']: for image in cdata['images']:
if 'img' in image and image['img']: if 'img' in image and image['img']:
if 'id' in image and image['id']: if 'id' in image and image['id']:
gi = GalleryImage.objects.get(id=image['id']) gi = GalleryImage.objects.get(id=image['id'])
gi.img = ImageObject.objects.get(id=image['img']) gi.img = ImageObject.objects.get(id=image['img'])
gi.save() gi.save()
else: else:
gi = GalleryImage.objects.create( gi = GalleryImage.objects.create(
gallery=g, gallery=g,
img=ImageObject.objects.get(id=image['img']) img=ImageObject.objects.get(id=image['img'])
) )
class DispatchMaterialMixin(object): class DispatchMaterialMixin(object):

@ -68,7 +68,6 @@ class PaymentSerializer(serializers.ModelSerializer):
class AuthorBalanceSerializer(serializers.ModelSerializer): class AuthorBalanceSerializer(serializers.ModelSerializer):
author = UserSerializer() author = UserSerializer()
# payment = PaymentSerializer()
payment = serializers.SerializerMethodField() payment = serializers.SerializerMethodField()
class Meta: class Meta:
@ -111,6 +110,41 @@ class AuthorBalanceSerializer(serializers.ModelSerializer):
return data return data
class PaymentSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Payment
fields = BASE_PAYMENT_FIELDS
read_only_fields = (
'id',
'user',
'data',
'created_at',
'update_at',
)
def to_representation(self, instance):
if isinstance(instance, CoursePayment):
return CoursePaymentSerializer(instance, context=self.context).to_representation(instance)
elif isinstance(instance, SchoolPayment):
return SchoolPaymentSerializer(instance, context=self.context).to_representation(instance)
class CoursePaymentCreateSerializer(serializers.ModelSerializer):
class Meta:
model = CoursePayment
fields = BASE_PAYMENT_FIELDS + ('course',)
read_only_fields = (
'id',
'user',
'course',
'created_at',
'update_at',
)
class CoursePaymentSerializer(serializers.ModelSerializer): class CoursePaymentSerializer(serializers.ModelSerializer):
user = UserSerializer() user = UserSerializer()
course = CourseSerializer() course = CourseSerializer()
@ -127,6 +161,24 @@ class CoursePaymentSerializer(serializers.ModelSerializer):
) )
class SchoolPaymentCreateSerializer(serializers.ModelSerializer):
class Meta:
model = SchoolPayment
fields = BASE_PAYMENT_FIELDS + (
'weekdays',
'date_start',
'date_end',
)
read_only_fields = (
'id',
'user',
'course',
'created_at',
'update_at',
)
class SchoolPaymentSerializer(serializers.ModelSerializer): class SchoolPaymentSerializer(serializers.ModelSerializer):
user = UserSerializer() user = UserSerializer()

@ -17,7 +17,7 @@ from .views import (
GalleryViewSet, GalleryImageViewSet, GalleryViewSet, GalleryImageViewSet,
UserViewSet, LessonViewSet, ImageObjectViewSet, UserViewSet, LessonViewSet, ImageObjectViewSet,
SchoolScheduleViewSet, LiveLessonViewSet, SchoolScheduleViewSet, LiveLessonViewSet,
PaymentViewSet, PaymentViewSet, ObjectCommentsViewSet,
ContestViewSet, ContestWorkViewSet) ContestViewSet, ContestWorkViewSet)
router = DefaultRouter() router = DefaultRouter()
@ -27,6 +27,7 @@ router.register(r'baners', BanerViewSet, base_name='baners')
router.register(r'categories', CategoryViewSet, base_name='categories') router.register(r'categories', CategoryViewSet, base_name='categories')
router.register(r'courses', CourseViewSet, base_name='courses') router.register(r'courses', CourseViewSet, base_name='courses')
router.register(r'comments', CommentViewSet, base_name='comments') router.register(r'comments', CommentViewSet, base_name='comments')
router.register(r'obj-comments', ObjectCommentsViewSet, base_name='obj-comments')
router.register(r'materials', MaterialViewSet, base_name='materials') router.register(r'materials', MaterialViewSet, base_name='materials')
router.register(r'lessons', LessonViewSet, base_name='lessons') router.register(r'lessons', LessonViewSet, base_name='lessons')
router.register(r'likes', LikeViewSet, base_name='likes') router.register(r'likes', LikeViewSet, base_name='likes')

@ -1,7 +1,9 @@
from datetime import datetime
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from rest_framework import status, views, viewsets, generics from rest_framework import status, views, viewsets, generics
from rest_framework.decorators import detail_route, list_route, action from rest_framework.decorators import detail_route, list_route, action, permission_classes, authentication_classes
from rest_framework.response import Response from rest_framework.response import Response
from . import ExtendedModelViewSet from . import ExtendedModelViewSet
@ -11,10 +13,11 @@ from .serializers.course import (
CategorySerializer, LikeSerializer, CategorySerializer, LikeSerializer,
CourseSerializer, CourseCreateSerializer, CourseSerializer, CourseCreateSerializer,
CourseBulkChangeCategorySerializer, CourseBulkChangeCategorySerializer,
CommentSerializer, CommentSerializer, CommentCreateSerializer,
MaterialSerializer, MaterialCreateSerializer, MaterialSerializer, MaterialCreateSerializer,
LessonSerializer, LessonCreateSerializer, LessonSerializer, LessonCreateSerializer,
LikeCreateSerializer) LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer,
LiveLessonCommentSerializer)
from .serializers.content import ( from .serializers.content import (
BanerSerializer, BanerSerializer,
ImageSerializer, ImageCreateSerializer, ImageSerializer, ImageCreateSerializer,
@ -34,7 +37,7 @@ from .serializers.payment import (
AuthorBalanceSerializer, AuthorBalanceCreateSerializer, AuthorBalanceSerializer, AuthorBalanceCreateSerializer,
PaymentSerializer, CoursePaymentSerializer, PaymentSerializer, CoursePaymentSerializer,
SchoolPaymentSerializer, SchoolPaymentSerializer,
) CoursePaymentCreateSerializer, SchoolPaymentCreateSerializer)
from .serializers.user import ( from .serializers.user import (
AuthorRequestSerializer, AuthorRequestSerializer,
UserSerializer, UserPhotoSerializer, UserSerializer, UserPhotoSerializer,
@ -54,7 +57,7 @@ from apps.course.models import (
Comment, CourseComment, LessonComment, Comment, CourseComment, LessonComment,
Material, Lesson, Material, Lesson,
Like, Like,
) LiveLessonComment)
from apps.config.models import Config from apps.config.models import Config
from apps.content.models import ( from apps.content.models import (
Baner, Image, Text, ImageText, Video, Baner, Image, Text, ImageText, Video,
@ -66,6 +69,7 @@ from apps.payment.models import (
) )
from apps.school.models import SchoolSchedule, LiveLesson from apps.school.models import SchoolSchedule, LiveLesson
from apps.user.models import AuthorRequest from apps.user.models import AuthorRequest
from project.pusher import pusher
User = get_user_model() User = get_user_model()
@ -429,15 +433,15 @@ class ConfigViewSet(generics.RetrieveUpdateAPIView):
class CommentViewSet(ExtendedModelViewSet): class CommentViewSet(ExtendedModelViewSet):
queryset = Comment.objects.filter(level=0) queryset = Comment.objects.all()
serializer_class = CommentSerializer serializer_class = CommentSerializer
permission_classes = (IsAdmin,) permission_classes = (IsAuthorObjectOrAdmin,)
def get_queryset(self): def get_queryset(self):
queryset = self.queryset queryset = self.queryset
is_deactivated = self.request.query_params.get('is_deactivated', '0') is_deactivated = self.request.query_params.get('is_deactivated', '0')
if is_deactivated == '0': if is_deactivated == '0':
queryset = queryset queryset = queryset.filter(level=0)
elif is_deactivated == '1': elif is_deactivated == '1':
queryset = queryset.filter(deactivated_at__isnull=True) queryset = queryset.filter(deactivated_at__isnull=True)
elif is_deactivated == '2': elif is_deactivated == '2':
@ -446,6 +450,79 @@ class CommentViewSet(ExtendedModelViewSet):
return queryset return queryset
class ObjectCommentsViewSet(ExtendedModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentCreateSerializer
ordering_fields = ('update_at', )
def get_queryset(self):
queryset = self.queryset
obj_type = self.request.query_params.get('obj_type')
obj_id = self.request.query_params.get('obj_id')
is_deactivated = self.request.query_params.get('is_deactivated')
if obj_type == Comment.OBJ_TYPE_COURSE:
queryset = CourseComment.objects.filter(course=obj_id)
elif obj_type == Comment.OBJ_TYPE_LESSON:
queryset = LessonComment.objects.filter(lesson=obj_id)
elif obj_type == Comment.OBJ_TYPE_LIVE_LESSON:
queryset = LiveLessonComment.objects.filter(live_lesson=obj_id)
if is_deactivated == '0':
queryset = queryset.filter(level=0)
elif is_deactivated == '1':
queryset = queryset.filter(deactivated_at__isnull=True)
elif is_deactivated == '2':
queryset = queryset.filter(deactivated_at__isnull=False)
return queryset
def get_serializer_class(self):
if self.request.method == 'POST':
return CommentCreateSerializer
obj_type = self.request.query_params.get('obj_type')
serializer_class = CommentSerializer
if obj_type == Comment.OBJ_TYPE_COURSE:
serializer_class = CourseCommentSerializer
elif obj_type == Comment.OBJ_TYPE_LESSON:
serializer_class = LessonCommentSerializer
elif obj_type == Comment.OBJ_TYPE_LIVE_LESSON:
serializer_class = LiveLessonCommentSerializer
return serializer_class
def perform_create(self, serializer):
obj_type = self.request.data.get('obj_type')
obj_id = self.request.data.get('obj_id')
serializer.save()
try:
pusher().trigger(f'comments_{obj_type}_{obj_id}', 'add', serializer.data)
except Exception as e:
print(e)
@permission_classes((IsAuthorObjectOrAdmin,))
def perform_destroy(self, instance):
obj_type = None
obj_id = None
if isinstance(instance, LessonComment):
obj_type = Comment.OBJ_TYPE_LESSON
obj_id = instance.lesson_id
elif isinstance(instance, CourseComment):
obj_type = Comment.OBJ_TYPE_COURSE
obj_id = instance.course_id
elif isinstance(instance, LiveLessonComment):
obj_type = Comment.OBJ_TYPE_LIVE_LESSON
obj_id = instance.live_lesson_id
serializer = self.get_serializer(instance)
try:
pusher().trigger(f'comments_{obj_type}_{obj_id}', 'delete', serializer.data)
except Exception as e:
print(e)
instance.delete()
@authentication_classes([])
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()).filter(parent__isnull=True)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class AuthorRequestViewSet(ExtendedModelViewSet): class AuthorRequestViewSet(ExtendedModelViewSet):
queryset = AuthorRequest.objects.all() queryset = AuthorRequest.objects.all()
serializer_class = AuthorRequestSerializer serializer_class = AuthorRequestSerializer
@ -453,7 +530,7 @@ class AuthorRequestViewSet(ExtendedModelViewSet):
filter_fields = ('status',) filter_fields = ('status',)
class PaymentViewSet(ExtendedModelViewSet): class PaymentViewSet(viewsets.ModelViewSet):
queryset = Payment.objects.all() queryset = Payment.objects.all()
serializer_class = PaymentSerializer serializer_class = PaymentSerializer
permission_classes = (IsAdmin,) permission_classes = (IsAdmin,)
@ -465,6 +542,16 @@ class PaymentViewSet(ExtendedModelViewSet):
) )
search_fields = ('user__email', 'user__first_name', 'user__last_name',) search_fields = ('user__email', 'user__first_name', 'user__last_name',)
def get_serializer(self, instance, *args, **kwargs):
serializer_class = self.get_serializer_class()
if 'update' in self.action:
if isinstance(instance, CoursePayment):
serializer_class = CoursePaymentCreateSerializer
elif isinstance(instance, SchoolPayment):
serializer_class = SchoolPaymentCreateSerializer
kwargs['context'] = self.get_serializer_context()
return serializer_class(instance, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
queryset = self.queryset queryset = self.queryset
course = self.request.query_params.get('course') course = self.request.query_params.get('course')
@ -481,10 +568,12 @@ class PaymentViewSet(ExtendedModelViewSet):
user = request.query_params.get('user') user = request.query_params.get('user')
course = request.query_params.get('course') course = request.query_params.get('course')
weekdays = request.query_params.getlist('weekdays[]') weekdays = request.query_params.getlist('weekdays[]')
date_start = request.query_params.get('date_start')
user = user and User.objects.get(pk=user) user = user and User.objects.get(pk=user)
course = course and Course.objects.get(pk=course) course = course and Course.objects.get(pk=course)
date_start = date_start and datetime.strptime(date_start, '%Y-%m-%d')
return Response(Payment.calc_amount(user=user, course=course, weekdays=weekdays)) return Response(Payment.calc_amount(user=user, course=course, date_start=date_start, weekdays=weekdays))
class ContestViewSet(ExtendedModelViewSet): class ContestViewSet(ExtendedModelViewSet):

@ -15,29 +15,3 @@
</div> </div>
</div> </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>

@ -5,7 +5,8 @@ from .models import Course
class CourseFilter(django_filters.FilterSet): class CourseFilter(django_filters.FilterSet):
category = django_filters.CharFilter(field_name='category') category = django_filters.CharFilter(field_name='category')
age = django_filters.ChoiceFilter(field_name='age', choices=Course.AGE_CHOICES)
class Meta: class Meta:
model = Course model = Course
fields = ['category'] fields = ['category', 'age']

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-09-07 00:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0043_auto_20180824_2132'),
]
operations = [
migrations.AddField(
model_name='course',
name='age',
field=models.SmallIntegerField(choices=[(0, 'Любой возраст'), (1, 'до 5'), (2, '5-7'), (3, '7-9'), (4, '9-12'), (5, '12-15'), (6, '15-18'), (7, 'от 18')], default=0),
),
]

@ -0,0 +1,30 @@
# Generated by Django 2.0.6 on 2018-09-19 15:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('school', '0021_schoolschedule_trial_lesson'),
('course', '0043_auto_20180824_2132'),
]
operations = [
migrations.CreateModel(
name='LiveLessonComment',
fields=[
('comment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='course.Comment')),
('live_lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='school.LiveLesson')),
],
options={
'verbose_name': 'Комментарий урока школы',
'verbose_name_plural': 'Комментарии уроков школы',
'ordering': ('tree_id', 'lft'),
'abstract': False,
'base_manager_name': 'objects',
},
bases=('course.comment',),
),
]

@ -10,6 +10,7 @@ from django.urls import reverse_lazy
from django.conf import settings from django.conf import settings
from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey
from apps.school.models import LiveLesson
from project.mixins import BaseModel, DeactivatedMixin from project.mixins import BaseModel, DeactivatedMixin
from apps.content.models import ImageObject, Gallery, Video, ContestWork from apps.content.models import ImageObject, Gallery, Video, ContestWork
@ -49,6 +50,23 @@ class Course(BaseModel, DeactivatedMixin):
(ARCHIVED, 'Archived'), (ARCHIVED, 'Archived'),
(DENIED, 'Denied') (DENIED, 'Denied')
) )
AGE_LT5 = 1
AGE_57 = 2
AGE_79 = 3
AGE_912 = 4
AGE_1215 = 5
AGE_1518 = 6
AGE_GT18 = 7
AGE_CHOICES = (
(0, 'Любой возраст'),
(AGE_LT5, 'до 5'),
(AGE_57, '5-7'),
(AGE_79, '7-9'),
(AGE_912, '9-12'),
(AGE_1215, '12-15'),
(AGE_1518, '15-18'),
(AGE_GT18, 'от 18'),
)
slug = models.SlugField( slug = models.SlugField(
allow_unicode=True, null=True, blank=True, allow_unicode=True, null=True, blank=True,
max_length=100, unique=True, db_index=True, max_length=100, unique=True, db_index=True,
@ -72,6 +90,7 @@ class Course(BaseModel, DeactivatedMixin):
'Цена курса', help_text='Если цена не выставлена, то курс бесплатный', 'Цена курса', help_text='Если цена не выставлена, то курс бесплатный',
max_digits=10, decimal_places=2, null=True, blank=True max_digits=10, decimal_places=2, null=True, blank=True
) )
age = models.SmallIntegerField(choices=AGE_CHOICES, default=0)
is_infinite = models.BooleanField(default=False) is_infinite = models.BooleanField(default=False)
deferred_start_at = models.DateTimeField( deferred_start_at = models.DateTimeField(
'Отложенный запуск курса', help_text='Заполнить если курс отложенный', 'Отложенный запуск курса', help_text='Заполнить если курс отложенный',
@ -240,6 +259,9 @@ class Material(models.Model):
class Comment(PolymorphicMPTTModel, DeactivatedMixin): class Comment(PolymorphicMPTTModel, DeactivatedMixin):
OBJ_TYPE_COURSE = 'course'
OBJ_TYPE_LESSON = 'lesson'
OBJ_TYPE_LIVE_LESSON = 'live-lesson'
content = models.TextField('Текст комментария', default='') content = models.TextField('Текст комментария', default='')
author = models.ForeignKey(User, on_delete=models.CASCADE) author = models.ForeignKey(User, on_delete=models.CASCADE)
parent = PolymorphicTreeForeignKey( parent = PolymorphicTreeForeignKey(
@ -284,5 +306,15 @@ class LessonComment(Comment):
verbose_name_plural = 'Комментарии уроков' verbose_name_plural = 'Комментарии уроков'
class LiveLessonComment(Comment):
live_lesson = models.ForeignKey(
LiveLesson, on_delete=models.CASCADE, related_name='comments'
)
class Meta(Comment.Meta):
verbose_name = 'Комментарий урока школы'
verbose_name_plural = 'Комментарии уроков школы'
class ContestWorkComment(Comment): class ContestWorkComment(Comment):
contest_work = models.ForeignKey(ContestWork, on_delete=models.CASCADE, related_name='comments') contest_work = models.ForeignKey(ContestWork, on_delete=models.CASCADE, related_name='comments')

@ -54,7 +54,7 @@
{% endif %} {% endif %}
</div> </div>
<a class="courses__title" href="{% url 'course' course.id %}?next={{ request.get_full_path }}">{{ course.title }}</a> <a class="courses__title" href="{% url 'course' course.id %}?next={{ request.get_full_path }}">{{ course.title }}</a>
<div class="courses__content">{{ course.short_description }} <div class="courses__content">{{ course.short_description | safe | linebreaks | truncatechars_html:300 }}
</div> </div>
<div class="courses__user user"> <div class="courses__user user">
<a href="{% if course.author %}{% url 'user' course.author.id %}{% endif %}"> <a href="{% if course.author %}{% url 'user' course.author.id %}{% endif %}">

@ -4,14 +4,14 @@
{% load rupluralize from plural %} {% load rupluralize from plural %}
{% block title %}{{ course.title }} - {{ block.super }}{% endblock title %} {% block title %}{{ course.title }} - {{ block.super }}{% endblock title %}
{% comment %} seo tags {% endcomment %} {% comment %} seo tags {% endcomment %}
{% block description %}{{ course.short_description }}{% endblock description%} {% block description %}{{ course.short_description | striptags }}{% endblock description%}
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %} {% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ course.title }} - {{ block.super }}{% endblock ogtitle %} {% block ogtitle %}{{ course.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %} {% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% if course.cover and course.cover.image %} {% if course.cover and course.cover.image %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %} {% block ogimage %}http://{{request.META.HTTP_HOST}}{% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %}
{% endif %} {% endif %}
{% block ogdescription %}{{ course.short_description }}{% endblock ogdescription %} {% block ogdescription %}{{ course.short_description | striptags }}{% endblock ogdescription %}
{% block content %} {% block content %}
<div class="section section_border course"> <div class="section section_border course">
@ -25,6 +25,9 @@
</div> </div>
<div class="go__title">Вернуться</div> <div class="go__title">Вернуться</div>
</a> </a>
{% if has_full_access %}
<a class="go__btn btn" href="{% url 'course_edit' course.id %}">Редактировать</a>
{% endif %}
{% if course.author != request.user and not paid and course.price %} {% if course.author != request.user and not paid and course.price %}
<a <a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md" class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
@ -70,7 +73,7 @@
</a> </a>
</div> </div>
<div class="course__title title">{{ course.title }}</div> <div class="course__title title">{{ course.title }}</div>
<div class="course__content">{{ course.short_description | linebreaks }}</div> <div class="course__content">{{ course.short_description | safe | linebreaks }}</div>
<a href="{% url 'user' course.author.id %}"> <a href="{% url 'user' course.author.id %}">
<div class="course__user user"> <div class="course__user user">
{% if course.author.photo %} {% if course.author.photo %}
@ -270,7 +273,7 @@
</a> </a>
</div> </div>
<div class="course__title title">{{ course.title }}</div> <div class="course__title title">{{ course.title }}</div>
<div class="course__content">{{ course.short_description | linebreaks }}</div> <div class="course__content">{{ course.short_description | safe | linebreaks }}</div>
<a href="{% url 'user' course.author.id %}"> <a href="{% url 'user' course.author.id %}">
<div class="course__user user user_white"> <div class="course__user user user_white">
{% if course.author.photo %} {% if course.author.photo %}
@ -340,38 +343,7 @@
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">Задавайте вопросы:</div> <div class="title">Задавайте вопросы:</div>
<div class="questions"> <comments obj-type="course" obj-id="{{ course.id }}"></comments>
{% if request.user.is_authenticated %}
<form class="questions__form" method="post" action="{% url 'coursecomment' course_id=course.id %}">
<input type="hidden" name="reply_id">
<div class="questions__ava ava">
<img
class="ava__pic"
{% if request.user.photo %}
src="{{ request.user.photo.url }}"
{% else %}
src="{% static 'img/user_default.jpg' %}"
{% endif %}
>
</div>
<div class="questions__wrap">
<div class="questions__reply-info">В ответ на
<a href="" class="questions__reply-anchor">этот комментарий</a>.
<a href="#" class="questions__reply-cancel grey-link">Отменить</a>
</div>
<div class="questions__field">
<textarea class="questions__textarea" placeholder="Спросите автора курса интересующие вас вопросы"></textarea>
</div>
<button class="questions__btn btn btn_light">ОТПРАВИТЬ</button>
</div>
</form>
{% else %}
<div>Только зарегистрированные пользователи могут оставлять комментарии.</div>
{% endif %}
<div class="questions__list">
{% include "templates/blocks/comments.html" with object=course %}
</div>
</div>
</div> </div>
</div> </div>
<div class="section"> <div class="section">

@ -4,14 +4,14 @@
{% load rupluralize from plural %} {% load rupluralize from plural %}
{% block title %}{{ course.title }} - {{ block.super }}{% endblock title %} {% block title %}{{ course.title }} - {{ block.super }}{% endblock title %}
{% comment %} seo tags {% endcomment %} {% comment %} seo tags {% endcomment %}
{% block description %}{{ course.short_description }}{% endblock description%} {% block description %}{{ course.short_description | striptags }}{% endblock description%}
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %} {% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ course.title }} - {{ block.super }}{% endblock ogtitle %} {% block ogtitle %}{{ course.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %} {% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% if course.cover %} {% if course.cover %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %} {% block ogimage %}http://{{request.META.HTTP_HOST}}{% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %}
{% endif %} {% endif %}
{% block ogdescription %}{{ course.short_description }}{% endblock ogdescription %} {% block ogdescription %}{{ course.short_description | striptags }}{% endblock ogdescription %}
{% block content %} {% block content %}
<div class="section section_border"> <div class="section section_border">
@ -25,6 +25,9 @@
</div> </div>
<div class="go__title">Вернуться</div> <div class="go__title">Вернуться</div>
</a> </a>
{% if has_full_access %}
<a class="go__btn btn" href="{% url 'course_edit' course.id %}">Редактировать</a>
{% endif %}
{% if not paid and course.price and not has_full_access %} {% if not paid and course.price and not has_full_access %}
<a <a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md" class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
@ -68,7 +71,7 @@
</a> </a>
</div> </div>
<div class="course__title title">{{ course.title }}</div> <div class="course__title title">{{ course.title }}</div>
<div class="course__content">{{ course.short_description }}</div> <div class="course__content">{{ course.short_description | safe | linebreaks }}</div>
<a href="{% url 'user' course.author.id %}"> <a href="{% url 'user' course.author.id %}">
<div class="course__user user"> <div class="course__user user">
{% if course.author.photo %} {% if course.author.photo %}
@ -174,27 +177,21 @@
<div class="lessons__title title">Содержание курса</div> <div class="lessons__title title">Содержание курса</div>
<div class="lessons__list"> <div class="lessons__list">
{% for lesson in lessons %} {% for lesson in lessons %}
{% if course.author == request.user or request.user.role >= request.user.TEACHER_ROLE or paid %}
<a href="{% url 'lesson' pk=lesson.id %}">
{% else %}
<a href="#">
{% endif %}
<div class="lessons__item"> <div class="lessons__item">
<div class="lessons__row"> <div class="lessons__row">
<div class="lessons__content-wrapper">
<div class="lessons__subtitle subtitle">{{ forloop.counter }}. {{ lesson.title }}</div>
<div class="lessons__content">{{ lesson.short_description | truncatechars_html:800 | safe }}</div>
<a href="{% url 'lesson' pk=lesson.id %}" class="btn btn_stroke">Перейти к уроку</a>
</div>
<div class="lessons__preview"> <div class="lessons__preview">
<div class="lessons__pic-wrapper"> <div class="lessons__pic-wrapper">
<img class="lessons__pic" <img class="lessons__pic"
src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}"> src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}">
</div> </div>
</div> </div>
<div>
<div class="lessons__subtitle subtitle">{{ forloop.counter }}. {{ lesson.title }}</div>
<div class="lessons__content">{{ lesson.short_description | truncatechars_html:800 | safe }}</div>
<a href="{% url 'lesson' pk=lesson.id %}" class="btn btn_stroke">Перейти к уроку</a>
</div>
</div> </div>
</div> </div>
</a>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -204,31 +201,7 @@
<div class="section section_gray"> <div class="section section_gray">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">Задавайте вопросы:</div> <div class="title">Задавайте вопросы:</div>
<div class="questions"> <comments obj-type="course" obj-id="{{ course.id }}"></comments>
{% if user.is_authenticated %}
<form class="questions__form" method="post" action="{% url 'coursecomment' course_id=course.id %}">
<input type="hidden" name="reply_id">
<div class="questions__ava ava">
<img class="ava__pic" src="{% static 'img/user_default.jpg' %}">
</div>
<div class="questions__wrap">
<div class="questions__reply-info">В ответ на
<a href="" class="questions__reply-anchor">этот комментарий</a>.
<a href="#" class="questions__reply-cancel grey-link">Отменить</a>
</div>
<div class="questions__field">
<textarea class="questions__textarea" placeholder="Задайте автору курса интересующие вас вопросы"></textarea>
</div>
<button class="questions__btn btn btn_light">ОТПРАВИТЬ</button>
</div>
</form>
{% else %}
<div>Только зарегистрированные пользователи могут оставлять комментарии.</div>
{% endif %}
<div class="questions__list">
{% include "templates/blocks/comments.html" with object=course %}
</div>
</div>
</div> </div>
</div> </div>
<div class="section"> <div class="section">

@ -23,15 +23,32 @@
<div class="field__select select js-select{% if category %} selected{% endif %}" data-category-select> <div class="field__select select js-select{% if category %} selected{% endif %}" data-category-select>
<div class="select__head js-select-head">Категории</div> <div class="select__head js-select-head">Категории</div>
<div class="select__drop js-select-drop"> <div class="select__drop js-select-drop">
<div class="select__option js-select-option{% if not category %} active{% endif %}" <div class="select__option js-select-option{% if not category.0 %} active{% endif %}" data-category-option>
data-category-option data-category-url="{% url 'courses' %}"> <div class="select__title">Все категории</div>
<div class="select__title">Все курсы</div>
</div> </div>
{% category_items category %} {% category_items category %}
</div> </div>
<input class="select__input" type="hidden"></div> <input class="select__input" type="hidden"></div>
</div> </div>
</div> </div>
<div class="head__field field">
<div class="field__wrap">
<div class="field__select select js-select{% if age.0 %} selected{% endif %}">
<div class="select__head js-select-head">{% if age_name %}{{ age_name }}{% else %}Возраст{% endif %}</div>
<div class="select__drop js-select-drop">
<div class="select__option js-select-option{% if not age.0 %} active{% endif %}" data-age-option>
<div class="select__title">Любой возраст</div>
</div>
{% for a in ages %}
<div class="select__option js-select-option{% if age and age.0 == a.0 %} active{% endif %}" data-age-option
data-age="{{ a.0 }}" data-age-name="{{ a.1 }}">
<div class="select__title">{{ a.1 }}</div>
</div>
{% endfor %}
</div>
<input class="select__input" type="hidden"></div>
</div>
</div>
</div> </div>
</div> </div>
<div class="courses"> <div class="courses">
@ -40,7 +57,7 @@
</div> </div>
<div class="courses__load load"> <div class="courses__load load">
{% if page_obj.has_next %} {% if page_obj.has_next %}
<button class="load__btn btn" data-next-page-url="{% url 'courses' %}?page={{ page_obj.next_page_number }}">Подгрузить еще</button> <button class="load__btn btn" data-next-page="{{ page_obj.next_page_number }}">Подгрузить еще</button>
{% endif %} {% endif %}
</div> </div>
</div> </div>

@ -1,6 +1,6 @@
{% for cat in category_items %} {% for cat in category_items %}
<div class="select__option js-select-option{% if category == cat.id %} active{% endif %}" <div class="select__option js-select-option{% if category == cat.id %} active{% endif %}" data-category-option
data-category-option data-category-name="{{ cat.title }}" data-category-url="{% url 'courses' %}?category={{ cat.id }}"> data-category-name="{{ cat.title }}" data-category="{{ cat.id }}">
<div class="select__title">{{ cat.title }}</div> <div class="select__title">{{ cat.title }}</div>
</div> </div>
{% endfor %} {% endfor %}

@ -2,6 +2,9 @@
{% load static %} {% load static %}
{% block title %}{{ lesson.title }} - {{ block.super }}{% endblock title %} {% block title %}{{ lesson.title }} - {{ block.super }}{% endblock title %}
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ lesson.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{% if lesson.course.cover %}{{ lesson.course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %} {% block ogimage %}http://{{request.META.HTTP_HOST}}{% if lesson.course.cover %}{{ lesson.course.cover.image.url }}{% else %}{% static 'img/og_courses.jpg' %}{% endif %}{% endblock ogimage %}
{% block content %} {% block content %}
<div class="section" style="margin-bottom:0;padding-bottom:0"> <div class="section" style="margin-bottom:0;padding-bottom:0">
@ -28,15 +31,9 @@
</div> </div>
<div class="lesson"> <div class="lesson">
<div class="lesson__row"> <div class="lesson__row">
<div class="lesson__preview">
<div class="lesson__pic-wrapper">
<img class="lesson__pic"
src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}">
</div>
</div>
<div> <div>
<div class="lesson__subtitle subtitle">{{ lesson.title }}</div> <div class="lesson__subtitle subtitle">{{ lesson.title }}</div>
<div class="lesson__content">{{ lesson.short_description | linebreaks }}</div> <div class="lesson__content">{{ lesson.short_description | safe | linebreaks }}</div>
<a href="{% url 'user' lesson.author.id %}"> <a href="{% url 'user' lesson.author.id %}">
<div class="lesson__user user"> <div class="lesson__user user">
{% if lesson.author.photo %} {% if lesson.author.photo %}
@ -57,6 +54,12 @@
</div> </div>
</a> </a>
</div> </div>
<div class="lesson__preview">
<div class="lesson__pic-wrapper">
<img class="lesson__pic"
src="{% if lesson.cover %}{{ lesson.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}">
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -114,38 +117,7 @@
<div class="section section_gray"> <div class="section section_gray">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">Задавайте вопросы:</div> <div class="title">Задавайте вопросы:</div>
<div class="questions"> <comments obj-type="lesson" obj-id="{{ lesson.id }}"></comments>
{% if request.user.is_authenticated %}
<form class="questions__form" method="post" action="{% url 'lessoncomment' lesson_id=lesson.id %}">
<input type="hidden" name="reply_id">
<div class="questions__ava ava">
<img
class="ava__pic"
{% if request.user.photo %}
src="{{ request.user.photo.url }}"
{% else %}
src="{% static 'img/user_default.jpg' %}"
{% endif %}
>
</div>
<div class="questions__wrap">
<div class="questions__reply-info">В ответ на
<a href="" class="questions__reply-anchor">этот комментарий</a>.
<a href="#" class="questions__reply-cancel grey-link">Отменить</a>
</div>
<div class="questions__field">
<textarea class="questions__textarea" placeholder="Спросите автора курса интересующие вас вопросы"></textarea>
</div>
<button class="questions__btn btn btn_light">ОТПРАВИТЬ</button>
</div>
</form>
{% else %}
<div>Только зарегистрированные пользователи могут оставлять комментарии.</div>
{% endif %}
<div class="questions__list">
{% include "templates/blocks/comments.html" with object=lesson %}
</div>
</div>
</div> </div>
</div> </div>
<div class="section"> <div class="section">

@ -167,9 +167,8 @@ class CourseEditView(TemplateView):
self.object = Course.objects.create( self.object = Course.objects.create(
author=request.user, author=request.user,
) )
#TODO if request.user != self.object.author and request.user.role != User.ADMIN_ROLE:
#if (request.user != self.object.author and request.user.role < User.AUTHOR_ROLE) or request.user.role != User.ADMIN_ROLE: raise Http404
# raise Http404
return super().get(request) return super().get(request)
def get_context_data(self): def get_context_data(self):
@ -283,6 +282,14 @@ class CoursesView(ListView):
filtered = CourseFilter(self.request.GET) filtered = CourseFilter(self.request.GET)
context.update(filtered.data) context.update(filtered.data)
context['course_items'] = Course.shuffle(context.get('course_items')) context['course_items'] = Course.shuffle(context.get('course_items'))
context['ages'] = Course.AGE_CHOICES[1:]
age = context.get('age')
if age and age[0]:
age = int(age[0])
context['age'] = [age]
context['age_name'] = dict(Course.AGE_CHOICES).get(age, '')
else:
context['age_name'] = ''
return context return context
def get_template_names(self): def get_template_names(self):

@ -116,13 +116,15 @@ class Payment(PolymorphicModel):
def add_months(cls, sourcedate, months=1): def add_months(cls, sourcedate, months=1):
result = arrow.get(sourcedate, settings.TIME_ZONE).shift(months=months) result = arrow.get(sourcedate, settings.TIME_ZONE).shift(months=months)
if months == 1: if months == 1:
if (sourcedate.month == 2 and sourcedate.day >= 28) or (sourcedate.day == 31 and result.day <= 30)\ if (sourcedate.month == 2 and sourcedate.day >= 28) or (sourcedate.day == 31 and result.day <= 30) \
or (sourcedate.month == 1 and sourcedate.day >= 29 and result.day == 28): or (sourcedate.month == 1 and sourcedate.day >= 29 and result.day == 28):
result = result.replace(day=1, month=result.month + 1) result = result.replace(day=1, month=result.month + 1)
return result.datetime return result.datetime
@classmethod @classmethod
def calc_amount(cls, payment=None, user=None, course=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 isinstance(payment, CoursePayment): if isinstance(payment, CoursePayment):
course = payment.course course = payment.course
user = payment.user user = payment.user
@ -135,6 +137,7 @@ class Payment(PolymorphicModel):
if hasattr(user, 'referral') and not user.referral.payment: if hasattr(user, 'referral') and not user.referral.payment:
referral_bonus = user.referral.bonus referral_bonus = user.referral.bonus
referrer_bonus = user.referral.referrer_bonus referrer_bonus = user.referral.referrer_bonus
date_start = school_payment.date_start
discount = 0 discount = 0
if course: if course:
price = course.price price = course.price
@ -142,8 +145,8 @@ class Payment(PolymorphicModel):
if user: if user:
school_payments = SchoolPayment.objects.filter( school_payments = SchoolPayment.objects.filter(
user=user, user=user,
date_start__lte=now().date(), date_start__lte=date_start,
date_end__gte=now().date(), date_end__gte=date_start,
status__in=[ status__in=[
Pingback.PINGBACK_TYPE_REGULAR, Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL, Pingback.PINGBACK_TYPE_GOODWILL,
@ -162,7 +165,8 @@ class Payment(PolymorphicModel):
weekday__in=weekdays, weekday__in=weekdays,
) )
if add_days: if add_days:
weekdays_count = weekdays_in_date_range(now().date(), prev_school_payment.date_end) date_end = prev_school_payment.date_end
weekdays_count = weekdays_in_date_range(date_start, prev_school_payment.date_end)
all_weekdays_count = weekdays_in_date_range(prev_school_payment.date_start, prev_school_payment.date_end) all_weekdays_count = weekdays_in_date_range(prev_school_payment.date_start, prev_school_payment.date_end)
for ss in school_schedules: for ss in school_schedules:
price += ss.month_price // all_weekdays_count.get(ss.weekday, 0) * weekdays_count.get( price += ss.month_price // all_weekdays_count.get(ss.weekday, 0) * weekdays_count.get(
@ -182,6 +186,8 @@ class Payment(PolymorphicModel):
'referral_bonus': referral_bonus, 'referral_bonus': referral_bonus,
'referrer_bonus': referrer_bonus, 'referrer_bonus': referrer_bonus,
'discount': discount, 'discount': discount,
'date_start': date_start,
'date_end': date_end,
'weekdays': weekdays, 'weekdays': weekdays,
} }

@ -102,6 +102,8 @@ class SchoolBuyView(TemplateView):
weekdays = set(request.GET.getlist('weekdays', [])) weekdays = set(request.GET.getlist('weekdays', []))
use_bonuses = request.GET.get('use_bonuses') use_bonuses = request.GET.get('use_bonuses')
roistat_visit = request.COOKIES.get('roistat_visit', None) roistat_visit = request.COOKIES.get('roistat_visit', None)
date_start = request.GET.get('date_start')
date_start = date_start and datetime.datetime.strptime(date_start, '%Y-%m-%d') or now().date()
if not weekdays: if not weekdays:
messages.error(request, 'Выберите несколько дней недели.') messages.error(request, 'Выберите несколько дней недели.')
return redirect('school:school') return redirect('school:school')
@ -112,8 +114,8 @@ class SchoolBuyView(TemplateView):
return redirect('school:school') return redirect('school:school')
prev_school_payment = SchoolPayment.objects.filter( prev_school_payment = SchoolPayment.objects.filter(
user=request.user, user=request.user,
date_start__lte=now().date(), date_start__lte=date_start,
date_end__gte=now().date(), date_end__gte=date_start,
add_days=False, add_days=False,
status__in=[ status__in=[
Pingback.PINGBACK_TYPE_REGULAR, Pingback.PINGBACK_TYPE_REGULAR,
@ -126,7 +128,7 @@ class SchoolBuyView(TemplateView):
school_payment = SchoolPayment.objects.create( school_payment = SchoolPayment.objects.create(
user=request.user, user=request.user,
weekdays=weekdays, weekdays=weekdays,
date_start=now().date(), date_start=date_start,
date_end=prev_school_payment.date_end, date_end=prev_school_payment.date_end,
add_days=True, add_days=True,
roistat_visit=roistat_visit, roistat_visit=roistat_visit,
@ -140,6 +142,8 @@ class SchoolBuyView(TemplateView):
user=request.user, user=request.user,
weekdays=weekdays, weekdays=weekdays,
roistat_visit=roistat_visit, roistat_visit=roistat_visit,
date_start=date_start,
date_end=Payment.add_months(date_start),
) )
if use_bonuses: if use_bonuses:
if request.user.bonus >= school_payment.amount: if request.user.bonus >= school_payment.amount:
@ -224,30 +228,7 @@ class PaymentwallCallbackView(View):
product_type_name, product_type_name,
) )
if product_type_name == 'school':
school_payment = SchoolPayment.objects.filter(
user=payment.user,
add_days=False,
date_start__lte=now().date(),
date_end__gte=now().date(),
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
).last()
if school_payment:
if payment.add_days:
date_start = now().date()
date_end = school_payment.date_end
else:
date_start = arrow.get(school_payment.date_end, settings.TIME_ZONE).shift(days=1).datetime
date_end = Payment.add_months(date_start)
else:
date_start = now().date()
date_end = Payment.add_months(date_start)
payment.date_start = date_start
payment.date_end = date_end
if product_type_name == 'course': if product_type_name == 'course':
properties = { properties = {
'payment_id': payment.id, 'payment_id': payment.id,

@ -4,9 +4,6 @@
<div class="online__type">ВИДЕОУРОКИ</div> <div class="online__type">ВИДЕОУРОКИ</div>
<div class="online__title">Каждый день в 17.00 (по Мск) </div> <div class="online__title">Каждый день в 17.00 (по Мск) </div>
<div class="online__text text">Кроме выходных. Запись эфира доступна по завершению трансляции</div> <div class="online__text text">Кроме выходных. Запись эфира доступна по завершению трансляции</div>
<div class="online__action"> <div class="online__action"></div>
<svg class="icon icon-play">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use>
</svg>СМОТРЕТЬ ПРИМЕР ЭФИРА</div>
</div> </div>
</a> </a>

@ -2,19 +2,27 @@
{% load static %} {% load static %}
{% block title %}{{ livelesson.title }} - {{ block.super }}{% endblock title %} {% block title %}{{ livelesson.title }} - {{ block.super }}{% endblock title %}
{% block twurl %}{{ request.build_absolute_uri }}{% endblock twurl %}
{% block ogtitle %}{{ livelesson.title }} - {{ block.super }}{% endblock ogtitle %}
{% block ogurl %}{{ request.build_absolute_uri }}{% endblock ogurl %}
{% if livelesson.cover and livelesson.cover.image %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{{ livelesson.cover.image.url }}{% endblock ogimage %}
{% endif %}
{% block content %} {% block content %}
<div class="section" style="margin-bottom:0;padding-bottom:0"> <div class="section" style="margin-bottom:0;padding-bottom:0">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="lesson"> <div class="lesson">
<div class="lesson__subtitle subtitle">{{ livelesson.title }}</div> <div class="lesson__subtitle subtitle">{{ livelesson.title }}</div>
<div class="lesson__content">{{ livelesson.short_description | linebreaks }}</div> <div class="lesson__content">{{ livelesson.short_description | safe | linebreaks }}</div>
<div class="lesson__video video"> <div class="lesson__video video">
{% if livelesson.stream_index %} {% if livelesson.stream_index %}
<iframe class="lesson__video_frame" src="https://player.vimeo.com/video/{{ livelesson.stream_index }}?autoplay=1" frameborder="0" webkitallowfullscreen <iframe class="lesson__video_frame" src="https://player.vimeo.com/video/{{ livelesson.stream_index }}?autoplay=1" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen> mozallowfullscreen allowfullscreen>
</iframe> </iframe>
<span>Если видео не загрузилось, - уменьшите качество видео или <a href="#" onclick="location.reload();">обновите страницу</a></span> <span>Если видео не загрузилось, - уменьшите качество видео или <a href="#" onclick="location.reload();">обновите страницу</a></span>
<iframe class="lesson__chat_frame" src="https://vimeo.com/live-chat/{{ livelesson.stream_index }}" frameborder="0"></iframe> <!--<iframe class="lesson__chat_frame" src="https://vimeo.com/live-chat/{{ livelesson.stream_index }}" frameborder="0"></iframe>-->
<comments obj-type="live-lesson" obj-id="{{ livelesson.id }}" :is-chat="true"></comments>
{% else %} {% else %}
{% if livelesson.cover %} {% if livelesson.cover %}
<img class="video__pic" src="{{ livelesson.cover.image.url }}"/> <img class="video__pic" src="{{ livelesson.cover.image.url }}"/>

@ -24,7 +24,7 @@
</div> </div>
<div class="lessons__subtitle subtitle">{{ livelesson.date }} // {{ livelesson.title }}</div> <div class="lessons__subtitle subtitle">{{ livelesson.date }} // {{ livelesson.title }}</div>
<div class="lessons__row"> <div class="lessons__row">
<div class="lessons__content">{{ livelesson.short_description | linebreaks }}</div> <div class="lessons__content">{{ livelesson.short_description | safe | linebreaks | truncatechars_html:800 }}</div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}

@ -3,10 +3,7 @@
<div class="online__center center"> <div class="online__center center">
<div class="online__type">ВИДЕОУРОКИ</div> <div class="online__type">ВИДЕОУРОКИ</div>
<div class="online__title">В 17.00 (по Мск) </div> <div class="online__title">В 17.00 (по Мск) </div>
<div class="online__text text">Каждый день с 1 сентября</div> <div class="online__text text">Каждый день</div>
<div class="online__action"> <div class="online__action"></div>
<svg class="icon icon-play">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use>
</svg>СМОТРЕТЬ ПРИМЕР ЭФИРА</div>
</div> </div>
</a> </a>

@ -1,9 +1,5 @@
<a <a
{% if not user.is_authenticated %} data-popup=".js-popup-buy" data-prolong="1" data-date-start="{{ prolong_date_start|date:'Y-m-d' }}"
data-popup=".js-popup-auth"
{% else %}
data-popup=".js-popup-buy"
{% endif %}
class="casing__btn btn{% if pink %} btn_pink{% endif %}" class="casing__btn btn{% if pink %} btn_pink{% endif %}"
href="#" href="#"
>продлить</a> >продлить</a>

@ -5,7 +5,7 @@
<span class="main__bold">Lil School</span> — первая образовательная онлайн-платформа креативного мышления для детей <span class="main__bold">Lil School</span> — первая образовательная онлайн-платформа креативного мышления для детей
</div> </div>
<div class="main__subtitle"> <div class="main__subtitle">
Старт школы - 1 сентября <!--Старт школы - 1 сентября-->
</div> </div>
<div class="main__actions"> <div class="main__actions">
<a <a
@ -29,7 +29,7 @@
</svg> </svg>
</div> </div>
<div class="school__title">Видеоуроки</div> <div class="school__title">Видеоуроки</div>
<div class="school__text">Каждый день с 1 сентября</div> <div class="school__text">Каждый день</div>
</div> </div>
<div class="school__col"> <div class="school__col">
<div class="school__preview"> <div class="school__preview">

@ -5,14 +5,10 @@
<div class="casing"> <div class="casing">
<div class="casing__col"> <div class="casing__col">
<div class="casing__subscribe"> <div class="casing__subscribe">
{% if is_purchased %}
<div class="casing__msg">Подписка истекает <div class="casing__msg">Подписка истекает
<span class="bold">{{ subscription_ends }}</span> <span class="bold">{{ subscription_ends }}</span>
</div> </div>
{% else %} {% if allow_prolong %}
<div class="casing__msg">Подписка
<span class="bold">истекла</span>
</div>
{% include './prolong_btn.html' with pink=True %} {% include './prolong_btn.html' with pink=True %}
{% endif %} {% endif %}
</div> </div>

@ -187,6 +187,7 @@ class SchoolView(TemplateView):
ll.school_schedule = school_schedules_dict.get(ll.date.isoweekday()) ll.school_schedule = school_schedules_dict.get(ll.date.isoweekday())
live_lessons_exists = live_lessons.exists() live_lessons_exists = live_lessons.exists()
live_lessons = live_lessons or None live_lessons = live_lessons or None
subscription_ends = school_payment.filter(add_days=False).last().date_end if school_payment_exists else None
context.update({ context.update({
'online': online, 'online': online,
'live_lessons': live_lessons, 'live_lessons': live_lessons,
@ -199,7 +200,9 @@ class SchoolView(TemplateView):
'school_schedules': school_schedules, 'school_schedules': school_schedules,
'school_schedules_purchased': school_schedules_purchased, 'school_schedules_purchased': school_schedules_purchased,
'school_purchased_future': False, 'school_purchased_future': False,
'subscription_ends': school_payment.filter(add_days=False).first().date_end if school_payment_exists else None, 'subscription_ends': subscription_ends,
'prolong_date_start': subscription_ends + timedelta(days=1) if subscription_ends else None,
'allow_prolong': subscription_ends - date_now <= timedelta(days=14) if subscription_ends else False,
}) })
return context return context

@ -73,13 +73,14 @@
</div> </div>
<div class="tabs__container"> <div class="tabs__container">
<div class="tabs__item js-tabs-item" style="display: block;"> <div class="tabs__item js-tabs-item" style="display: block;">
<div class="courses courses_scroll"> <div class="courses">
<div class="courses__list"> <div class="courses__list">
{% if is_school_purchased %} {% if is_school_purchased %}
<div class="courses__item"> <div class="courses__item">
<a class="courses__preview" href="{% url 'school:school' %}"> <a class="courses__preview" href="{% url 'school:school' %}">
<img class="courses__pic" src="{% static 'img/og_main.jpg' %}" <img class="courses__pic" src="{% static 'img/og_main.jpg' %}"
style="height: 201px; width: 300px; object-fit: cover;" /> style="height: 200px; object-fit: cover;" />
<div class="courses__view">Подробнее</div>
</a> </a>
<div class="courses__details"> <div class="courses__details">
<a class="courses__theme theme">{{ school_purchased_weekdays }}</a> <a class="courses__theme theme">{{ school_purchased_weekdays }}</a>
@ -110,7 +111,7 @@
</div> </div>
{% if is_author %} {% if is_author %}
<div class="tabs__item js-tabs-item"> <div class="tabs__item js-tabs-item">
<div class="courses courses_scroll"> <div class="courses">
<div class="courses__list"> <div class="courses__list">
{% if published.exists %} {% if published.exists %}
{% include "course/course_items.html" with course_items=published %} {% include "course/course_items.html" with course_items=published %}

@ -1,3 +1,4 @@
from django.db.models import Func, F
from django.utils.timezone import now from django.utils.timezone import now
from paymentwall.pingback import Pingback from paymentwall.pingback import Pingback
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -17,7 +18,7 @@ def baner(request):
return {'baner': Baner.objects.filter(use=True).first()} return {'baner': Baner.objects.filter(use=True).first()}
def is_summer_school_purchased(request): def school_purchased(request):
if request.user.is_authenticated: if request.user.is_authenticated:
n = now().date() n = now().date()
school_payment = SchoolPayment.objects.filter( school_payment = SchoolPayment.objects.filter(
@ -30,8 +31,18 @@ def is_summer_school_purchased(request):
date_start__lte=n, date_start__lte=n,
date_end__gte=n date_end__gte=n
) )
return {'is_summer_school_purchased': school_payment.exists()} school_schedules_purchased = school_payment.annotate(
return {'is_summer_school_purchased': False} joined_weekdays=Func(F('weekdays'), function='unnest', )
).values_list('joined_weekdays', flat=True).distinct()
return {
'is_school_purchased': school_payment.exists(),
'school_schedules_purchased': school_schedules_purchased,
}
return {
'is_school_purchased': False,
'school_schedules_purchased': [],
}
def referrer(request): def referrer(request):
referrer = request.session.get('referrer') referrer = request.session.get('referrer')

@ -0,0 +1,14 @@
from django.conf import settings
from pusher import Pusher
def pusher():
try:
pusher_cluster = settings.PUSHER_CLUSTER
except AttributeError:
pusher_cluster = 'mt1'
return Pusher(app_id=settings.PUSHER_APP_ID,
key=settings.PUSHER_KEY,
secret=settings.PUSHER_SECRET,
cluster=pusher_cluster)

@ -95,7 +95,7 @@ TEMPLATES = [
'context_processors': [ 'context_processors': [
'project.context_processors.config', 'project.context_processors.config',
'project.context_processors.baner', 'project.context_processors.baner',
'project.context_processors.is_summer_school_purchased', 'project.context_processors.school_purchased',
'project.context_processors.referrer', 'project.context_processors.referrer',
'project.context_processors.settings', 'project.context_processors.settings',
'django.template.context_processors.debug', 'django.template.context_processors.debug',
@ -200,6 +200,13 @@ TWILIO_FROM_PHONE = os.getenv('TWILIO_FROM_PHONE', '+37128914409')
ACTIVE_LINK_STRICT = True ACTIVE_LINK_STRICT = True
# PUSHER settings
PUSHER_APP_ID = os.getenv('PUSHER_APP_ID', '')
PUSHER_KEY = os.getenv('PUSHER_KEY', '')
PUSHER_SECRET = os.getenv('PUSHER_SECRET', '')
PUSHER_CLUSTER = 'eu'
# DRF settings # DRF settings
REST_FRAMEWORK = { REST_FRAMEWORK = {

@ -13,7 +13,7 @@
</svg> </svg>
</div> </div>
<div class="school__title">Видеоуроки</div> <div class="school__title">Видеоуроки</div>
<div class="school__text">Каждый день с 1 сентября</div> <div class="school__text">Каждый день</div>
</div> </div>
<div class="school__col"> <div class="school__col">
<div class="school__preview"> <div class="school__preview">

@ -11,80 +11,56 @@
</div> </div>
<div class="gallery"> <div class="gallery">
<div class="gallery__grid"> <div class="gallery__grid">
<div <a href="{% get_media_prefix %}instagram/results/0.jpg"
class="gallery__item gallery__item_lg" class="gallery__item gallery__item_lg">
style="background-image: url({% get_media_prefix %}instagram/results/0.jpg);"
>
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/0.jpg" onerror="this.style.display='none'"> <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/0.jpg" onerror="this.style.display='none'">
</div> </a>
<div <a href="{% get_media_prefix %}instagram/results/1.jpg"
class="gallery__item" class="gallery__item">
style="background-image: url({% get_media_prefix %}instagram/results/1.jpg);" <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/1.jpg" onerror="this.style.display='none'">
> </a>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/1.jpg" onerror="this.style.display='none'"> {% endcomment %} <a href="{% get_media_prefix %}instagram/results/2.jpg"
</div> class="gallery__item">
<div <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/2.jpg" onerror="this.style.display='none'">
class="gallery__item" </a>
style="background-image: url({% get_media_prefix %}instagram/results/2.jpg);" <a href="{% get_media_prefix %}instagram/results/3.jpg"
> class="gallery__item">
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/2.jpg" onerror="this.style.display='none'"> {% endcomment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/3.jpg" onerror="this.style.display='none'">
</div> </a>
<div <a href="{% get_media_prefix %}instagram/results/4.jpg"
class="gallery__item" class="gallery__item">
style="background-image: url({% get_media_prefix %}instagram/results/3.jpg);" <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/4.jpg" onerror="this.style.display='none'">
> </a>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/3.jpg" onerror="this.style.display='none'"> {% endcomment %} <a href="{% get_media_prefix %}instagram/results/5.jpg"
</div> class="gallery__item">
<div <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/5.jpg" onerror="this.style.display='none'">
class="gallery__item" </a>
style="background-image: url({% get_media_prefix %}instagram/results/4.jpg);"
>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/4.jpg" onerror="this.style.display='none'"> {% endcomment %}
</div>
<div
class="gallery__item"
style="background-image: url({% get_media_prefix %}instagram/results/5.jpg);"
>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/5.jpg" onerror="this.style.display='none'"> {% endcomment %}
</div>
</div> </div>
<div class="gallery__grid"> <div class="gallery__grid">
<div <a href="{% get_media_prefix %}instagram/results/6.jpg"
class="gallery__item" class="gallery__item">
style="background-image: url({% get_media_prefix %}instagram/results/6.jpg);" <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/6.jpg" onerror="this.style.display='none'">
> </a>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/6.jpg" onerror="this.style.display='none'"> {% endcomment %} <a href="{% get_media_prefix %}instagram/results/7.jpg"
</div> class="gallery__item">
<div <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/7.jpg" onerror="this.style.display='none'">
class="gallery__item" </a>
style="background-image: url({% get_media_prefix %}instagram/results/7.jpg);" <a href="{% get_media_prefix %}instagram/results/8.jpg"
> class="gallery__item">
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/7.jpg" onerror="this.style.display='none'"> {% endcomment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/8.jpg" onerror="this.style.display='none'">
</div> </a>
<div <a href="{% get_media_prefix %}instagram/results/9.jpg"
class="gallery__item" class="gallery__item gallery__item_lg">
style="background-image: url({% get_media_prefix %}instagram/results/8.jpg);" <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/9.jpg" onerror="this.style.display='none'">
> </a>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/8.jpg" onerror="this.style.display='none'"> {% endcomment %} <a href="{% get_media_prefix %}instagram/results/10.jpg"
</div> class="gallery__item">
<div class="gallery__item gallery__item_lg" <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/10.jpg" onerror="this.style.display='none'">
style="background-image: url({% get_media_prefix %}instagram/results/9.jpg);" </a>
<a href="{% get_media_prefix %}instagram/results/11.jpg"
> class="gallery__item">
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/9.jpg" onerror="this.style.display='none'"> {% endcomment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/11.jpg" onerror="this.style.display='none'">
</div> </a>
<div
class="gallery__item"
style="background-image: url({% get_media_prefix %}instagram/results/10.jpg);"
>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/10.jpg" onerror="this.style.display='none'"> {% endcomment %}
</div>
<div
class="gallery__item"
style="background-image: url({% get_media_prefix %}instagram/results/11.jpg);"
>
{% comment %} <img class="gallery__pic" src="{% get_media_prefix %}instagram/results/11.jpg" onerror="this.style.display='none'"> {% endcomment %}
</div>
</div> </div>
</div> </div>
</div> </div>

@ -3,9 +3,8 @@
<div class="section__center center"> <div class="section__center center">
<div class="title title_center">Видео-курсы без расписания</div> <div class="title title_center">Видео-курсы без расписания</div>
<div class="text text_courses">Если вам не совсем удобно заниматься с нами каждый день в нашей онлайн-школе, специально для вас мы <div class="text text_courses">Если вам не совсем удобно заниматься с нами каждый день в нашей онлайн-школе, специально для вас мы
делаем отдельные уроки в записи, которые вы можете проходить когда вам будем удобно.</div> делаем отдельные уроки в записи, которые вы можете проходить когда вам будем удобно.<br><br>
<div class="head"> Учите и развивайте креативное мышление когда и где угодно
<div class="head__text">Учите и развивайте креативное мышление когда и где угодно</div>
</div> </div>
<div class="courses"> <div class="courses">
<div class="courses__list"> <div class="courses__list">

@ -1,11 +1,26 @@
{% load static %} {% load static %}
{% load setting from settings %}
<script> <script>
window.LIL_STORE = { window.LIL_STORE = {
roles: {
USER_ROLE: 0,
AUTHOR_ROLE: 1,
TEACHER_ROLE: 2,
ADMIN_ROLE: 3,
},
pusherKey: '{% setting "PUSHER_KEY" %}',
staticUrl: '{% static "" %}', staticUrl: '{% static "" %}',
accessToken: '{{ request.user.auth_token }}', accessToken: '{{ request.user.auth_token }}',
isMobile: {{ request.user_agent.is_mobile|yesno:"true,false" }}, isMobile: {{ request.user_agent.is_mobile|yesno:"true,false" }},
defaultUserPhoto: "{% static 'img/user_default.jpg' %}",
user: { user: {
id: '{{ request.user.id|default:'' }}', id: '{{ request.user.id|default:'' }}',
role: +'{{ request.user.role }}',
photo: '{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user_default.jpg' %}{% endif %}',
},
components: {},
urls: {
courses: "{% url 'courses' %}"
}, },
flags: { flags: {
referrer: '{{ referrer.id|default:'' }}', referrer: '{{ referrer.id|default:'' }}',

@ -3,10 +3,7 @@
<div class="online__center center"> <div class="online__center center">
<div class="online__type">ВИДЕОУРОКИ</div> <div class="online__type">ВИДЕОУРОКИ</div>
<div class="online__title">В 17.00 (по Мск) </div> <div class="online__title">В 17.00 (по Мск) </div>
<div class="online__text text">Каждый день с 1 сентября</div> <div class="online__text text">Каждый день</div>
<div class="online__action"> <div class="online__action"></div>
<svg class="icon icon-play">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-play"></use>
</svg>СМОТРЕТЬ ПРИМЕР ЭФИРА</div>
</div> </div>
</a> </a>

@ -20,40 +20,6 @@
</div> </div>
<div class="buy__col"> <div class="buy__col">
<div class="buy__list"> <div class="buy__list">
{% if all_school_schedules %}
{% for school_schedule in all_school_schedules %}
<label class="switch switch_lesson">
<input
class="switch__input"
type="checkbox"
data-day="{{school_schedule.weekday}}"
data-price="{{school_schedule.month_price}}"
autocomplete="off"
{% if school_schedule.weekday in school_schedules_purchased %}
disabled
{% endif %}
{% if not is_purchased %}
checked
{% endif %}>
<span class="switch__content">
<span class="switch__cell">{{ school_schedule }}</span>
{% 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.weekday in school_schedules_purchased %}
Куплено
{% else %}
{% if school_schedule.trial_lesson %}
<a class="js-video-modal" data-video-url="{{ school_schedule.trial_lesson }}" href="">Пробный урок</a>
{% endif %}
{% endif %}
</span>
<span class="switch__cell">{{school_schedule.month_price}}р в мес.</span>
</span>
</label>
{% endfor %}
{% else %}
{% for school_schedule in school_schedules %} {% for school_schedule in school_schedules %}
<label class="switch switch_lesson"> <label class="switch switch_lesson">
<input <input
@ -63,6 +29,7 @@
data-price="{{school_schedule.month_price}}" data-price="{{school_schedule.month_price}}"
autocomplete="off" autocomplete="off"
{% if school_schedule.weekday in school_schedules_purchased %} {% if school_schedule.weekday in school_schedules_purchased %}
data-purchased="1"
disabled disabled
{% endif %} {% endif %}
{% if not is_purchased %} {% if not is_purchased %}
@ -86,7 +53,6 @@
</span> </span>
</label> </label>
{% endfor %} {% endfor %}
{% endif %}
</div> </div>
</div> </div>
<div class="buy__col"> <div class="buy__col">
@ -102,6 +68,7 @@
</div> </div>
<div class="order__info"> <div class="order__info">
<div class="order__label">ШКОЛА</div> <div class="order__label">ШКОЛА</div>
<div class="order__dates"></div>
<div class="order__days"></div> <div class="order__days"></div>
{% if request.user.bonuses %} {% if request.user.bonuses %}
<label class="buy__bonuses switch" style="display: none;"> <label class="buy__bonuses switch" style="display: none;">

@ -6,7 +6,7 @@
<div class="lock"> <div class="lock">
<div class="lock__label">ЧТОБЫ ПРОДОЛЖИТЬ ПРОСМОТР</div> <div class="lock__label">ЧТОБЫ ПРОДОЛЖИТЬ ПРОСМОТР</div>
<div class="lock__title title">{{ course.title }}</div> <div class="lock__title title">{{ course.title }}</div>
<div class="lock__content">{{ course.short_description }}</div> <div class="lock__content">{{ course.short_description | safe | linebreaks }}</div>
<a href="{% url 'course-checkout' course.id %}" class="lock__btn btn">КУПИТЬ КУРС</a> <a href="{% url 'course-checkout' course.id %}" class="lock__btn btn">КУПИТЬ КУРС</a>
</div> </div>
</div> </div>

@ -48,7 +48,7 @@
</div> </div>
{% else %} {% else %}
<div class="main__subtitle"> <div class="main__subtitle">
Приглашаем вас присоединиться к онлайн-школе с 1 сентября! Приглашаем вас присоединиться к онлайн-школе
</div> </div>
<div class="main__actions"> <div class="main__actions">
{% if not is_purchased and not is_purchased_future %} {% if not is_purchased and not is_purchased_future %}

@ -316,12 +316,12 @@
</div> </div>
</div> </div>
{% include 'templates/blocks/lil_store_js.html' %} {% include 'templates/blocks/lil_store_js.html' %}
<script type="text/javascript" src={% static "app.js" %}></script>
<script> <script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }}); var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }});
var schoolAmountForDiscount = parseFloat({{ config.SERVICE_DISCOUNT_MIN_AMOUNT }}); var schoolAmountForDiscount = parseFloat({{ config.SERVICE_DISCOUNT_MIN_AMOUNT }});
</script> </script>
{% block foot %}{% endblock foot %} {% block foot %}{% endblock foot %}
<script type="text/javascript" src={% static "app.js" %}></script>
</body> </body>
</html> </html>

@ -56,6 +56,7 @@
powered by <a href="https://www.livechatinc.com/?welcome" rel="noopener" target="_blank">LiveChat</a> powered by <a href="https://www.livechatinc.com/?welcome" rel="noopener" target="_blank">LiveChat</a>
</noscript> </noscript>
<!-- End of LiveChat code --> <!-- End of LiveChat code -->
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>
<script> <script>
var viewportmeta = document.querySelector('meta[name="viewport"]'); var viewportmeta = document.querySelector('meta[name="viewport"]');
if (viewportmeta) { if (viewportmeta) {

@ -9,7 +9,7 @@
{% include "templates/blocks/about.html" %} {% include "templates/blocks/about.html" %}
{% include "templates/blocks/last_courses.html" %} {% include "templates/blocks/last_courses.html" %}
{% include "templates/blocks/gallery.html" %} {% include "templates/blocks/gallery.html" %}
{% include "templates/blocks/game.html" %}
{% include "templates/blocks/teachers.html" %} {% include "templates/blocks/teachers.html" %}
{% include "templates/blocks/game.html" %}
{% include "templates/blocks/partners.html" %} {% include "templates/blocks/partners.html" %}
{% endblock content %} {% endblock content %}

@ -47,14 +47,14 @@ urlpatterns = [
path('author-request/success/', TemplateView.as_view(template_name='user/become-author-success.html'), name='author-request-success'), path('author-request/success/', TemplateView.as_view(template_name='user/become-author-success.html'), name='author-request-success'),
path('courses/', CoursesView.as_view(), name='courses'), path('courses/', CoursesView.as_view(), name='courses'),
path('course/create', CourseEditView.as_view(), name='course_create'), path('course/create', CourseEditView.as_view(), name='course_create'),
path('course/create/lessons', CourseEditView.as_view(), name='course_create_lessons'), path('course/<int:pk>/lessons/new', CourseEditView.as_view(), name='course_lessons_new'),
path('course/create/lessons/new', CourseEditView.as_view(), name='course_create_lessons_new'), path('course/<int:pk>/lessons/<int:lesson>/edit', CourseEditView.as_view(), name='course_lessons_edit'),
path('course/create/lessons/edit/<int:lesson>', CourseEditView.as_view(), name='course_create_lessons_edit'),
path('course/on-moderation', CourseOnModerationView.as_view(), name='course-on-moderation'), path('course/on-moderation', CourseOnModerationView.as_view(), name='course-on-moderation'),
path('course/<int:pk>/', CourseView.as_view(), name='course'), path('course/<int:pk>/', CourseView.as_view(), name='course'),
path('course/<str:slug>/', CourseView.as_view(), name='course'), path('course/<str:slug>/', CourseView.as_view(), name='course'),
path('course/<int:pk>/checkout', CourseBuyView.as_view(), name='course-checkout'), path('course/<int:pk>/checkout', CourseBuyView.as_view(), name='course-checkout'),
path('course/<int:pk>/edit', CourseEditView.as_view(), name='course_edit'), path('course/<int:pk>/edit', CourseEditView.as_view(), name='course_edit'),
path('course/<int:pk>/edit/lessons', CourseEditView.as_view(), name='course_edit_lessons'),
path('course/<int:pk>/lessons', CourseView.as_view(template_name='course/course_only_lessons.html', only_lessons=True), name='course-only-lessons'), path('course/<int:pk>/lessons', CourseView.as_view(template_name='course/course_only_lessons.html', only_lessons=True), name='course-only-lessons'),
path('course/<int:course_id>/like', likes, name='likes'), path('course/<int:course_id>/like', likes, name='likes'),
path('course/<int:course_id>/comment', coursecomment, name='coursecomment'), path('course/<int:course_id>/comment', coursecomment, name='coursecomment'),

@ -83,7 +83,7 @@ class IndexView(TemplateView):
'online': online, 'online': online,
'online_coming_soon': online_coming_soon, 'online_coming_soon': online_coming_soon,
'school_schedule': school_schedule, 'school_schedule': school_schedule,
'course_items': Course.shuffle(Course.objects.filter(status=Course.PUBLISHED)[:6]), 'course_items': Course.shuffle(Course.objects.filter(status=Course.PUBLISHED)[:3]),
'is_purchased': school_payment_exists, 'is_purchased': school_payment_exists,
'min_school_price': SchoolSchedule.objects.aggregate(Min('month_price'))['month_price__min'], 'min_school_price': SchoolSchedule.objects.aggregate(Min('month_price'))['month_price__min'],
'school_schedules': SchoolSchedule.objects.all(), 'school_schedules': SchoolSchedule.objects.all(),

@ -30,5 +30,5 @@ user-agents==1.1.0
ua-parser==0.8.0 ua-parser==0.8.0
django-ipware django-ipware
django-imagekit django-imagekit
short_url pusher==2.0.1
short_url

@ -0,0 +1,71 @@
<template>
<div>
<div v-if="! comment.deactivated_at">
<a class="questions__anchor" :id="'question__' + comment.id"></a>
<div :id="'question__replyto__' + comment.id" :class="{'questions__item_reply': comment.parent && ! controller.isChat}" class="questions__item">
<div v-if="comment.author.photo" class="questions__ava ava">
<img class="ava__pic" :src="comment.author.photo">
</div>
<div v-if="! comment.author.photo" class="questions__ava ava">
<img class="ava__pic" :src="$root.store.defaultUserPhoto">
</div>
<div class="questions__wrap">
<div class="questions__details">
<div class="questions__head">
<span class="questions__author">{{ comment.author.first_name }} {{ comment.author.last_name }}</span>
<span class="questions__date">{{ comment.created_at_humanize }}</span>
</div>
<div class="questions__content">
<svg v-if="isHeart" class="icon questions__heart"><use xlink:href="/static/img/sprite.svg#icon-like"></use></svg>
<span v-if="! isHeart">{{ comment.content }}</span>
</div>
</div>
<div class="questions__foot">
<button @click="controller.reply(comment)" v-if="$root.store.user.id && ! controller.isChat" class="questions__action question__reply-button">ОТВЕТИТЬ</button>
<button @click="controller.remove(comment)"
v-if="$root.store.user.id == comment.author.id || $root.store.user.role == $root.store.roles.ADMIN_ROLE"
class="questions__action question__reply-button">
<span v-if="! controller.isChat">УДАЛИТЬ</span>
<span v-if="controller.isChat">
<svg class="icon questions__delete-icon"><use xlink:href="/static/img/sprite.svg#icon-delete"></use></svg>
</span>
</button>
</div>
</div>
</div>
</div>
<comment-form v-if="$root.store.user.id && !controller.$data.isChat && controller.$data.replyTo && controller.$data.replyTo.id == comment.id"
:controller="controller"></comment-form>
<ul v-if="comment.children" v-for="(node, index) in comment.children" :key="index">
<li>
<comment v-if="! node.deactivated_at" :controller="controller" :comment="node"></comment>
</li>
</ul>
</div>
</template>
<script>
import CommentForm from './CommentForm';
export default {
name: 'comment',
props: ['controller', 'comment',],
computed: {
isHeart(){
return this.comment.content === '❤';
},
},
mounted(){
this.controller.flatComments[this.comment.id] = this.comment;
},
components: {
CommentForm
}
}
</script>

@ -0,0 +1,53 @@
<template>
<div class="questions__form" :class="{'questions__item_reply': controller.$data.replyTo}">
<div class="questions__form-loader loading-loader"></div>
<div class="questions__ava ava">
<img class="ava__pic" :src="$root.store.user.photo || $root.store.defaultUserPhoto">
</div>
<div class="questions__wrap">
<div class="questions__field">
<textarea v-model="content" class="questions__textarea" @keyup.enter.exact="addOnEnter"
:placeholder="controller.$data.replyTo ? 'Ответ на комментарий' : 'Ваш комментарий или вопрос'"></textarea>
</div>
<div class="questions__form-foot">
<button v-if="controller.isChat" class="questions__btn"
@click="controller.addHeart"><svg class="icon questions__heart"><use xlink:href="/static/img/sprite.svg#icon-like"></use></svg></button>
<button class="questions__btn" :class="{'btn btn_light': ! controller.isChat}" @click="add">
<span :class="{'mobile-hide': controller.isChat }">ОТПРАВИТЬ</span>
<span class="mobile-show" v-if="controller.isChat">
<svg class="icon questions__send-icon"><use xlink:href="/static/img/sprite.svg#icon-plus"></use></svg>
</span>
</button>
<button v-show="! controller.isChat && controller.$data.replyTo" class="questions__btn" @click="controller.cancelReply">
<span>ОТМЕНИТЬ</span>
</button>
</div>
</div>
</div>
</template>
<script>
import {api} from "../js/modules/api";
export default {
name: 'comment-form',
props: ['controller',],
data() {
return {
content: '',
}
},
methods: {
addOnEnter() {
if(this.controller.isChat) {
this.add();
}
},
add() {
this.controller.add(this.content);
this.content = '';
},
}
}
</script>

@ -0,0 +1,134 @@
<template>
<div class="questions" :class="{'questions--chat': isChat, 'questions--loading': loading}">
<div v-show="nodes.length" class="questions__items">
<ul v-for="(node, index) in nodes" :key="index">
<li>
<comment v-if="! node.deactivated_at" :comment="node" :controller="controller" v-on:remove="remove"></comment>
</li>
</ul>
</div>
<comment-form v-if="$root.store.user.id && ! replyTo" :controller="controller"></comment-form>
</div>
</template>
<script type="text/javascript">
import Comment from './Comment';
import CommentForm from './CommentForm';
import {api} from "../js/modules/api";
export default {
name: 'comments',
props: ['objType', 'objId', 'isChat'],
data() {
return {
loading: false,
replyTo: null,
nodes: [],
controller: this,
flatComments: {},
}
},
methods: {
reply(comment) {
this.replyTo = comment;
},
cancelReply(){
this.replyTo = null;
},
addHeart(){
this.add('❤');
},
add(content){
let vm = this;
this.loading = true;
let request = api.addObjComment(this.objId, this.objType, {
content: content,
author: this.$root.store.user.id,
parent: this.replyTo && this.replyTo.id,
});
request.then((response) => {
vm.loading = false;
vm.onAdd(response.data);
if(vm.replyTo){
vm.cancelReply();
}
}).catch(() => {
vm.loading = false;
});
},
remove(comment){
if(! confirm('Удалить комментарий?')){
return;
}
let vm = this;
this.loading = true;
let request = api.removeObjComment(comment.id);
request.then((response) => {
vm.loading = false;
vm.onRemove(comment);
});
},
onAdd(comment){
if(this.flatComments[comment.id]){
return;
}
const method = this.isChat ? 'push' : 'unshift';
if(comment.parent){
this.flatComments[comment.parent].children[method](comment);
}
else{
this.nodes[method](comment);
}
this.flatComments[comment.id] = comment;
},
onRemove(comment){
let comments = [];
if(comment.parent){
comments = this.flatComments[comment.parent].children;
}
else{
comments = this.nodes;
}
let index = comments.findIndex((c) => +c.id === +comment.id);
if(index === -1){
return;
}
comments.splice(index, 1);
delete this.flatComments[comment.id];
},
connectToPusher(){
let vm = this;
// Enable pusher logging - don't include this in production
Pusher.logToConsole = true;
let pusher = new Pusher(this.$root.store.pusherKey, {
cluster: 'eu',
encrypted: true
});
let channel = pusher.subscribe('comments_' + this.objType + '_' + this.objId);
channel.bind('add', this.onAdd);
channel.bind('delete', this.onRemove);
}
},
mounted() {
let vm = this;
this.loading = true;
let request = api.getObjComments(this.objId, this.objType, this.isChat ? 'update_at' : '-update_at');
request
.then((response) => {
vm.loading = false;
vm.nodes = response.data;
vm.connectToPusher();
})
.catch(() => {
vm.loading = false;
});
},
components: {
Comment,
CommentForm
}
}
</script>

@ -66,45 +66,7 @@
<div class="section__center center"> <div class="section__center center">
<div class="kit"> <div class="kit">
<div class="kit__body"> <div class="kit__body">
<vue-draggable v-model="contest.content" @start="drag=true" @end="drag=false" :options="{ handle: '.sortable__handle' }"> <block-content :content.sync="contest.content"></block-content>
<div v-for="(block, index) in contest.content" :key="block.data.id ? block.data.id : block.data.guid">
<block-text v-if="block.type === 'text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image-text v-if="block.type === 'image-text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image v-if="block.type === 'image'"
:index="index"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-images v-if="block.type === 'images'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:images.sync="block.data.images"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-video v-if="block.type === 'video'"
:index="index"
:title.sync="block.data.title"
v-on:remove="onBlockRemoved"
:video-url.sync="block.data.video_url"/>
</div>
</vue-draggable>
<block-add v-on:added="onBlockAdded"/>
</div> </div>
</div> </div>
</div> </div>
@ -113,15 +75,9 @@
</template> </template>
<script> <script>
import BlockText from './blocks/BlockText'
import BlockImage from './blocks/BlockImage'
import BlockImages from './blocks/BlockImages'
import BlockImageText from './blocks/BlockImageText'
import BlockVideo from './blocks/BlockVideo'
import BlockAdd from "./blocks/BlockAdd";
import {api} from "../js/modules/api"; import {api} from "../js/modules/api";
import DatePicker from 'vuejs-datepicker'; import DatePicker from 'vuejs-datepicker';
import Draggable from 'vuedraggable'; import BlockContent from './blocks/BlockContent'
import slugify from 'slugify'; import slugify from 'slugify';
import {required, minValue, numeric, url } from 'vuelidate/lib/validators' import {required, minValue, numeric, url } from 'vuelidate/lib/validators'
import _ from 'lodash'; import _ from 'lodash';
@ -175,18 +131,6 @@
} }
}, },
methods: { methods: {
onBlockRemoved(blockIndex) {
const blockToRemove = this.contest.content[blockIndex];
// Удаляем блок из Vue
this.contest.content.splice(blockIndex, 1);
// Если блок уже был записан в БД, отправляем запрос на сервер на удаление блока из БД
if (blockToRemove.data.id) {
api.removeContentBlock(blockToRemove, this.accessToken);
}
},
onBlockAdded(blockData) {
this.contest.content.push(blockData);
},
onTitleInput() { onTitleInput() {
this.$v.contest.title.$touch(); this.$v.contest.title.$touch();
if (!this.slugChanged) { if (!this.slugChanged) {
@ -219,7 +163,7 @@
id: data.id, id: data.id,
title: data.title, title: data.title,
description: data.description, description: data.description,
content: api.convertContentResponse(data.content), content: api.convertContentJson(data.content),
date_start: data.date_start, date_start: data.date_start,
date_end: data.date_end, date_end: data.date_end,
slug: data.slug, slug: data.slug,
@ -252,70 +196,7 @@
data.date_start = data.date_start ? moment(data.date_start).format('MM-DD-YYYY') : null; data.date_start = data.date_start ? moment(data.date_start).format('MM-DD-YYYY') : null;
data.date_end = data.date_end ? moment(data.date_end).format('MM-DD-YYYY') : null; data.date_end = data.date_end ? moment(data.date_end).format('MM-DD-YYYY') : null;
data.cover = this.contest.coverImageId || ''; data.cover = this.contest.coverImageId || '';
data.content = this.contest.content.map((block, index) => { data.content = api.convertContentJson(this.contest.content, true);
if (block.type === 'text') {
return {
'type': 'text',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'txt': block.data.text,
}
}
} else if (block.type === 'image') {
return {
'type': 'image',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'img': block.data.image_id,
}
}
} else if (block.type === 'image-text') {
return {
'type': 'image-text',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'img': block.data.image_id,
'txt': block.data.text,
}
}
} else if (block.type === 'images') {
return {
'type': 'images',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'images': block.data.images.map((galleryImage) => {
return {
'id': galleryImage.id ? galleryImage.id : null,
'img': galleryImage.img,
}
}),
}
}
} else if (block.type === 'video') {
return {
'type': 'video',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'url': block.data.video_url,
}
}
}
});
const request = this.contest.id const request = this.contest.id
? api.put(`/api/v1/contests/${this.contest.id}/`, data, { ? api.put(`/api/v1/contests/${this.contest.id}/`, data, {
@ -343,15 +224,8 @@
} }
}, },
components: { components: {
BlockAdd, BlockContent,
'vue-datepicker': DatePicker, 'vue-datepicker': DatePicker,
'block-text': BlockText,
'block-image': BlockImage,
'block-image-text': BlockImageText,
'block-images': BlockImages,
'block-video': BlockVideo,
'vue-draggable': Draggable,
} }
}; };
</script> </script>

@ -37,10 +37,8 @@
v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }"> v-bind:class="{ error: ($v.course.short_description.$dirty || showErrors) && $v.course.short_description.$invalid }">
<div class="field__label">{{titles.shortDescription}}</div> <div class="field__label">{{titles.shortDescription}}</div>
<div class="field__wrap"> <div class="field__wrap">
<textarea class="field__textarea" <vue-redactor :value="course.short_description"
v-autosize="course.short_description" v-on:update:value="(value) => { this.course.short_description = value; }" />
@input="$v.course.short_description.$touch()"
v-model="course.short_description"></textarea>
</div> </div>
</div> </div>
</div> </div>
@ -49,7 +47,7 @@
v-bind:class="{ error: ($v.course.category.$dirty || showErrors) && $v.course.category.$invalid }"> v-bind:class="{ error: ($v.course.category.$dirty || showErrors) && $v.course.category.$invalid }">
<div class="field__label field__label_gray">КАТЕГОРИЯ</div> <div class="field__label field__label_gray">КАТЕГОРИЯ</div>
<div class="field__wrap"> <div class="field__wrap">
<lil-select :value.sync="categorySelect" :options="categoryOptions" <lil-select :value.sync="course.category" :options="categoryOptions"
placeholder="Выберите категорию"/> placeholder="Выберите категорию"/>
</div> </div>
</div> </div>
@ -113,6 +111,13 @@
<button disabled class="field__append">руб.</button> <button disabled class="field__append">руб.</button>
</div> </div>
</div> </div>
<div v-if="!live" class="info__field field">
<div class="field__label field__label_gray">ВОЗРАСТ</div>
<div class="field__wrap">
<lil-select :value.sync="course.age" :options="ages" value-key="value"
placeholder="Выберите возраст"/>
</div>
</div>
<label v-if="me && !live && me.role === ROLE_ADMIN" class="info__switch switch switch_lg"> <label v-if="me && !live && me.role === ROLE_ADMIN" class="info__switch switch switch_lg">
<input type="checkbox" class="switch__input" v-model="course.is_featured"> <input type="checkbox" class="switch__input" v-model="course.is_featured">
<span class="switch__content">Выделить</span> <span class="switch__content">Выделить</span>
@ -166,45 +171,7 @@
</button> </button>
</div> </div>
<div v-if="viewSection === 'course'" class="kit__body"> <div v-if="viewSection === 'course'" class="kit__body">
<vue-draggable v-model="course.content" @start="drag=true" @end="drag=false" :options="{ handle: '.sortable__handle' }"> <block-content :content.sync="course.content"></block-content>
<div v-for="(block, index) in course.content" :key="block.data.id ? block.data.id : block.data.guid">
<block-text v-if="block.type === 'text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image-text v-if="block.type === 'image-text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image v-if="block.type === 'image'"
:index="index"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-images v-if="block.type === 'images'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:images.sync="block.data.images"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-video v-if="block.type === 'video'"
:index="index"
:title.sync="block.data.title"
v-on:remove="onBlockRemoved"
:video-url.sync="block.data.video_url"/>
</div>
</vue-draggable>
<block-add v-on:added="onBlockAdded"/>
<!--<div class="kit__foot"> <!--<div class="kit__foot">
<button type="submit" class="kit__submit btn btn_md" v-bind:class="{ loading: courseSaving }"> <button type="submit" class="kit__submit btn btn_md" v-bind:class="{ loading: courseSaving }">
@ -236,7 +203,7 @@
</div> </div>
<div class="lessons__subtitle subtitle">{{ lesson.title }}</div> <div class="lessons__subtitle subtitle">{{ lesson.title }}</div>
<div class="lessons__row"> <div class="lessons__row">
<div class="lessons__content">{{ lesson.short_description | linebreaks }}</div> <div class="lessons__content" v-html="lesson.short_description"></div>
</div> </div>
</div> </div>
</vue-draggable> </vue-draggable>
@ -269,15 +236,11 @@
import { ROLE_ADMIN, ROLE_AUTHOR } from './consts' import { ROLE_ADMIN, ROLE_AUTHOR } from './consts'
import LinkInput from './inputs/LinkInput' import LinkInput from './inputs/LinkInput'
import DatePicker from 'vuejs-datepicker' import DatePicker from 'vuejs-datepicker'
import BlockText from './blocks/BlockText' import BlockContent from './blocks/BlockContent'
import BlockImage from './blocks/BlockImage' import VueRedactor from './redactor/VueRedactor';
import BlockImages from './blocks/BlockImages'
import BlockImageText from './blocks/BlockImageText'
import BlockVideo from './blocks/BlockVideo'
import LilSelect from "./inputs/LilSelect"; import LilSelect from "./inputs/LilSelect";
import LessonRedactor from "./LessonRedactor"; import LessonRedactor from "./LessonRedactor";
import {api} from "../js/modules/api"; import {api} from "../js/modules/api";
import BlockAdd from "./blocks/BlockAdd";
import $ from 'jquery'; import $ from 'jquery';
import {required, minValue, numeric, url } from 'vuelidate/lib/validators' import {required, minValue, numeric, url } from 'vuelidate/lib/validators'
import slugify from 'slugify'; import slugify from 'slugify';
@ -312,6 +275,7 @@
duration: null, duration: null,
author: null, author: null,
price: null, price: null,
age: 0,
url: '', url: '',
coverImage: '', coverImage: '',
kit__body: null, kit__body: null,
@ -376,6 +340,40 @@
'value': '18:00', 'value': '18:00',
} }
], ],
ages: [
{
'title': 'Любой возраст',
'value': 0,
},
{
'title': 'до 5',
'value': 1,
},
{
'title': '5-7',
'value': 2,
},
{
'title': '7-9',
'value': 3,
},
{
'title': '9-12',
'value': 4,
},
{
'title': '12-15',
'value': 5,
},
{
'title': '15-18',
'value': 6,
},
{
'title': 'от 18',
'value': 7,
},
],
weekdays: [ weekdays: [
'', '',
@ -484,27 +482,6 @@
this.course.url = slugify(this.course.title); this.course.url = slugify(this.course.title);
} }
}, },
updateCategory() {
if (this.categoryOptions && Array.isArray(this.categoryOptions) && this.course.category) {
this.categoryOptions.forEach((category) => {
if (category.id === this.course.category) {
this.course.categorySelect = category;
}
});
}
},
onBlockRemoved(blockIndex) {
const blockToRemove = this.course.content[blockIndex];
// Удаляем блок из Vue
this.course.content.splice(blockIndex, 1);
// Если блок уже был записан в БД, отправляем запрос на сервер на удаление блока из БД
if (blockToRemove.data.id) {
api.removeContentBlock(blockToRemove, this.accessToken);
}
},
onBlockAdded(blockData) {
this.course.content.push(blockData);
},
removeLesson(lessonIndex) { removeLesson(lessonIndex) {
if (!confirm('Вы действительно хотите удалить этот урок?')) { if (!confirm('Вы действительно хотите удалить этот урок?')) {
return; return;
@ -518,18 +495,18 @@
}, },
editLesson(lessonIndex) { editLesson(lessonIndex) {
this.currentLesson = this.lessons[lessonIndex]; this.currentLesson = this.lessons[lessonIndex];
history.push("/course/create/lessons/edit/"+this.currentLesson.id); history.push(`/course/${this.course.id}/lessons/${this.currentLesson.id}/edit`);
this.viewSection = 'lessons-edit'; this.viewSection = 'lessons-edit';
}, },
showCourse() { showCourse() {
if (this.viewSection !== 'course') { if (this.viewSection !== 'course') {
history.push("/course/create"); history.push(this.course.id ? `/course/${this.course.id}/edit` : "/course/create");
} }
this.viewSection = 'course' this.viewSection = 'course'
}, },
showLessons() { showLessons() {
if (this.viewSection !== 'lessons') { if (this.viewSection !== 'lessons') {
history.push("/course/create/lessons"); history.push(`/course/${this.course.id}/edit/lessons`);
} }
this.viewSection = 'lessons'; this.viewSection = 'lessons';
}, },
@ -540,7 +517,7 @@
content: [], content: [],
}; };
if (this.viewSection !== 'lessons-edit') { if (this.viewSection !== 'lessons-edit') {
history.push("/course/create/lessons/new"); history.push(`/course/${this.course.id}/lessons/new`);
} }
this.viewSection = 'lessons-edit'; this.viewSection = 'lessons-edit';
window.scrollTo(0, 0); window.scrollTo(0, 0);
@ -613,7 +590,7 @@
return req; return req;
}, },
goToLessons() { goToLessons() {
history.push("/course/create/lessons"); history.push(`/course/${this.course.id}/edit/lessons`);
this.viewSection = 'lessons'; this.viewSection = 'lessons';
this.$nextTick(() => { this.$nextTick(() => {
const elementTop = $('#course-redactor__nav').position().top - 130; const elementTop = $('#course-redactor__nav').position().top - 130;
@ -868,18 +845,17 @@
}, },
updateViewSection(location, action) { updateViewSection(location, action) {
//console.log('updateViewSection[action]', action); //console.log('updateViewSection[action]', action);
if (location.pathname === '/course/create/lessons') { if (location.pathname.match(/course\/\d+\/edit\/lessons/)) {
this.viewSection = 'lessons'; this.viewSection = 'lessons';
} else if (location.pathname === '/course/create') { } else if (location.pathname.match(/course\/\d+\/lessons\/new/)){
this.viewSection = 'course';
} else if (location.pathname === '/course/create/lessons/new') {
this.viewSection = 'lessons-edit'; this.viewSection = 'lessons-edit';
} else if (location.pathname.indexOf('/course/create/lessons/edit') !== -1) { } else if (location.pathname.match(/course\/\d+\/lessons\/\d+\/edit/)) {
let lessonId = parseInt(location.pathname.split('/').pop()); // let lessonId = parseInt(location.pathname.split('/').pop());
//console.log('lessonId', lessonId, this.lessons.toString()); const lessonId = +location.pathname.match(/lessons\/(\d+)\/edit/)[1];
//console.log('lessod edit', this.lessons.find((i)=>{return i.id === lessonId}));
this.currentLesson = this.lessons.find((i)=>{return i.id === lessonId}); this.currentLesson = this.lessons.find((i)=>{return i.id === lessonId});
this.viewSection = 'lessons-edit'; this.viewSection = 'lessons-edit';
} else {
this.viewSection = 'course';
} }
}, },
onLessonsChanged() { onLessonsChanged() {
@ -941,14 +917,8 @@
promises.push(cats); promises.push(cats);
cats.then((response) => { cats.then((response) => {
if (response.data) { if (response.data) {
this.categoryOptions = response.data.results.map((category) => { this.categoryOptions = response.data.results;
return {
title: category.title,
value: category.id
}
});
} }
this.updateCategory();
}); });
if(this.live) { if(this.live) {
@ -988,7 +958,6 @@
this.scheduleOptions = _.orderBy(options, (item)=>{return moment(item.value)}); this.scheduleOptions = _.orderBy(options, (item)=>{return moment(item.value)});
} }
this.updateCategory();
}); });
} }
@ -1059,23 +1028,6 @@
this.course.price = value || 0; this.course.price = value || 0;
} }
}, },
categorySelect: {
get() {
if (!this.categoryOptions || this.categoryOptions.length === 0 || !this.course || !this.course.category) {
return null;
}
let value;
this.categoryOptions.forEach((category) => {
if (category.value === this.course.category) {
value = category;
}
});
return value;
},
set(value) {
this.course.category = value.value;
}
},
// userSelect: { // userSelect: {
// get() { // get() {
// if (!this.users || this.users.length === 0 || !this.course || !this.course.author) { // if (!this.users || this.users.length === 0 || !this.course || !this.course.author) {
@ -1127,19 +1079,14 @@
}, },
components: { components: {
BlockAdd,
LessonRedactor, LessonRedactor,
LilSelect, LilSelect,
BlockText, BlockContent,
'link-input': LinkInput, 'link-input': LinkInput,
'vue-datepicker': DatePicker, 'vue-datepicker': DatePicker,
'block-text': BlockText,
'block-image': BlockImage,
'block-image-text': BlockImageText,
'block-images': BlockImages,
'block-video': BlockVideo,
'lesson-redactor': LessonRedactor, 'lesson-redactor': LessonRedactor,
'vue-draggable': Draggable, 'vue-draggable': Draggable,
'vue-redactor': VueRedactor,
} }
} }
</script> </script>
@ -1240,9 +1187,10 @@
overflow: scroll; overflow: scroll;
} }
.field_short_description { .field_short_description .redactor-box {
overflow-x: visible;
overflow-y: auto;
max-height: 200px; max-height: 200px;
overflow: scroll;
} }
</style> </style>

@ -27,50 +27,14 @@
<div class="kit__field field" <div class="kit__field field"
v-bind:class="{ error: $v.currentLesson.short_description.$invalid }"> v-bind:class="{ error: $v.currentLesson.short_description.$invalid }">
<div class="field__wrap"> <div class="field__wrap">
<textarea class="field__input" v-autosize="lesson.short_description" placeholder="Описание урока" v-model="lesson.short_description"></textarea> <vue-redactor :value="lesson.short_description"
v-on:update:value="(value) => { this.lesson.short_description = value; }" placeholder="Описание урока"/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<vue-draggable v-model="lesson.content" @start="drag=true" @end="drag=false" :options="{ handle: '.sortable__handle' }"> <block-content :content.sync="lesson.content"></block-content>
<div v-for="(block, index) in lesson.content">
<block-text v-if="block.type === 'text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image-text v-if="block.type === 'image-text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image v-if="block.type === 'image'"
:index="index"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-images v-if="block.type === 'images'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:images.sync="block.data.images"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-video v-if="block.type === 'video'"
:index="index"
:title.sync="block.data.title"
v-on:remove="onBlockRemoved"
:video-url.sync="block.data.video_url"/>
</div>
</vue-draggable>
<block-add v-on:added="onBlockAdded" />
<div class="kit__foot"> <div class="kit__foot">
<button class="kit__submit btn btn_md" v-bind:class="{ loading: saving }">Сохранить</button> <button class="kit__submit btn btn_md" v-bind:class="{ loading: saving }">Сохранить</button>
@ -81,13 +45,9 @@
</template> </template>
<script> <script>
import BlockAdd from "./blocks/BlockAdd"; import BlockContent from './blocks/BlockContent'
import BlockText from './blocks/BlockText'
import BlockImage from './blocks/BlockImage'
import BlockImages from './blocks/BlockImages'
import BlockImageText from './blocks/BlockImageText'
import BlockVideo from './blocks/BlockVideo'
import LilImage from "./blocks/Image" import LilImage from "./blocks/Image"
import VueRedactor from './redactor/VueRedactor';
import {api} from "../js/modules/api"; import {api} from "../js/modules/api";
import Draggable from 'vuedraggable'; import Draggable from 'vuedraggable';
import _ from 'lodash' import _ from 'lodash'
@ -99,20 +59,6 @@
goBack() { goBack() {
this.$emit('back'); this.$emit('back');
}, },
onBlockAdded(blockData) {
this.lesson.content.push(blockData);
this.$emit('update:lesson', this.lesson);
},
onBlockRemoved(blockIndex) {
const blockToRemove = this.lesson.content[blockIndex];
// Удаляем блок из Vue
this.lesson.content.splice(blockIndex, 1);
this.$emit('update:lesson', this.lesson);
// Если блок уже был записан в БД, отправляем запрос на сервер на удаление блока из БД
if (blockToRemove.data.id) {
api.removeContentBlock(blockToRemove, this.accessToken);
}
},
onUpdateCoverUrl(newValue) { onUpdateCoverUrl(newValue) {
this.lesson.coverImage = newValue; this.lesson.coverImage = newValue;
}, },
@ -126,14 +72,9 @@
} }
}, },
components: { components: {
BlockAdd, BlockContent,
'block-text': BlockText,
'block-image': BlockImage,
'block-image-text': BlockImageText,
'block-images': BlockImages,
'block-video': BlockVideo,
'vue-draggable': Draggable,
'lil-image': LilImage, 'lil-image': LilImage,
'vue-redactor': VueRedactor,
} }
} }
</script> </script>

@ -0,0 +1,93 @@
<template>
<div>
<vue-draggable :list="content" @start="drag=true" @end="drag=false" :options="{ handle: '.sortable__handle' }">
<div v-for="(block, index) in content" :key="block.id ? block.id : block.uuid">
<block-text v-if="block.type === 'text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
v-on:remove="onBlockRemoved"/>
<block-image-text v-if="block.type === 'image-text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_thumbnail_url"
v-on:remove="onBlockRemoved"
:access-token="$root.store.accessToken"/>
<block-image v-if="block.type === 'image'"
:index="index"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_thumbnail_url"
v-on:remove="onBlockRemoved"
:access-token="$root.store.accessToken"/>
<block-images v-if="block.type === 'images'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:images.sync="block.data.images"
v-on:remove="onBlockRemoved"
:access-token="$root.store.accessToken"/>
<block-video v-if="block.type === 'video'"
:index="index"
:title.sync="block.data.title"
v-on:remove="onBlockRemoved"
:video-url.sync="block.data.video_url"/>
</div>
</vue-draggable>
<block-add v-on:added="onBlockAdded"/>
</div>
</template>
<script>
import {api} from "../../js/modules/api";
import Draggable from 'vuedraggable';
import BlockText from './BlockText'
import BlockImage from './BlockImage'
import BlockImages from './BlockImages'
import BlockImageText from './BlockImageText'
import BlockVideo from './BlockVideo'
import BlockAdd from "./BlockAdd"
export default {
name: 'block-content',
props: ['content'],
methods: {
onBlockRemoved(blockIndex) {
const remove = () => {
// Удаляем блок из Vue
content.splice(blockIndex, 1);
this.$emit('update:content', content);
}
const content = this.content;
const blockToRemove = this.content[blockIndex];
// Если блок уже был записан в БД, отправляем запрос на сервер на удаление блока из БД
if (blockToRemove.data.id) {
api.removeContentBlock(blockToRemove, this.$root.store.accessToken).then(response => {
remove();
});
}
else {
remove();
}
},
onBlockAdded(blockData) {
const content = this.content;
content.push(blockData);
this.$emit('update:content', content);
},
},
components: {
BlockAdd,
'block-text': BlockText,
'block-image': BlockImage,
'block-image-text': BlockImageText,
'block-images': BlockImages,
'block-video': BlockVideo,
'vue-draggable': Draggable,
}
}
</script>

@ -23,7 +23,7 @@
</div> </div>
<div class="kit__gallery"> <div class="kit__gallery">
<div class="kit__preview" v-for="(image, index) in images" v-bind:class="{ 'kit__preview--loading': image.loading }"> <div class="kit__preview" v-for="(image, index) in images" v-bind:class="{ 'kit__preview--loading': image.loading }">
<img :src="image.src" class="kit__pic"> <img :src="image.image_thumbnail_url" class="kit__pic">
<button type="button" @click="onRemoveImage(index)"> <button type="button" @click="onRemoveImage(index)">
<svg class="icon icon-delete"> <svg class="icon icon-delete">
<use xlink:href="/static/img/sprite.svg#icon-delete"></use> <use xlink:href="/static/img/sprite.svg#icon-delete"></use>
@ -66,12 +66,12 @@
api.uploadImage(reader.result, this.accessToken) api.uploadImage(reader.result, this.accessToken)
.then((response) => { .then((response) => {
let images = this.images; let images = this.images;
console.log('images before', JSON.stringify(images));
images.forEach((image, index) => { images.forEach((image, index) => {
if (image.src === reader.result) { if (image.src === reader.result) {
images[index].img = response.data.id; images[index].image_id = response.data.id;
images[index].loading = false; images[index].loading = false;
images[index].src = response.data.image; images[index].image_url = response.data.image;
images[index].image_thumbnail_url = response.data.image_thumbnail;
} }
}); });
console.log('images after', JSON.stringify(images)); console.log('images after', JSON.stringify(images));
@ -91,12 +91,14 @@
}, },
onRemoveImage(index) { onRemoveImage(index) {
let images = this.images; let images = this.images;
let id = images[index].img; let id = images[index].image_id;
images.splice(index, 1);
this.$emit('update:images', images);
api.removeImage(id, this.accessToken); api.removeImage(id, this.accessToken)
.then(response => {
images.splice(index, 1);
this.$emit('update:images', images);
});
} }
} }
} }
</script> </script>

@ -18,6 +18,7 @@
data() { data() {
return { return {
loading: false, loading: false,
} }
}, },
methods: { methods: {
@ -45,7 +46,7 @@
.then((response) => { .then((response) => {
this.loading = false; this.loading = false;
this.$emit('update:imageId', response.data.id); this.$emit('update:imageId', response.data.id);
this.$emit('update:imageUrl', response.data.image); this.$emit('update:imageUrl', response.data.image_thumbnail);
}) })
.catch((error) => { .catch((error) => {
this.loading = false; this.loading = false;

@ -4,8 +4,9 @@
{{ selectedTitle }} {{ selectedTitle }}
</div> </div>
<div class="select__drop"> <div class="select__drop">
<div v-for="option in options" :class="{ select__option: true, active: value && option.value == value.value }" @click.stop.prevent="selectOption(option)"> <div v-for="option in options" :class="{ select__option: true, active: option[vk] == selectedValue }"
<div class="select__title">{{ option.title }}</div> @click.stop.prevent="selectOption(option)">
<div class="select__title">{{ option[tk] }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -14,11 +15,14 @@
<script> <script>
export default { export default {
name: "lil-select", name: "lil-select",
props: ["options", "value", "valueKey", "placeholder"], props: ["options", "value", "titleKey", "valueKey", "isObj", "placeholder"],
data() { data() {
return { return {
key: this.valueKey ? this.valueKey : 'title', tk: this.titleKey || 'title',
vk: this.valueKey || 'id',
isOpened: false, isOpened: false,
selected: null,
optionsDict: {},
} }
}, },
methods: { methods: {
@ -30,22 +34,30 @@
}, },
selectOption(option) { selectOption(option) {
this.isOpened = !this.isOpened; this.isOpened = !this.isOpened;
this.$emit('update:value', option); this.$emit('update:value', this.isObj ? option : option[this.vk]);
this.selected = option;
} }
}, },
mounted() { mounted() {
document.addEventListener("click", this.clickListener); document.addEventListener("click", this.clickListener);
this.options.forEach((option) => {
this.optionsDict[option[this.vk]] = option;
});
this.selected = this.optionsDict[this.selectedValue];
}, },
destroyed() { destroyed() {
document.removeEventListener("click", this.clickListener); document.removeEventListener("click", this.clickListener);
}, },
computed: { computed: {
isSelected() { isSelected() {
return this.value && this.value[this.key] ? true : false; return !!this.selected;
},
selectedValue() {
return this.isObj ? this.value && this.value[this.vk] : this.value;
}, },
selectedTitle() { selectedTitle() {
if (this.isSelected) { if (this.isSelected) {
return this.value[this.key]; return this.selected[this.tk];
} }
return this.placeholder ? this.placeholder : ''; return this.placeholder ? this.placeholder : '';
} }
@ -55,4 +67,4 @@
<style scoped> <style scoped>
</style> </style>

@ -7,7 +7,7 @@
import './redactor-full.js'; import './redactor-full.js';
export default { export default {
props: ['value', 'placeholder'], props: ['value', 'placeholder', 'air', 'buttonsHide'],
name: "vue-redactor", name: "vue-redactor",
data() { data() {
return { return {
@ -18,7 +18,7 @@
const me = this; const me = this;
$(me.$refs.input).redactor({ $(me.$refs.input).redactor({
air: true, air: true,
buttonsHide: ['image', 'format'], buttonsHide: this.buttonsHide || ['image', 'format'],
lang: 'ru', lang: 'ru',
placeholder: this.placeholder, placeholder: this.placeholder,
callbacks: { callbacks: {

@ -15,6 +15,7 @@ import "./modules/tabs";
import "./modules/popup"; import "./modules/popup";
import "./modules/courses"; import "./modules/courses";
import "./modules/comments"; import "./modules/comments";
import "./modules/comments";
import "./modules/password-show"; import "./modules/password-show";
import "./modules/profile"; import "./modules/profile";
import "./modules/notification"; import "./modules/notification";
@ -24,17 +25,29 @@ import "../sass/app.sass";
import Vue from 'vue'; import Vue from 'vue';
import Vuelidate from 'vuelidate'; import Vuelidate from 'vuelidate';
import VueAutosize from '../components/directives/autosize'
import Comments from '../components/Comments';
import UploadContestWork from '../components/UploadContestWork.vue'; import UploadContestWork from '../components/UploadContestWork.vue';
import ContestWorks from '../components/ContestWorks.vue'; import ContestWorks from '../components/ContestWorks.vue';
import Likes from '../components/blocks/Likes.vue'; import Likes from '../components/blocks/Likes.vue';
Vue.use(Vuelidate); Vue.use(Vuelidate);
Vue.use(VueAutosize);
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
// Enable vue-devtools // Enable vue-devtools
Vue.config.devtools = true; Vue.config.devtools = true;
} }
const components = {
UploadContestWork,
ContestWorks,
Likes,
Comments,
};
Object.assign(components, window.LIL_STORE.components);
const app = new Vue({ const app = new Vue({
el: '#lilcity-vue-app', el: '#lilcity-vue-app',
data() { data() {
@ -42,9 +55,5 @@ const app = new Vue({
store: window.LIL_STORE, store: window.LIL_STORE,
} }
}, },
components: { components: components
UploadContestWork,
ContestWorks,
Likes,
}
}); });

@ -1,19 +1,4 @@
import Vue from 'vue'
import Vuelidate from 'vuelidate'
import VueAutosize from '../components/directives/autosize'
import ContestRedactor from '../components/ContestRedactor.vue' import ContestRedactor from '../components/ContestRedactor.vue'
if (process.env.NODE_ENV === 'development') { window.LIL_STORE.components['contest-redactor'] = ContestRedactor;
// Enable vue-devtools
Vue.config.devtools = true;
}
Vue.use(VueAutosize);
Vue.use(Vuelidate);
let app = new Vue({
el: '#lilcity-vue-app',
components: {
'contest-redactor': ContestRedactor,
}
});

@ -1,24 +1,8 @@
import Vue from 'vue'
import VueAutosize from '../components/directives/autosize'
import Vuelidate from 'vuelidate'
import 'babel-polyfill' import 'babel-polyfill'
import CourseRedactor from '../components/CourseRedactor.vue' import CourseRedactor from '../components/CourseRedactor.vue'
import $ from 'jquery'; import $ from 'jquery';
if (process.env.NODE_ENV === 'development') { window.LIL_STORE.components['course-redactor'] = CourseRedactor;
// Enable vue-devtools
Vue.config.devtools = true;
}
Vue.use(VueAutosize);
Vue.use(Vuelidate);
let app = new Vue({
el: '#lilcity-vue-app',
components: {
'course-redactor': CourseRedactor,
}
});
$(document).ready(function () { $(document).ready(function () {
$('#course-redactor__publish-button').on('click', function () { $('#course-redactor__publish-button').on('click', function () {
@ -29,4 +13,4 @@ $(document).ready(function () {
let event = new Event('course_preview'); let event = new Event('course_preview');
document.getElementById('lilcity__course-redactor').dispatchEvent(event); document.getElementById('lilcity__course-redactor').dispatchEvent(event);
}); });
}); });

@ -108,6 +108,7 @@ export const api = {
short_description: courseObject.short_description, short_description: courseObject.short_description,
category: courseObject.category, category: courseObject.category,
price: courseObject.is_paid && courseObject.price || 0, price: courseObject.is_paid && courseObject.price || 0,
age: courseObject.age,
deferred_start_at: deferredStart, deferred_start_at: deferredStart,
duration: courseObject.duration || 0, duration: courseObject.duration || 0,
is_featured: courseObject.is_featured, is_featured: courseObject.is_featured,
@ -116,72 +117,9 @@ export const api = {
stream: courseObject.stream, stream: courseObject.stream,
cover: courseObject.coverImageId ? courseObject.coverImageId : null, cover: courseObject.coverImageId ? courseObject.coverImageId : null,
gallery: { gallery: {
gallery_images: courseObject.gallery && courseObject.gallery.images ? courseObject.gallery.images : [] gallery_images: courseObject.gallery && courseObject.gallery.images || []
}, },
content: courseObject.content.map((block, index) => { content: api.convertContentJson(courseObject.content, true),
if (block.type === 'text') {
return {
'type': 'text',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'txt': block.data.text,
}
}
} else if (block.type === 'image') {
return {
'type': 'image',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'img': block.data.image_id,
}
}
} else if (block.type === 'image-text') {
return {
'type': 'image-text',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'img': block.data.image_id,
'txt': block.data.text,
}
}
} else if (block.type === 'images') {
return {
'type': 'images',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'images': block.data.images.map((galleryImage) => {
return {
'id': galleryImage.id ? galleryImage.id : null,
'img': galleryImage.img,
}
}),
}
}
} else if (block.type === 'video') {
return {
'type': 'video',
'data': {
'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index,
'title': block.data.title,
'url': block.data.video_url,
}
}
}
}),
}; };
if(courseObject.live) { if(courseObject.live) {
@ -209,16 +147,70 @@ export const api = {
short_description: lessonObject.short_description, short_description: lessonObject.short_description,
course: lessonObject.course_id, course: lessonObject.course_id,
position: lessonObject.position, position: lessonObject.position,
content: lessonObject.content.map((block, index) => { content: api.convertContentJson(lessonObject.content, true),
};
if (isAdding) {
return api.addLesson(lessonJson, accessToken);
} else {
return api.updateLesson(lessonObject.id, lessonJson, accessToken);
}
},
convertLessonJson: (lessonJSON) => {
return {
id: lessonJSON.id,
title: lessonJSON.title,
short_description: lessonJSON.short_description,
coverImageId: lessonJSON.cover && lessonJSON.cover.id ? lessonJSON.cover.id : null,
coverImage: lessonJSON.cover && lessonJSON.cover.image ? lessonJSON.cover.image : null,
content: api.convertContentJson(lessonJSON.content),
position: lessonJSON.position,
}
},
convertCourseJson: (courseJSON) => {
let isDeferred = false;
let deferredDate = false;
let deferredTime = '';
if (courseJSON.deferred_start_at) {
let deferredDateTime = moment(courseJSON.deferred_start_at);
isDeferred = true;
deferredDate = deferredDateTime.format('MM-DD-YYYY');
deferredTime = deferredDateTime.format('HH:mm');
}
return {
id: courseJSON.id,
title: courseJSON.title,
status: courseJSON.status,
short_description: courseJSON.short_description,
category: courseJSON.category && courseJSON.category.id ? courseJSON.category.id : courseJSON.category,
author: courseJSON.author && courseJSON.author.id ? courseJSON.author.id : courseJSON.author,
price: parseFloat(courseJSON.price),
is_paid: parseFloat(courseJSON.price) > 0,
is_deferred: isDeferred,
date: deferredDate || courseJSON.date,
time: deferredTime ? {title: deferredTime, value: deferredTime} : null,
duration: courseJSON.duration,
is_featured: courseJSON.is_featured,
url: courseJSON.slug,
stream: courseJSON.stream,
coverImageId: courseJSON.cover && courseJSON.cover.id ? courseJSON.cover.id : null,
coverImage: courseJSON.cover && courseJSON.cover.image ? courseJSON.cover.image : null,
content: api.convertContentJson(courseJSON.content),
gallery: {images: (courseJSON.gallery) ? courseJSON.gallery.gallery_images:[]},
}
},
convertContentJson: (contentJson, forSaving) => {
if(forSaving){
return contentJson.map((block, index) => {
if (block.type === 'text') { if (block.type === 'text') {
return { return {
'type': 'text', 'type': 'text',
'data': { 'data': {
'id': block.data.id ? block.data.id : null, 'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index, 'position': ++index,
'title': block.data.title, 'title': block.data.title,
'txt': block.data.text, 'txt': block.data.text,
'uuid': block.uuid,
} }
} }
} else if (block.type === 'image') { } else if (block.type === 'image') {
@ -226,10 +218,10 @@ export const api = {
'type': 'image', 'type': 'image',
'data': { 'data': {
'id': block.data.id ? block.data.id : null, 'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index, 'position': ++index,
'title': block.data.title, 'title': block.data.title,
'img': block.data.image_id, 'img': block.data.image_id,
'uuid': block.uuid,
} }
} }
} else if (block.type === 'image-text') { } else if (block.type === 'image-text') {
@ -237,11 +229,11 @@ export const api = {
'type': 'image-text', 'type': 'image-text',
'data': { 'data': {
'id': block.data.id ? block.data.id : null, 'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index, 'position': ++index,
'title': block.data.title, 'title': block.data.title,
'img': block.data.image_id, 'img': block.data.image_id,
'txt': block.data.text, 'txt': block.data.text,
'uuid': block.uuid,
} }
} }
} else if (block.type === 'images') { } else if (block.type === 'images') {
@ -249,15 +241,15 @@ export const api = {
'type': 'images', 'type': 'images',
'data': { 'data': {
'id': block.data.id ? block.data.id : null, 'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index, 'position': ++index,
'title': block.data.title, 'title': block.data.title,
'images': block.data.images.map((galleryImage) => { 'images': block.data.images.map((galleryImage) => {
return { return {
'id': galleryImage.id ? galleryImage.id : null, 'id': galleryImage.id ? galleryImage.id : null,
'img': galleryImage.img, 'img': galleryImage.image_id,
} }
}), }),
'uuid': block.uuid,
} }
} }
} else if (block.type === 'video') { } else if (block.type === 'video') {
@ -265,66 +257,15 @@ export const api = {
'type': 'video', 'type': 'video',
'data': { 'data': {
'id': block.data.id ? block.data.id : null, 'id': block.data.id ? block.data.id : null,
'uuid': block.uuid,
'position': ++index, 'position': ++index,
'title': block.data.title, 'title': block.data.title,
'url': block.data.video_url, 'url': block.data.video_url,
'uuid': block.uuid,
} }
} }
} }
}), });
};
if (isAdding) {
return api.addLesson(lessonJson, accessToken);
} else {
return api.updateLesson(lessonObject.id, lessonJson, accessToken);
}
},
convertLessonJson: (lessonJSON) => {
return {
id: lessonJSON.id,
title: lessonJSON.title,
short_description: lessonJSON.short_description,
coverImageId: lessonJSON.cover && lessonJSON.cover.id ? lessonJSON.cover.id : null,
coverImage: lessonJSON.cover && lessonJSON.cover.image ? lessonJSON.cover.image : null,
content: api.convertContentResponse(lessonJSON.content),
position: lessonJSON.position,
} }
},
convertCourseJson: (courseJSON) => {
let isDeferred = false;
let deferredDate = false;
let deferredTime = '';
if (courseJSON.deferred_start_at) {
let deferredDateTime = moment(courseJSON.deferred_start_at);
isDeferred = true;
deferredDate = deferredDateTime.format('MM-DD-YYYY');
deferredTime = deferredDateTime.format('HH:mm');
}
return {
id: courseJSON.id,
title: courseJSON.title,
status: courseJSON.status,
short_description: courseJSON.short_description,
category: courseJSON.category && courseJSON.category.id ? courseJSON.category.id : courseJSON.category,
author: courseJSON.author && courseJSON.author.id ? courseJSON.author.id : courseJSON.author,
price: parseFloat(courseJSON.price),
is_paid: parseFloat(courseJSON.price) > 0,
is_deferred: isDeferred,
date: deferredDate || courseJSON.date,
time: deferredTime ? {title: deferredTime, value: deferredTime} : null,
duration: courseJSON.duration,
is_featured: courseJSON.is_featured,
url: courseJSON.slug,
stream: courseJSON.stream,
coverImageId: courseJSON.cover && courseJSON.cover.id ? courseJSON.cover.id : null,
coverImage: courseJSON.cover && courseJSON.cover.image ? courseJSON.cover.image : null,
content: api.convertContentResponse(courseJSON.content),
gallery: {images: (courseJSON.gallery) ? courseJSON.gallery.gallery_images:[]},
}
},
convertContentResponse: (contentJson) => {
return contentJson.sort((a, b) => { return contentJson.sort((a, b) => {
if (a.position < b.position) { if (a.position < b.position) {
return -1; return -1;
@ -353,6 +294,7 @@ export const api = {
'title': contentItem.title, 'title': contentItem.title,
'image_id': (contentItem.img) ? contentItem.img.id:null, 'image_id': (contentItem.img) ? contentItem.img.id:null,
'image_url': (contentItem.img) ? contentItem.img.image:null, 'image_url': (contentItem.img) ? contentItem.img.image:null,
'image_thumbnail_url': (contentItem.img) ? contentItem.img.image_thumbnail:null,
} }
} }
} else if (contentItem.type === 'image-text') { } else if (contentItem.type === 'image-text') {
@ -364,6 +306,7 @@ export const api = {
'title': contentItem.title, 'title': contentItem.title,
'image_id': (contentItem.img) ? contentItem.img.id:null, 'image_id': (contentItem.img) ? contentItem.img.id:null,
'image_url': (contentItem.img) ? contentItem.img.image:null, 'image_url': (contentItem.img) ? contentItem.img.image:null,
'image_thumbnail_url': (contentItem.img) ? contentItem.img.image_thumbnail:null,
'text': contentItem.txt, 'text': contentItem.txt,
} }
} }
@ -377,8 +320,9 @@ export const api = {
'images': contentItem.gallery_images.map((galleryImage) => { 'images': contentItem.gallery_images.map((galleryImage) => {
return { return {
'id': galleryImage.id, 'id': galleryImage.id,
'img': galleryImage.img.id, 'image_id': galleryImage.img.id,
'src': galleryImage.img.image, 'image_url': galleryImage.img.image,
'image_thumbnail_url': galleryImage.img.image_thumbnail,
} }
}), }),
} }
@ -509,5 +453,31 @@ export const api = {
'Authorization': `Token ${window.LIL_STORE.accessToken}`, 'Authorization': `Token ${window.LIL_STORE.accessToken}`,
} }
}); });
},
getObjComments: (objId, objType, ordering) => {
return api.get('/api/v1/obj-comments/', {
params: {
obj_id: objId,
obj_type: objType,
ordering: ordering || '',
}
});
},
addObjComment: (objId, objType, commentJson) => {
let data = commentJson;
data.obj_id = objId;
data.obj_type = objType;
return api.post('/api/v1/obj-comments/', data, {
headers: {
'Authorization': `Token ${window.LIL_STORE.accessToken}`,
}
});
},
removeObjComment: (commentId) => {
return api.delete(`/api/v1/obj-comments/${commentId}/`, {
headers: {
'Authorization': `Token ${window.LIL_STORE.accessToken}`,
}
});
} }
}; };

@ -62,3 +62,4 @@ $(document).ready(function () {
form.find('.questions__reply-info').hide(); form.find('.questions__reply-info').hide();
} }
}); });

@ -12,9 +12,12 @@ moment.locale('ru');
const history = createHistory(); const history = createHistory();
$(document).ready(function () { $(document).ready(function () {
const currentCategory = $('div.js-select-option.active[data-category-option]').attr('data-category-name'); const categoryName = $('div.js-select-option.active[data-category-option]').attr('data-category-name');
if(currentCategory) { let category = $('div.js-select-option.active[data-category-option]').attr('data-category');
$('.js-select[data-category-select] .js-select-head').text(currentCategory); let age = $('div.js-select-option.active[data-age-option]').data('age');
let page = 1;
if(category) {
$('.js-select[data-category-select] .js-select-head').text(categoryName);
} }
// Обработчик отложенных курсов // Обработчик отложенных курсов
setInterval(() => { setInterval(() => {
@ -29,17 +32,30 @@ $(document).ready(function () {
// Обработчик кнопки "Подгрузить еще" // Обработчик кнопки "Подгрузить еще"
$('.courses').on('click', 'button.load__btn', function () { $('.courses').on('click', 'button.load__btn', function () {
load_courses($(this).attr('data-next-page-url'), false); page = $(this).attr('data-next-page');
loadCourses();
}); });
// Обработчик выбора категории // Обработчик выбора категории
$('div.js-select-option[data-category-option]').on('click', function (e) { $('div.js-select-option[data-category-option]').on('click', function (e) {
e.preventDefault(); e.preventDefault();
const currentCategory = $(this).attr('data-category-name'); const categoryName = $(this).attr('data-category-name');
$('[data-category-name]').removeClass('active'); $('[data-category-name]').removeClass('active');
$(`[data-category-name='${currentCategory}']`).addClass('active'); $(`[data-category-name='${categoryName}']`).addClass('active');
history.replace($(this).attr('data-category-url')); category = $(this).attr('data-category');
load_courses($(this).attr('data-category-url'), true); page = 1;
loadCourses(true);
});
// Обработчик выбора возраста
$('div.js-select-option[data-age-option]').on('click', function (e) {
e.preventDefault();
const currentAge = $(this).attr('data-age-name');
$('[data-age-name]').removeClass('active');
$(`[data-age-name='${currentAge}']`).addClass('active');
age = $(this).attr('data-age');
page = 1;
loadCourses(true);
}); });
// Обработчик лайков // Обработчик лайков
@ -95,42 +111,50 @@ $(document).ready(function () {
} }
}); });
}) })
});
function load_courses(coursesUrl, fromStart) { function loadCourses(replaceHistory) {
$('.courses__list').css('opacity', '0.9'); $('.courses__list').css('opacity', '0.9');
const buttonElement = $('.courses').find('button.load__btn'); const buttonElement = $('.courses').find('button.load__btn');
if (!fromStart) { let coursesUrl = window.LIL_STORE.urls.courses + '?' + $.param({
buttonElement.addClass('loading'); category: category,
} age: age,
$.ajax(coursesUrl, { });
method: 'GET' if (page > 1) {
}) buttonElement.addClass('loading');
.done(function (data) { }
if (data.success === true) { else{
if (!fromStart) { history.replace(coursesUrl);
$('.courses__list').append(data.content); }
} else { coursesUrl += `&page=${page}`;
$('.courses__list').html(data.content); $.ajax(coursesUrl, {
method: 'GET'
})
.done(function (data) {
if (data.success === true) {
if (page > 1) {
$('.courses__list').append(data.content);
} else {
$('.courses__list').html(data.content);
}
if (data.next_url) {
buttonElement.attr('data-next-page-url', data.next_url);
buttonElement.show();
} else {
buttonElement.hide()
}
} }
if (data.next_url) { })
buttonElement.attr('data-next-page-url', data.next_url); .fail(function (xhr) {
buttonElement.show(); if (xhr.status === 404) {
} else { // Нет результатов, скрываем кнопку
buttonElement.hide() buttonElement.hide();
} }
} })
}) .always(function () {
.fail(function (xhr) { $('.courses__list').css('opacity', '1');
if (xhr.status === 404) { if (buttonElement) {
// Нет результатов, скрываем кнопку buttonElement.removeClass('loading');
buttonElement.hide(); }
} });
}) }
.always(function () { });
$('.courses__list').css('opacity', '1');
if (buttonElement) {
buttonElement.removeClass('loading');
}
});
}

@ -1,6 +1,9 @@
import $ from 'jquery'; import $ from 'jquery';
import moment from 'moment';
import {api} from './api'; import {api} from './api';
moment.locale('ru');
var selectedWeekdays = {}; var selectedWeekdays = {};
$(document).ready(function () { $(document).ready(function () {
$(".js-video-modal").each(function(){ $(".js-video-modal").each(function(){
@ -46,21 +49,12 @@ $(document).ready(function () {
popup = $(data); popup = $(data);
showPopup(); showPopup();
let is_extend = false;
if(data === '.js-popup-buy') { if(data === '.js-popup-buy') {
console.log('reset selected'); console.log('reset selected');
popup.data('date-start', $this.data('date-start') || '');
popup.data('day', $this.data('day') || '');
$('[data-day]').prop('checked', false); $('[data-day]').prop('checked', false);
if ($this.text() === 'продлить') {
//data-purchased
//restore purchased selection
console.log('restore purchased');
$('[data-purchased]').each(function(){
$('[data-day='+$(this).data('purchased')+']').prop('checked', true);
});
is_extend = true;
}
if(! window.LIL_STORE.user.id) { if(! window.LIL_STORE.user.id) {
const $btn = popup.find('.buy__btn'); const $btn = popup.find('.buy__btn');
$btn.click(function(event) { $btn.click(function(event) {
@ -72,7 +66,30 @@ $(document).ready(function () {
}); });
}); });
} }
if ($this.data('prolong')) {
$('[data-day][data-purchased]').each(function(){
$(this).prop('checked', true).prop('disabled', false);
});
}
else{
if($this.data('day')) {
let day = $this.data('day');
$('[data-day][data-purchased]').each(function(){
$(this).prop('checked', false).prop('disabled', true);
});
$('[data-day='+day+']').prop('checked', true);
}
else{
$('[data-day]').each(function(){
$(this).prop('checked', true).prop('disabled', false);
});
}
}
updateCart();
} }
if( data === '.js-popup-auth') { if( data === '.js-popup-auth') {
let nextUrl = $this.data('auth-next-url'); let nextUrl = $this.data('auth-next-url');
if(nextUrl === 'href') { if(nextUrl === 'href') {
@ -80,20 +97,6 @@ $(document).ready(function () {
} }
popup.data('next-url', nextUrl); popup.data('next-url', nextUrl);
} }
if($this.data('day')) {
let day = $this.data('day');
$('[data-day='+day+']').prop('checked', true);
}
if(!is_extend && !$this.data('day')) {
console.log('check all');
$('[data-day]').each(function(){
$(this).prop('checked', true);
});
}
updateCart();
}); });
$('.js-popup-close').on('click', function(e){ $('.js-popup-close').on('click', function(e){
@ -151,7 +154,10 @@ $(document).ready(function () {
}); });
function updateCart(){ function updateCart(){
var link = $('.but_btn_popup').data('link');
var $orderPrice = $('.order_price_text'); var $orderPrice = $('.order_price_text');
var $orderDates = $('.order__dates');
var dateStart = popup.data('date-start');
var days = ['', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье']; var days = ['', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'];
var weekdays = [], daysText = []; var weekdays = [], daysText = [];
var $bonuses = $('.buy__bonuses'); var $bonuses = $('.buy__bonuses');
@ -167,7 +173,7 @@ $(document).ready(function () {
}); });
if(weekdays.length){ if(weekdays.length){
api.getPaymentAmount({ user: window.LIL_STORE.user.id, weekdays: weekdays }) api.getPaymentAmount({ user: window.LIL_STORE.user.id, weekdays: weekdays, date_start: dateStart})
.then((response) => { .then((response) => {
var text = ''; var text = '';
var amount = +response.data.amount; var amount = +response.data.amount;
@ -183,15 +189,17 @@ $(document).ready(function () {
text = amount + 'p.'; text = amount + 'p.';
} }
$orderPrice.html(text); $orderPrice.html(text);
$orderDates.text(moment(response.data.date_start).format('D MMM') + ' - ' + moment(response.data.date_end).format('D MMM'));
$('.but_btn_popup').attr('href', link+'?'+decodeURIComponent($.param({
weekdays: weekdays,
date_start: moment(response.data.date_start).format('YYYY-MM-DD'),
use_bonuses: useBonuses || '',
}, true)));
}); });
} }
else { else {
$orderPrice.html('0p.'); $orderPrice.html('0p.');
} }
$('.order__days').html((daysText.length) ? daysText.join(', '):'Ничего не выбрано');
var link = $('.but_btn_popup').data('link');
link = link+'?'+decodeURIComponent($.param({weekdays: weekdays, use_bonuses: useBonuses || ''}, true));
$('.but_btn_popup').attr('href', link);
} }
}); });

@ -891,11 +891,11 @@ a[name]
font-size: 20px font-size: 20px
.section .section
padding: 60px 0 padding: 50px 0
+t +t
padding: 40px 0
+m
padding: 30px 0 padding: 30px 0
+m
padding: 20px 0
&_review &_review
background: url(../img/bg-elephants.jpg) 0 0 / 100px 102px background: url(../img/bg-elephants.jpg) 0 0 / 100px 102px
&_gray &_gray
@ -1352,6 +1352,8 @@ a[name]
&__pic &__pic
display: block display: block
width: 100% width: 100%
height: 100%
object-fit: cover
.app .app
position: relative position: relative
@ -2456,11 +2458,11 @@ a.grey-link
.go .go
display: flex display: flex
margin-bottom: 50px margin-bottom: 40px
justify-content: space-between justify-content: space-between
align-items: center align-items: center
+t +t
margin-bottom: 30px margin-bottom: 20px
&__item &__item
display: flex display: flex
max-width: calc(50% - 20px) max-width: calc(50% - 20px)
@ -2517,11 +2519,11 @@ a.grey-link
fill: white fill: white
.course .course
margin-bottom: 60px margin-bottom: 50px
&__head &__head
display: flex display: flex
min-height: 40px min-height: 40px
margin-bottom: 30px margin-bottom: 20px
align-items: center align-items: center
justify-content: space-between justify-content: space-between
+t +t
@ -2531,9 +2533,9 @@ a.grey-link
+t +t
margin-bottom: 10px margin-bottom: 10px
&__content &__content
margin-bottom: 40px margin-bottom: 30px
+t +t
margin-bottom: 25px margin-bottom: 15px
&__about &__about
position: relative position: relative
margin-bottom: 40px margin-bottom: 40px
@ -2563,9 +2565,9 @@ a.grey-link
&:active &:active
color: rgba(white,.4) color: rgba(white,.4)
&__user &__user
margin-bottom: 40px margin-bottom: 30px
+t +t
margin-bottom: 30px margin-bottom: 20px
&__info &__info
display: flex display: flex
align-items: center align-items: center
@ -2580,17 +2582,17 @@ a.grey-link
margin: 20px 0 0 margin: 20px 0 0
&__metas &__metas
display: flex display: flex
margin-bottom: 45px margin-bottom: 35px
justify-content: space-between justify-content: space-between
+m +m
display: block display: block
margin-bottom: 25px margin-bottom: 15px
&__metas &__meta &__metas &__meta
+m +m
margin-bottom: 20px margin-bottom: 20px
&__actions &__actions
display: flex display: flex
margin: 0 -10px 50px margin: 0 -10px 40px
&__action &__action
position: relative position: relative
margin: 0 10px margin: 0 10px
@ -2658,6 +2660,8 @@ a.grey-link
visibility: visible visibility: visible
&__subtitle &__subtitle
margin-bottom: 20px margin-bottom: 20px
text-transform: none
letter-spacing: normal
&__row &__row
display: flex display: flex
+m +m
@ -2676,6 +2680,8 @@ a.grey-link
object-fit: cover object-fit: cover
width: 100% width: 100%
height: 100% height: 100%
&__content-wrapper
flex: 1
&__content &__content
flex: 0 0 calc(100% - 165px) flex: 0 0 calc(100% - 165px)
&__actions &__actions
@ -2715,6 +2721,8 @@ a.grey-link
.lesson .lesson
&__subtitle &__subtitle
letter-spacing: normal
text-transform: none
margin-bottom: 10px margin-bottom: 10px
color: #191919 color: #191919
&__content &__content
@ -2811,16 +2819,23 @@ a.grey-link
&__item &__item
display: flex display: flex
&__form &__form
margin-bottom: 40px position: relative;
margin-top: 20px
padding-bottom: 20px padding-bottom: 20px
border-bottom: 1px solid $border border-bottom: 1px solid $border
&__form-loader
display: none;
&__form-foot
text-align: center;
&__item &__item
&:not(:last-child) &:not(:last-child)
margin-bottom: 25px margin-bottom: 25px
+m
padding: 10px 0
&_reply &_reply
padding-left: 80px padding-left: 80px
+m +m
padding: 0 padding: 10px 0
&__reply-info &__reply-info
display: none display: none
margin-bottom: 10px margin-bottom: 10px
@ -2859,11 +2874,13 @@ a.grey-link
+m +m
height: 64px height: 64px
&__btn &__btn
display: block margin: 0 15px;
margin: 0 auto
border-radius: 20px border-radius: 20px
+m
margin-left: 0
&__details &__details
margin-bottom: 5px margin-bottom: 5px
flex: 1
&__head, &__head,
&__action &__action
+fb +fb
@ -2880,11 +2897,76 @@ a.grey-link
&__author &__author
margin-right: 15px margin-right: 15px
&__date &__date
font-size: 10px
display: inline-block display: inline-block
&__foot &__foot
height: 20px
text-align: right text-align: right
&__action &__action
margin-left: auto margin-left: auto
&__heart
fill: #d40700
width: 28px
height: 28px
&__send-icon
fill: $gray
width: 20px
height: 20px
&__delete-icon
fill: #C8C8C8
width: 17px
height: 17px
:hover
fill: #000000
&--heart
&__content
font-size: 24px
color: #d40700
&--chat
margin-top: 15px
&--chat &__items
background: white
padding: 10px
border-radius: 5px
max-height: 400px
overflow: auto
+m
max-height: inherit
&--chat &__item, &--chat &__item_reply
+m
padding: 0
&--chat &__ava
height: 40px
margin-right: 10px
flex: 0 0 40px
+m
display: none
&--chat &__content
margin-bottom: 10px
&--chat &__wrap
display: flex
flex: 0 0 calc(100% - 60px);
+m
flex: 1
&--chat &__field
margin-bottom: 0;
flex: 0 0 calc(100% - 160px);
+m
flex: 1
&--chat &__btn
font-weight: 600;
text-shadow: 1px 1px #fff;
+m
margin: 0 0 0 15px;
&--chat &__form-foot
display: flex;
&--loading &__form-loader
display: block
&--loading &__form &__ava, &--loading &__form &__wrap
opacity: 0.4
.share .share
&__title &__title
@ -3676,6 +3758,11 @@ a.grey-link
+m +m
display: none display: none
.mobile-show
display: none
+m
display: block
.school .school
display: flex display: flex
position: relative position: relative
@ -4118,6 +4205,8 @@ a
font-size: 16px font-size: 16px
padding-bottom: 24px padding-bottom: 24px
text-align: left text-align: left
margin: 0
max-width: 100%
&.pic &.pic
position: relative position: relative
width: 100% width: 100%
@ -4127,6 +4216,24 @@ a
max-width: 100%; max-width: 100%;
max-height: 60%; max-height: 60%;
@media only screen and (max-width: 639px)
.content-block__video iframe
width: 100%;
height: 240px;
.content-block__video.iframe__container
text-align: center;
width: 100%;
padding-bottom: 56.25%;
margin-bottom: -56.25%;
@media only screen and (min-width: 640px)
.content-block__video.iframe__container
text-align: center;
iframe
width: 640px;
height: 360px;
.banner .banner
height: 140px height: 140px

Loading…
Cancel
Save