LIL-559, API создания-получения комментов, компоненты комментариев

remotes/origin/hotfix/LIL-661
gzbender 8 years ago
parent 8fd70682fa
commit f9b72b81e7
  1. 46
      api/v1/serializers/course.py
  2. 3
      api/v1/urls.py
  3. 37
      api/v1/views.py
  4. 9
      apps/course/templates/course/lesson.html
  5. 32
      web/src/components/Comment.vue
  6. 79
      web/src/components/Comments.vue
  7. 16
      web/src/js/app.js
  8. 21
      web/src/js/modules/api.js

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

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

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

@ -96,7 +96,12 @@
<div class="section__center center center_sm">
<div class="title">Задавайте вопросы:</div>
<div class="questions" id="comments_block">
<comments :nodes="comments" user-id="123" :access-token="accessToken"></comments>
<comments obj-type="lesson" obj-id="{{ lesson.id }}"
{% if request.user.is_authenticated %}
user-id="{{ request.user.id }}" user-photo="{{ request.user.photo.url }}"
{% endif %}
default-author-picture="{% static 'img/user_default.jpg' %}"
access-token="{{ request.user.auth_token }}"></comments>
</div>
</div>
</div>
@ -137,6 +142,4 @@
{% include 'templates/blocks/share.html' %}
</div>
</div>
<script type="text/javascript" src="{% static 'comments_vue.js' %}">
</script>
{% endblock content %}

@ -1,26 +1,26 @@
<template>
<div v-if="! node.deactivated_at">
<a class="questions__anchor" :id="'question__' + node.id"></a>
<div :id="'question__replyto__' + node.id" :class="{'questions__item_reply': node.is_child_node}" class="questions__item">
<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.is_child_comment}" class="questions__item">
<div v-if="node.author.photo" class="questions__ava ava">
<img class="ava__pic" :src="node.author.photo.url">
<div v-if="comment.author.photo" class="questions__ava ava">
<img class="ava__pic" :src="comment.author.photo">
</div>
<div v-if="! node.author.photo" class="questions__ava ava">
<img class="ava__pic" src="{% static 'img/user_default.jpg' %}">
<div v-if="! comment.author.photo" class="questions__ava ava">
<img class="ava__pic" :src="defaultAuthorPicture">
</div>
<div class="questions__wrap">
<div class="questions__details">
<div class="questions__head">
<span class="questions__author">{{ node.author.get_full_name }}</span>
<span class="questions__date">{{ node.created_at_humanize }}</span>
<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">{{ node.content }}</div>
<div class="questions__content">{{ comment.content }}</div>
</div>
<div class="questions__foot">
<button @click="reply" v-if="userId" class="questions__action question__reply-button" :data-reply-id="node.id">ОТВЕТИТЬ</button>
<button @click="reply" v-if="userId" class="questions__action question__reply-button">ОТВЕТИТЬ</button>
</div>
</div>
</div>
@ -31,14 +31,14 @@
<script>
export default {
name: 'comment',
props: ['node', 'userId'],
props: ['comment', 'userId', 'defaultAuthorPicture'],
methods: {
reply() {
//
this.$emit('reply', this.comment);
}
},
components: {
}
mounted() {
console.log(this.defaultAuthorPicture);
},
}
</script>

@ -2,10 +2,26 @@
<div>
<ul v-for="node in nodes">
<li>
<comment v-if="! node.deactivated_at" :node="node" :user-id="userId"></comment>
<comments v-if="! node.is_leaf_node && node.children" :nodes="node.children"></comments>
<comment v-on:reply="onReply" v-if="! node.deactivated_at" :comment="node" :user-id="userId" :default-author-picture="defaultAuthorPicture"></comment>
<comments v-if="! node.is_leaf_node && node.children" :comments="node.children" default-author-picture="defaultAuthorPicture"></comments>
</li>
</ul>
<form v-if="userId" class="questions__form" method="post" >
<input type="hidden" name="reply_id">
<div class="questions__ava ava">
<img class="ava__pic" :src="userPhoto || defaultAuthorPicture">
</div>
<div class="questions__wrap">
<div v-show="replyTo" class="questions__reply-info" style="display: block;">В ответ на
<a :href="replyAnchor" class="questions__reply-anchor">этот комментарий</a>.
<a href="#" @click="cancelReply" class="questions__reply-cancel grey-link">Отменить</a>
</div>
<div class="questions__field">
<textarea v-model="content" class="questions__textarea" placeholder="Задайте автору курса интересующие вас вопросы"></textarea>
</div>
<button class="questions__btn btn btn_light" @click="post">ОТПРАВИТЬ</button>
</div>
</form>
</div>
</template>
@ -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);
},

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

@ -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}`,
}
});
}
};

Loading…
Cancel
Save