diff --git a/api/v1/serializers/course.py b/api/v1/serializers/course.py index 2b7d2806..551830ad 100644 --- a/api/v1/serializers/course.py +++ b/api/v1/serializers/course.py @@ -10,7 +10,7 @@ from apps.course.models import ( Comment, CourseComment, LessonComment, Material, Lesson, Like, -) + LiveLessonComment) from .content import ( ImageObjectSerializer, ContentSerializer, ContentCreateSerializer, GallerySerializer, GalleryImageSerializer, @@ -329,6 +329,7 @@ class CommentSerializer(serializers.ModelSerializer): 'parent', 'deactivated_at', 'created_at', + 'created_at_humanize', 'update_at', ) @@ -344,6 +345,8 @@ class CommentSerializer(serializers.ModelSerializer): 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) class CourseCommentSerializer(serializers.ModelSerializer): @@ -376,3 +379,67 @@ class LessonCommentSerializer(serializers.ModelSerializer): read_only_fields = CommentSerializer.Meta.read_only_fields + ( '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) diff --git a/api/v1/serializers/mixins.py b/api/v1/serializers/mixins.py index 40584503..50e7d19d 100644 --- a/api/v1/serializers/mixins.py +++ b/api/v1/serializers/mixins.py @@ -32,35 +32,23 @@ class DispatchContentMixin(object): if 'id' in cdata and cdata['id']: t = Text.objects.get(id=cdata.pop('id')) serializer = TextCreateSerializer(t, data=cdata) - if serializer.is_valid(): - serializer.save() else: serializer = TextCreateSerializer(data=cdata) - if serializer.is_valid(): - serializer.save() + if serializer.is_valid(): + serializer.save() elif ctype == 'image': if 'id' in cdata and cdata['id']: image = Image.objects.get(id=cdata.pop('id')) 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: serializer = ImageCreateSerializer(data=cdata) - if serializer.is_valid(): - image = serializer.save() - else: - continue + if serializer.is_valid(): + image = serializer.save() + else: + continue + if 'img' in cdata: try: - image_object = ImageObject.objects.get(id=cdata['img']) + image_object = ImageObject.objects.get(id=cdata.get('img')) except ImageObject.DoesNotExist: pass else: @@ -71,25 +59,15 @@ class DispatchContentMixin(object): if 'id' in cdata and cdata['id']: it = ImageText.objects.get(id=cdata.pop('id')) 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: serializer = ImageTextCreateSerializer(data=cdata) - if serializer.is_valid(): - it = serializer.save() - else: - continue + if serializer.is_valid(): + it = serializer.save() + else: + continue + if 'img' in cdata: try: - image_object = ImageObject.objects.get(id=cdata['img']) + image_object = ImageObject.objects.get(id=cdata.get('img')) except ImageObject.DoesNotExist: pass else: @@ -100,52 +78,36 @@ class DispatchContentMixin(object): if 'id' in cdata and cdata['id']: v = Video.objects.get(id=cdata.pop('id')) serializer = VideoCreateSerializer(v, data=cdata) - if serializer.is_valid(): - serializer.save() else: serializer = VideoCreateSerializer(data=cdata) - if serializer.is_valid(): - serializer.save() + if serializer.is_valid(): + serializer.save() elif ctype == 'images': if 'id' in cdata and cdata['id']: g = Gallery.objects.get(id=cdata['id']) g.position = cdata['position'] g.title = cdata['title'] 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: g = Gallery( position=cdata['position'], title=cdata['title'], 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']) - ) + 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']) + ) class DispatchMaterialMixin(object): diff --git a/api/v1/serializers/payment.py b/api/v1/serializers/payment.py index 6268a63d..b388be21 100644 --- a/api/v1/serializers/payment.py +++ b/api/v1/serializers/payment.py @@ -46,8 +46,29 @@ class AuthorBalanceCreateSerializer(serializers.ModelSerializer): 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): author = UserSerializer() + payment = serializers.SerializerMethodField() class Meta: model = AuthorBalance @@ -70,6 +91,24 @@ class AuthorBalanceSerializer(serializers.ModelSerializer): '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): user = UserSerializer() @@ -80,6 +119,7 @@ class PaymentSerializer(serializers.ModelSerializer): read_only_fields = ( 'id', 'user', + 'data', 'created_at', 'update_at', ) @@ -91,6 +131,20 @@ class PaymentSerializer(serializers.ModelSerializer): 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): user = UserSerializer() 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): user = UserSerializer() diff --git a/api/v1/urls.py b/api/v1/urls.py index 51610cb6..0eafbe10 100644 --- a/api/v1/urls.py +++ b/api/v1/urls.py @@ -17,7 +17,7 @@ from .views import ( GalleryViewSet, GalleryImageViewSet, UserViewSet, LessonViewSet, ImageObjectViewSet, SchoolScheduleViewSet, LiveLessonViewSet, - PaymentViewSet, + PaymentViewSet, ObjectCommentsViewSet, ContestViewSet, ContestWorkViewSet) 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'courses', CourseViewSet, base_name='courses') 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'lessons', LessonViewSet, base_name='lessons') router.register(r'likes', LikeViewSet, base_name='likes') diff --git a/api/v1/views.py b/api/v1/views.py index 7620244b..d8129ebe 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -1,3 +1,5 @@ +from datetime import datetime + from django.contrib.auth import get_user_model from rest_framework import status, views, viewsets, generics @@ -11,10 +13,11 @@ from .serializers.course import ( CategorySerializer, LikeSerializer, CourseSerializer, CourseCreateSerializer, CourseBulkChangeCategorySerializer, - CommentSerializer, + CommentSerializer, CommentCreateSerializer, MaterialSerializer, MaterialCreateSerializer, LessonSerializer, LessonCreateSerializer, - LikeCreateSerializer) + LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer, + LiveLessonCommentSerializer) from .serializers.content import ( BanerSerializer, ImageSerializer, ImageCreateSerializer, @@ -34,7 +37,7 @@ from .serializers.payment import ( AuthorBalanceSerializer, AuthorBalanceCreateSerializer, PaymentSerializer, CoursePaymentSerializer, SchoolPaymentSerializer, -) + CoursePaymentCreateSerializer, SchoolPaymentCreateSerializer) from .serializers.user import ( AuthorRequestSerializer, UserSerializer, UserPhotoSerializer, @@ -54,7 +57,7 @@ from apps.course.models import ( Comment, CourseComment, LessonComment, Material, Lesson, Like, -) + LiveLessonComment) from apps.config.models import Config from apps.content.models import ( Baner, Image, Text, ImageText, Video, @@ -66,13 +69,14 @@ from apps.payment.models import ( ) from apps.school.models import SchoolSchedule, LiveLesson from apps.user.models import AuthorRequest +from project.pusher import pusher User = get_user_model() class AuthorBalanceViewSet(ExtendedModelViewSet): queryset = AuthorBalance.objects.filter( - author__role__in=[User.AUTHOR_ROLE, User.ADMIN_ROLE], + author__role__in=[User.AUTHOR_ROLE, User.ADMIN_ROLE, User.TEACHER_ROLE], ) serializer_class = AuthorBalanceCreateSerializer serializer_class_map = { @@ -429,15 +433,15 @@ class ConfigViewSet(generics.RetrieveUpdateAPIView): class CommentViewSet(ExtendedModelViewSet): - queryset = Comment.objects.filter(level=0) + queryset = Comment.objects.all() serializer_class = CommentSerializer - permission_classes = (IsAdmin,) + permission_classes = (IsAuthorObjectOrAdmin,) def get_queryset(self): queryset = self.queryset is_deactivated = self.request.query_params.get('is_deactivated', '0') if is_deactivated == '0': - queryset = queryset + queryset = queryset.filter(level=0) elif is_deactivated == '1': queryset = queryset.filter(deactivated_at__isnull=True) elif is_deactivated == '2': @@ -446,6 +450,78 @@ class CommentViewSet(ExtendedModelViewSet): 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): queryset = AuthorRequest.objects.all() serializer_class = AuthorRequestSerializer @@ -453,7 +529,7 @@ class AuthorRequestViewSet(ExtendedModelViewSet): filter_fields = ('status',) -class PaymentViewSet(ExtendedModelViewSet): +class PaymentViewSet(viewsets.ModelViewSet): queryset = Payment.objects.all() serializer_class = PaymentSerializer permission_classes = (IsAdmin,) @@ -465,6 +541,16 @@ class PaymentViewSet(ExtendedModelViewSet): ) 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): queryset = self.queryset course = self.request.query_params.get('course') @@ -481,10 +567,12 @@ class PaymentViewSet(ExtendedModelViewSet): user = request.query_params.get('user') course = request.query_params.get('course') weekdays = request.query_params.getlist('weekdays[]') + date_start = request.query_params.get('date_start') user = user and User.objects.get(pk=user) 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): diff --git a/apps/course/migrations/0044_livelessoncomment.py b/apps/course/migrations/0044_livelessoncomment.py new file mode 100644 index 00000000..be5102af --- /dev/null +++ b/apps/course/migrations/0044_livelessoncomment.py @@ -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',), + ), + ] diff --git a/apps/course/models.py b/apps/course/models.py index 9a72d454..c7927600 100644 --- a/apps/course/models.py +++ b/apps/course/models.py @@ -10,6 +10,7 @@ from django.urls import reverse_lazy from django.conf import settings from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey +from apps.school.models import LiveLesson from project.mixins import BaseModel, DeactivatedMixin from apps.content.models import ImageObject, Gallery, Video, ContestWork @@ -258,6 +259,9 @@ class Material(models.Model): class Comment(PolymorphicMPTTModel, DeactivatedMixin): + OBJ_TYPE_COURSE = 'course' + OBJ_TYPE_LESSON = 'lesson' + OBJ_TYPE_LIVE_LESSON = 'live-lesson' content = models.TextField('Текст комментария', default='') author = models.ForeignKey(User, on_delete=models.CASCADE) parent = PolymorphicTreeForeignKey( @@ -302,5 +306,15 @@ class LessonComment(Comment): 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): contest_work = models.ForeignKey(ContestWork, on_delete=models.CASCADE, related_name='comments') diff --git a/apps/course/templates/course/lesson.html b/apps/course/templates/course/lesson.html index 608153f0..3f820a17 100644 --- a/apps/course/templates/course/lesson.html +++ b/apps/course/templates/course/lesson.html @@ -2,6 +2,9 @@ {% load static %} {% 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 content %}
+