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

remotes/origin/hotfix/LIL-691
gzbender 7 years ago
commit d182e7ccc3
  1. 69
      api/v1/serializers/course.py
  2. 72
      api/v1/serializers/payment.py
  3. 3
      api/v1/urls.py
  4. 100
      api/v1/views.py
  5. 30
      apps/course/migrations/0044_livelessoncomment.py
  6. 14
      apps/course/models.py
  7. 36
      apps/course/templates/course/lesson.html
  8. 1
      apps/school/models.py
  9. 4
      apps/school/templates/blocks/schedule_item.html
  10. 10
      apps/school/templates/school/livelesson_detail.html
  11. 48
      apps/school/views.py
  12. 12
      apps/user/models.py
  13. 60
      apps/user/templates/user/profile.html
  14. 32
      apps/user/views.py
  15. 14
      project/pusher.py
  16. 7
      project/settings.py
  17. 4
      project/templates/blocks/lil_store_js.html
  18. 1
      project/templates/lilcity/index.html
  19. 2
      requirements.txt
  20. 64
      web/src/components/Comment.vue
  21. 53
      web/src/components/CommentForm.vue
  22. 134
      web/src/components/Comments.vue
  23. 3
      web/src/js/app.js
  24. 29
      web/src/js/modules/api.js
  25. 1
      web/src/js/modules/comments.js
  26. 59
      web/src/js/modules/comments_vue.js
  27. 82
      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,
@ -327,6 +327,7 @@ class CommentSerializer(serializers.ModelSerializer):
'parent', 'parent',
'deactivated_at', 'deactivated_at',
'created_at', 'created_at',
'created_at_humanize',
'update_at', 'update_at',
) )
@ -342,6 +343,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 +377,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)

@ -46,8 +46,29 @@ class AuthorBalanceCreateSerializer(serializers.ModelSerializer):
return AuthorBalanceSerializer(instance, context=self.context).to_representation(instance) return AuthorBalanceSerializer(instance, context=self.context).to_representation(instance)
class PaymentSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Payment
fields = BASE_PAYMENT_FIELDS
read_only_fields = (
'id',
'user',
'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 AuthorBalanceSerializer(serializers.ModelSerializer): class AuthorBalanceSerializer(serializers.ModelSerializer):
author = UserSerializer() author = UserSerializer()
payment = serializers.SerializerMethodField()
class Meta: class Meta:
model = AuthorBalance model = AuthorBalance
@ -70,6 +91,24 @@ class AuthorBalanceSerializer(serializers.ModelSerializer):
'payment', 'payment',
) )
def get_payment(self, instance):
try:
p = instance.payment
except Exception:
return None
data = {
'id': p.id,
'created_at': p.created_at,
'amount': p.amount,
'data': p.data,
}
if isinstance(instance.payment, CoursePayment):
data['course'] = {
'id': p.course.id,
'title': p.course.title,
}
return data
class PaymentSerializer(serializers.ModelSerializer): class PaymentSerializer(serializers.ModelSerializer):
user = UserSerializer() user = UserSerializer()
@ -80,6 +119,7 @@ class PaymentSerializer(serializers.ModelSerializer):
read_only_fields = ( read_only_fields = (
'id', 'id',
'user', 'user',
'data',
'created_at', 'created_at',
'update_at', 'update_at',
) )
@ -91,6 +131,20 @@ class PaymentSerializer(serializers.ModelSerializer):
return SchoolPaymentSerializer(instance, context=self.context).to_representation(instance) 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()
@ -107,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')

@ -11,10 +11,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 +35,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 +55,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 +67,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 +431,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 +448,78 @@ class CommentViewSet(ExtendedModelViewSet):
return queryset return queryset
class ObjectCommentsViewSet(ExtendedModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentCreateSerializer
permission_classes = (IsAuthorObjectOrAdmin,)
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)
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()
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 +527,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 +539,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')

@ -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
@ -240,6 +241,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 +288,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')

@ -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">
@ -114,37 +117,8 @@
<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"> <div class="questions" id="comments_block">
{% if request.user.is_authenticated %} <comments obj-type="lesson" obj-id="{{ lesson.id }}"></comments>
<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>

@ -14,6 +14,7 @@ from apps.payment import models as payment_models
class SchoolSchedule(models.Model): class SchoolSchedule(models.Model):
WEEKDAY_SHORT_NAMES = ('пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс')
WEEKDAY_CHOICES = ( WEEKDAY_CHOICES = (
(1, 'понедельник'), (1, 'понедельник'),
(2, 'вторник'), (2, 'вторник'),

@ -4,7 +4,7 @@
<div class="timing__info"> <div class="timing__info">
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}"> <div class="timing__day{% if school_schedule.is_online %} active{% endif %}">
{{ school_schedule }} {{ school_schedule }}
{% if request.user_agent.is_mobile and school_schedule.trial_lesson %} {% if not is_purchased and request.user_agent.is_mobile and school_schedule.trial_lesson %}
<a class="timing__trial-lesson js-video-modal" href="#" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a> <a class="timing__trial-lesson js-video-modal" href="#" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a>
{% endif %} {% endif %}
</div> </div>
@ -20,7 +20,7 @@
{% else %} {% else %}
{% include './day_pay_btn.html' %} {% include './day_pay_btn.html' %}
{% endif %} {% endif %}
{% if not request.user_agent.is_mobile and school_schedule.trial_lesson %} {% if not is_purchased and not request.user_agent.is_mobile and school_schedule.trial_lesson %}
<a class="timing__trial-lesson js-video-modal" href="#" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a> <a class="timing__trial-lesson js-video-modal" href="#" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a>
{% endif %} {% endif %}
</div> </div>

@ -2,6 +2,13 @@
{% 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">
@ -14,7 +21,8 @@
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 }}"/>

@ -140,21 +140,9 @@ class SchoolView(TemplateView):
pass pass
school_schedules_dict = {ss.weekday: ss for ss in school_schedules} school_schedules_dict = {ss.weekday: ss for ss in school_schedules}
school_schedules_dict[0] = school_schedules_dict.get(7) school_schedules_dict[0] = school_schedules_dict.get(7)
all_schedules_purchased = [] live_lessons = None
live_lessons_exists = False
if self.request.user.is_authenticated: if self.request.user.is_authenticated:
all_schedules_purchased = SchoolPayment.objects.filter(
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
date_start__range=[month_start, date_now],
).annotate(
joined_weekdays=Func(F('weekdays'), function='unnest',)
).distinct().values_list('joined_weekdays', flat=True)
all_schedules_purchased = list(map(lambda x: 1 if x == 7 else x+1, all_schedules_purchased))
school_payment = SchoolPayment.objects.filter( school_payment = SchoolPayment.objects.filter(
user=self.request.user, user=self.request.user,
status__in=[ status__in=[
@ -173,18 +161,32 @@ class SchoolView(TemplateView):
else: else:
school_payment_exists = False school_payment_exists = False
school_schedules_purchased = [] school_schedules_purchased = []
if all_schedules_purchased and is_previous: if is_previous:
live_lessons = LiveLesson.objects.filter( prev_range = [yesterday - timedelta(days=7), yesterday]
date__range=[yesterday - timedelta(days=7), yesterday], live_lessons = []
deactivated_at__isnull=True, # берем все подписки, которые были в периоде
date__week_day__in=all_schedules_purchased, for sp in SchoolPayment.objects.filter(
).order_by('-date') date_start__lte=prev_range[1],
date_end__gte=prev_range[0],
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
):
# берем все уроки в оплаченном промежутке
date_range = [max(sp.date_start, prev_range[0]), min(sp.date_end, prev_range[1])]
live_lessons += LiveLesson.objects.filter(
date__range=date_range,
deactivated_at__isnull=True,
date__week_day__in=list(map(lambda x: 1 if x == 7 else x+1, sp.weekdays)),
).values_list('id', flat=True)
live_lessons = LiveLesson.objects.filter(id__in=set(live_lessons)).order_by('-date')
for ll in live_lessons: for ll in live_lessons:
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()
else: live_lessons = live_lessons or None
live_lessons = None
live_lessons_exists = False
context.update({ context.update({
'online': online, 'online': online,
'live_lessons': live_lessons, 'live_lessons': live_lessons,

@ -92,7 +92,7 @@ class User(AbstractUser):
@property @property
def balance(self): def balance(self):
aggregate = self.balances.filter( income = self.balances.filter(
type=0, type=0,
payment__isnull=False, payment__isnull=False,
payment__status__isnull=False payment__status__isnull=False
@ -100,9 +100,13 @@ class User(AbstractUser):
models.Sum('amount'), models.Sum('amount'),
models.Sum('commission'), models.Sum('commission'),
) )
amount = aggregate.get('amount__sum') or 0 income_amount = income.get('amount__sum') or 0
commission = aggregate.get('commission__sum') or 0 income_commission = income.get('commission__sum') or 0
return amount - commission
payout = self.balances.filter(type=1, status=1).aggregate(models.Sum('amount'))
payout_amount = payout.get('amount__sum') or 0
return income_amount - income_commission - payout_amount
@receiver(post_save, sender=User) @receiver(post_save, sender=User)

@ -64,10 +64,7 @@
<div class="section__center center"> <div class="section__center center">
<div class="tabs js-tabs"> <div class="tabs js-tabs">
<div class="tabs__nav"> <div class="tabs__nav">
<button class="tabs__btn js-tabs-btn active">ОНЛАЙН-ШКОЛА</button> <button class="tabs__btn js-tabs-btn active">МОИ ПОКУПКИ</button>
<button class="tabs__btn js-tabs-btn">ПРИОБРЕТЕННЫЕ
<span class="mobile-hide">КУРСЫ</span>
</button>
{% if is_author %} {% if is_author %}
<button class="tabs__btn js-tabs-btn">ОПУБЛИКОВАННЫЕ <button class="tabs__btn js-tabs-btn">ОПУБЛИКОВАННЫЕ
<span class="mobile-hide">КУРСЫ</span> <span class="mobile-hide">КУРСЫ</span>
@ -76,45 +73,34 @@
</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;">
{% if is_purchased_future %} <div class="courses">
<div class="center center_xs"> <div class="courses__list">
<div class="done"> {% if is_school_purchased %}
<div class="done__title title">Ваша подписка начинается {{school_purchased_future.date_start}}</div> <div class="courses__item">
</div> <a class="courses__preview" href="{% url 'school:school' %}">
</div> <img class="courses__pic" src="{% static 'img/og_main.jpg' %}"
{% else %} style="height: 200px; object-fit: cover;" />
{% if is_purchased %} <div class="courses__view">Подробнее</div>
{% include "blocks/schedule_purchased.html" %} </a>
{% else %} <div class="courses__details">
<div class="center center_xs"> <a class="courses__theme theme">{{ school_purchased_weekdays }}</a>
<div class="done"> <div class="courses__price">{{ school_purchased_price|floatformat:"-2" }}₽</div>
<div class="done__title title">Вы не подписаны на онлайн-школу!</div>
<div class="done__foot">
<a
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% else %}
data-popup=".js-popup-buy"
{% endif %}
href="#"
class="done__btn btn btn_md btn_stroke"
>Купить подписку</a>
</div> </div>
<a class="courses__title">Онлайн-школа&nbsp;{{ school_purchased_dates.0|date:"j b" }}&nbsp;-&nbsp;{{ school_purchased_dates.1|date:"j b" }}</a>
<a class="btn" href="{% url 'school:school' %}">Перейти в онлайн-школу</a>
</div> </div>
</div> {% endif %}
{% endif %}
{% endif %}
</div>
<div class="tabs__item js-tabs-item">
<div class="courses courses_scroll">
<div class="courses__list">
{% if paid.exists %} {% if paid.exists %}
{% include "course/course_items.html" with course_items=paid %} {% include "course/course_items.html" with course_items=paid %}
{% else %} {% endif %}
{% if not is_school_purchased and not paid.exists %}
<div class="center center_xs"> <div class="center center_xs">
<div class="done"> <div class="done">
<div class="done__title title">Нет приобретённых курсов!</div> <div class="done__title">Вы пока ничего не приобрели...</div>
<div class="done__foot"> <div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="{% url 'school:school' %}"
style="margin-bottom: 20px;">Записаться в школу</a>
<a class="done__btn btn btn_md btn_stroke" href="{% url 'courses' %}">Купить курсы</a> <a class="done__btn btn btn_md btn_stroke" href="{% url 'courses' %}">Купить курсы</a>
</div> </div>
</div> </div>
@ -125,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 %}

@ -16,7 +16,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.hashers import check_password, make_password from django.contrib.auth.hashers import check_password, make_password
from django.http import Http404 from django.http import Http404
from django.db.models import F, Func from django.db.models import F, Func, Sum, Min, Max
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.timezone import now from django.utils.timezone import now
@ -55,11 +55,11 @@ class ProfileView(TemplateView):
def get_context_data(self, object): def get_context_data(self, object):
context = super().get_context_data() context = super().get_context_data()
context['user'] = self.request.user context['user'] = self.request.user
context['is_author'] = self.request.user.role == User.AUTHOR_ROLE
context['published'] = Course.objects.filter( context['published'] = Course.objects.filter(
author=self.object, author=self.object,
) )
context['is_author'] = context['published'] or self.request.user.role == User.AUTHOR_ROLE
context['paid'] = Course.objects.filter( context['paid'] = Course.objects.filter(
payments__in=CoursePayment.objects.filter( payments__in=CoursePayment.objects.filter(
user=self.object, user=self.object,
@ -80,22 +80,18 @@ class ProfileView(TemplateView):
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
], ],
) )
school_schedules_purchased = school_payment.annotate( context['is_school_purchased'] = school_payment.exists()
joined_weekdays=Func(F('weekdays'), function='unnest',) if context['is_school_purchased']:
).values_list('joined_weekdays', flat=True).distinct() school_schedules_purchased = school_payment.annotate(
context['school_schedules_purchased'] = school_schedules_purchased joined_weekdays=Func(F('weekdays'), function='unnest',)
context['school_payment'] = school_payment ).values_list('joined_weekdays', flat=True).distinct()
context['is_purchased'] = school_payment.exists() aggregated = school_payment.aggregate(Sum('amount'), Min('date_start'), Max('date_end'),)
context['profile'] = True context['school_purchased_weekdays'] = '-'.join(map(lambda wd: SchoolSchedule.WEEKDAY_SHORT_NAMES[wd-1],
if school_payment.exists() and school_payment.last().date_end: set(sorted(school_schedules_purchased))))
context['subscription_ends'] = school_payment.last().date_end context['school_purchased_price'] = aggregated.get('amount__sum') or 0
context['school_schedules'] = SchoolSchedule.objects.filter( context['school_purchased_dates'] = [aggregated.get('date_start__min'), aggregated.get('date_end__max')]
weekday__in=school_schedules_purchased if school_payment.exists() else [],
).all()
context['all_school_schedules'] = SchoolSchedule.objects.all()
context['is_purchased_future'] = False context['profile'] = True
context['school_purchased_future'] = False
return context return context
@ -107,7 +103,7 @@ class UserView(DetailView):
def get_context_data(self, object): def get_context_data(self, object):
context = super().get_context_data() context = super().get_context_data()
context['published'] = Course.objects.filter( context['published'] = Course.objects.filter(
author=self.object, author=self.object, status=Course.PUBLISHED,
) )
return context return context

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

@ -197,6 +197,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 = {

@ -1,11 +1,15 @@
{% load static %} {% load static %}
{% load setting from settings %}
<script> <script>
window.LIL_STORE = { window.LIL_STORE = {
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:'' }}',
photo: '{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user_default.jpg' %}{% endif %}',
}, },
components: {} components: {}
}; };

@ -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) {

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

@ -0,0 +1,64 @@
<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" v-if="! controller.isChat">
<button @click="controller.reply(comment)" v-if="$root.store.user.id" class="questions__action question__reply-button">ОТВЕТИТЬ</button>
<button @click="controller.remove(comment)" v-if="$root.store.user.id == comment.author.id" class="questions__action question__reply-button">УДАЛИТЬ</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--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>

@ -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";
@ -25,6 +26,7 @@ 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 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';
@ -41,6 +43,7 @@ const components = {
UploadContestWork, UploadContestWork,
ContestWorks, ContestWorks,
Likes, Likes,
Comments,
}; };
Object.assign(components, window.LIL_STORE.components); Object.assign(components, window.LIL_STORE.components);

@ -384,5 +384,34 @@ 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 || '',
},
headers: {
'Authorization': `Token ${window.LIL_STORE.accessToken}`,
}
});
},
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();
} }
}); });

@ -0,0 +1,59 @@
import Vue from 'vue';
import Comments from '../../components/Comments';
//import $ from 'jquery';
if (process.env.NODE_ENV === 'development') {
// Enable vue-devtools
Vue.config.devtools = true;
}
let app = new Vue({
el: '#comments_block',
data(){
return {
userId: 123,
accessToken: 123,
comments: [{
author: {
get_full_name: 'John Doe',
photo: {url: ''},
},
created_at_humanize: '12 07 18',
content: 'content content content content',
id: 1,
is_child_node: false
}, {
author: {
get_full_name: 'Sarah Conor',
photo: {url: ''},
},
created_at_humanize: '5 05 18',
content: 'hasta la vista',
id: 2,
is_child_node: false,
children: [{
author: {
get_full_name: 'John Doe',
photo: {url: ''},
},
created_at_humanize: '12 07 18',
content: 'content content content content',
id: 10,
is_child_node: true
}, {
author: {
get_full_name: 'Sarah Conor',
photo: {url: ''},
},
created_at_humanize: '5 05 18',
content: 'hasta la vista',
id: 20,
is_child_node: true,
}]
}]
}
},
components: {
'comments': Comments,
}
});

@ -1710,6 +1710,8 @@ a.grey-link
color: $cl color: $cl
+t +t
line-height: 1.33 line-height: 1.33
&__theme
text-transform: uppercase
&__user &__user
margin-top: 20px margin-top: 20px
&_two &__item &_two &__item
@ -2809,16 +2811,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
@ -2857,9 +2866,10 @@ 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
&__head, &__head,
@ -2878,11 +2888,70 @@ 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
&--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
@ -3664,6 +3733,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

Loading…
Cancel
Save