diff --git a/api/v1/serializers/course.py b/api/v1/serializers/course.py index bdb1a289..298528bc 100644 --- a/api/v1/serializers/course.py +++ b/api/v1/serializers/course.py @@ -300,6 +300,7 @@ class CommentSerializer(serializers.ModelSerializer): 'parent', 'deactivated_at', 'created_at', + 'created_at_humanize', 'update_at', ) @@ -347,3 +348,48 @@ class LessonCommentSerializer(serializers.ModelSerializer): read_only_fields = CommentSerializer.Meta.read_only_fields + ( 'children', ) + + +class CommentCreateSerializer(serializers.ModelSerializer): + OBJ_TYPE_COURSE = 'course' + OBJ_TYPE_LESSON = 'lesson' + + 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 == self.OBJ_TYPE_COURSE: + validated_data['course_id'] = obj_id + return CourseCommentSerializer().create(validated_data) + elif obj_type == self.OBJ_TYPE_LESSON: + validated_data['lesson_id'] = obj_id + return LessonCommentSerializer().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) diff --git a/api/v1/urls.py b/api/v1/urls.py index 409ed79f..5905dc8b 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, ) 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 41cd881a..fb79a335 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -11,9 +11,10 @@ from .serializers.course import ( CategorySerializer, LikeSerializer, CourseSerializer, CourseCreateSerializer, CourseBulkChangeCategorySerializer, - CommentSerializer, + CommentSerializer, CommentCreateSerializer, MaterialSerializer, MaterialCreateSerializer, LessonSerializer, LessonCreateSerializer, + CourseCommentSerializer, LessonCommentSerializer, ) from .serializers.content import ( BanerSerializer, @@ -395,7 +396,7 @@ class ConfigViewSet(generics.RetrieveUpdateAPIView): class CommentViewSet(ExtendedModelViewSet): queryset = Comment.objects.filter(level=0) - serializer_class = CommentSerializer + serializer_class = CommentCreateSerializer permission_classes = (IsAdmin,) def get_queryset(self): @@ -411,6 +412,38 @@ class CommentViewSet(ExtendedModelViewSet): return queryset +class ObjectCommentsViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Comment.objects.filter(level=0) + OBJ_TYPE_COURSE = 'course' + OBJ_TYPE_LESSON = 'lesson' + permission_classes = (IsAdmin,) + + 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', '0') + if obj_type == self.OBJ_TYPE_COURSE: + queryset = CourseComment.objects.filter(course=obj_id) + elif obj_type == self.OBJ_TYPE_LESSON: + queryset = LessonComment.objects.filter(lesson=obj_id) + if is_deactivated == '0': + queryset = queryset + 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): + obj_type = self.request.query_params.get('obj_type') + if obj_type == self.OBJ_TYPE_COURSE: + serializer_class = CourseCommentSerializer + elif obj_type == self.OBJ_TYPE_LESSON: + serializer_class = LessonCommentSerializer + return serializer_class + + class AuthorRequestViewSet(ExtendedModelViewSet): queryset = AuthorRequest.objects.all() serializer_class = AuthorRequestSerializer diff --git a/apps/course/templates/course/lesson.html b/apps/course/templates/course/lesson.html index 9fbe5a88..18e51e78 100644 --- a/apps/course/templates/course/lesson.html +++ b/apps/course/templates/course/lesson.html @@ -96,7 +96,12 @@
Задавайте вопросы:
- +
@@ -137,6 +142,4 @@ {% include 'templates/blocks/share.html' %} - {% endblock content %} diff --git a/web/src/components/Comment.vue b/web/src/components/Comment.vue index b688c6b7..1abdd00b 100644 --- a/web/src/components/Comment.vue +++ b/web/src/components/Comment.vue @@ -1,26 +1,26 @@ @@ -15,19 +31,62 @@ export default { name: 'comments', - props: ['objType', 'objId', 'nodes', 'userId', 'accessToken'], - methods: { - reply() { - // + props: ['objType', 'objId', 'comments', 'userId', 'userPhoto', 'accessToken', 'defaultAuthorPicture'], + data() { + return { + loading: false, + replyTo: null, + content: '', + nodes: [], + } + }, + computed: { + replyAnchor(){ + return this.replyTo ? `#question__${this.replyTo.id}` : ''; } }, + methods: { + clear() { + this.content = '', + this.replyTo = null + }, + cancelReply() { + this.replyTo = null; + }, + post() { + this.loading = true; + let request = api.addComment(this.objId, this.objType, { + content: this.content, + author: this.userId, + parent: this.replyTo && this.replyTo.id, + }, this.accessToken); + request.then((response) => { + this.loading = false; + if(this.replyTo){ + this.replyTo.children.push(response.data); + } + else { + this.nodes.push(response.data); + } + this.clear(); + }).catch(() => { + this.loading = false; + }); + }, + onReply(replyTo) { + this.replyTo = replyTo; + }, + }, mounted() { - if(! this.nodes && this.objType && this.objId){ - request = api.loadComments(this.objId, this.objType, this.accessToken); + if(this.comments && this.comments.length){ + this.nodes = this.comments; + } + else if(this.objType && this.objId){ + let request = api.getObjComments(this.objId, this.objType, this.accessToken); request .then((response) => { - this.nodes = response.data; - }) + this.nodes = response.data.results; + }); } console.log('nodes', this.nodes); }, diff --git a/web/src/js/app.js b/web/src/js/app.js index 16651100..7be8af4e 100644 --- a/web/src/js/app.js +++ b/web/src/js/app.js @@ -11,9 +11,25 @@ import "./modules/tabs"; import "./modules/popup"; import "./modules/courses"; import "./modules/comments"; +import "./modules/comments"; import "./modules/password-show"; import "./modules/profile"; import "./modules/notification"; import "./modules/mixpanel"; import "../sass/app.sass"; + +import Vue from 'vue'; +import Comments from '../components/Comments'; + +if (process.env.NODE_ENV === 'development') { + // Enable vue-devtools + Vue.config.devtools = true; +} + +let app = new Vue({ + el: '.js-outer', + components: { + 'comments': Comments, + } +}) diff --git a/web/src/js/modules/api.js b/web/src/js/modules/api.js index 990ac7b2..003153a3 100644 --- a/web/src/js/modules/api.js +++ b/web/src/js/modules/api.js @@ -502,4 +502,25 @@ export const api = { } }); }, + getObjComments: (objId, objType, accessToken) => { + return api.get('/api/v1/obj-comments/', { + params: { + obj_id: objId, + obj_type: objType, + }, + headers: { + 'Authorization': `Token ${accessToken}`, + } + }); + }, + addComment: (objId, objType, commentJson, accessToken) => { + let data = commentJson; + data.obj_id = objId; + data.obj_type = objType; + return api.post('/api/v1/comments/', data, { + headers: { + 'Authorization': `Token ${accessToken}`, + } + }); + } };