Commit 16ab4f43 by wajeeha-khalid

Merge pull request #10105 from edx/jia/MA-1189

MA-1189 - Discussion API: implemented GET comment children
parents 2301bcbf c5afa44e
...@@ -8,6 +8,7 @@ from urlparse import urlunparse ...@@ -8,6 +8,7 @@ from urlparse import urlunparse
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import Http404 from django.http import Http404
import itertools
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
...@@ -378,7 +379,7 @@ def get_comment_list(request, thread_id, endorsed, page, page_size, mark_as_read ...@@ -378,7 +379,7 @@ def get_comment_list(request, thread_id, endorsed, page, page_size, mark_as_read
request, request,
thread_id, thread_id,
retrieve_kwargs={ retrieve_kwargs={
"recursive": True, "recursive": False,
"user_id": request.user.id, "user_id": request.user.id,
"mark_as_read": mark_as_read, "mark_as_read": mark_as_read,
"response_skip": response_skip, "response_skip": response_skip,
...@@ -717,6 +718,56 @@ def get_thread(request, thread_id): ...@@ -717,6 +718,56 @@ def get_thread(request, thread_id):
return serializer.data return serializer.data
def get_response_comments(request, comment_id, page, page_size):
"""
Return the list of comments for the given thread response.
Arguments:
request: The django request object used for build_absolute_uri and
determining the requesting user.
comment_id: The id of the comment/response to get child comments for.
page: The page number (1-indexed) to retrieve
page_size: The number of comments to retrieve per page
Returns:
A paginated result containing a list of comments
"""
try:
cc_comment = Comment(id=comment_id).retrieve()
cc_thread, context = _get_thread_and_context(
request,
cc_comment["thread_id"],
retrieve_kwargs={
"recursive": True,
}
)
if cc_thread["thread_type"] == "question":
thread_responses = itertools.chain(cc_thread["endorsed_responses"], cc_thread["non_endorsed_responses"])
else:
thread_responses = cc_thread["children"]
response_comments = []
for response in thread_responses:
if response["id"] == comment_id:
response_comments = response["children"]
break
response_skip = page_size * (page - 1)
paged_response_comments = response_comments[response_skip:(response_skip + page_size)]
results = [CommentSerializer(comment, context=context).data for comment in paged_response_comments]
comments_count = len(response_comments)
num_pages = (comments_count + page_size - 1) / page_size if comments_count else 1
return get_paginated_data(request, results, page, num_pages)
except CommentClientRequestError:
raise Http404
def delete_thread(request, thread_id): def delete_thread(request, thread_id):
""" """
Delete a thread. Delete a thread.
......
...@@ -282,6 +282,15 @@ class CommentSerializer(_ContentSerializer): ...@@ -282,6 +282,15 @@ class CommentSerializer(_ContentSerializer):
non_updatable_fields = NON_UPDATABLE_COMMENT_FIELDS non_updatable_fields = NON_UPDATABLE_COMMENT_FIELDS
def __init__(self, *args, **kwargs):
remove_fields = kwargs.pop('remove_fields', None)
super(CommentSerializer, self).__init__(*args, **kwargs)
if remove_fields:
# for multiple fields in a list
for field_name in remove_fields:
self.fields.pop(field_name)
def get_endorsed_by(self, obj): def get_endorsed_by(self, obj):
""" """
Returns the username of the endorsing user, if the information is Returns the username of the endorsing user, if the information is
......
...@@ -1085,7 +1085,7 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase): ...@@ -1085,7 +1085,7 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase):
self.assert_query_params_equal( self.assert_query_params_equal(
httpretty.httpretty.latest_requests[-2], httpretty.httpretty.latest_requests[-2],
{ {
"recursive": ["True"], "recursive": ["False"],
"user_id": [str(self.user.id)], "user_id": [str(self.user.id)],
"mark_as_read": ["False"], "mark_as_read": ["False"],
"resp_skip": ["70"], "resp_skip": ["70"],
...@@ -1146,8 +1146,8 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase): ...@@ -1146,8 +1146,8 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase):
"abuse_flagged": False, "abuse_flagged": False,
"voted": False, "voted": False,
"vote_count": 4, "vote_count": 4,
"children": [],
"editable_fields": ["abuse_flagged", "voted"], "editable_fields": ["abuse_flagged", "voted"],
"children": [],
}, },
{ {
"id": "test_comment_2", "id": "test_comment_2",
...@@ -1166,8 +1166,8 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase): ...@@ -1166,8 +1166,8 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase):
"abuse_flagged": True, "abuse_flagged": True,
"voted": False, "voted": False,
"vote_count": 7, "vote_count": 7,
"children": [],
"editable_fields": ["abuse_flagged", "voted"], "editable_fields": ["abuse_flagged", "voted"],
"children": [],
}, },
] ]
actual_comments = self.get_comment_list( actual_comments = self.get_comment_list(
......
...@@ -665,7 +665,6 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -665,7 +665,6 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"endorsed": False, "endorsed": False,
"abuse_flaggers": [], "abuse_flaggers": [],
"votes": {"up_count": 4}, "votes": {"up_count": 4},
"children": [],
}] }]
expected_comments = [{ expected_comments = [{
"id": "test_comment", "id": "test_comment",
...@@ -684,8 +683,8 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -684,8 +683,8 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"abuse_flagged": False, "abuse_flagged": False,
"voted": True, "voted": True,
"vote_count": 4, "vote_count": 4,
"children": [],
"editable_fields": ["abuse_flagged", "voted"], "editable_fields": ["abuse_flagged", "voted"],
"children": [],
}] }]
self.register_get_thread_response({ self.register_get_thread_response({
"id": self.thread_id, "id": self.thread_id,
...@@ -709,7 +708,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -709,7 +708,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
self.assert_query_params_equal( self.assert_query_params_equal(
httpretty.httpretty.latest_requests[-2], httpretty.httpretty.latest_requests[-2],
{ {
"recursive": ["True"], "recursive": ["False"],
"resp_skip": ["0"], "resp_skip": ["0"],
"resp_limit": ["10"], "resp_limit": ["10"],
"user_id": [str(self.user.id)], "user_id": [str(self.user.id)],
...@@ -743,7 +742,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -743,7 +742,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
self.assert_query_params_equal( self.assert_query_params_equal(
httpretty.httpretty.latest_requests[-2], httpretty.httpretty.latest_requests[-2],
{ {
"recursive": ["True"], "recursive": ["False"],
"resp_skip": ["68"], "resp_skip": ["68"],
"resp_limit": ["4"], "resp_limit": ["4"],
"user_id": [str(self.user.id)], "user_id": [str(self.user.id)],
...@@ -1028,3 +1027,74 @@ class ThreadViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase) ...@@ -1028,3 +1027,74 @@ class ThreadViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase)
self.register_get_thread_error_response(self.thread_id, 404) self.register_get_thread_error_response(self.thread_id, 404)
response = self.client.get(self.url) response = self.client.get(self.url)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
@httpretty.activate
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CommentViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for CommentViewSet Retrieve"""
def setUp(self):
super(CommentViewSetRetrieveTest, self).setUp()
self.url = reverse("comment-detail", kwargs={"comment_id": "test_comment"})
self.thread_id = "test_thread"
self.comment_id = "test_comment"
def make_comment_data(self, comment_id, parent_id=None, children=[]): # pylint: disable=W0102
"""
Returns comment dict object as returned by comments service
"""
return make_minimal_cs_comment({
"id": comment_id,
"parent_id": parent_id,
"course_id": unicode(self.course.id),
"thread_id": self.thread_id,
"thread_type": "discussion",
"username": self.user.username,
"user_id": str(self.user.id),
"created_at": "2015-06-03T00:00:00Z",
"updated_at": "2015-06-03T00:00:00Z",
"body": "Original body",
"children": children,
})
def test_basic(self):
self.register_get_user_response(self.user)
cs_comment_child = self.make_comment_data("test_child_comment", self.comment_id, children=[])
cs_comment = self.make_comment_data(self.comment_id, None, [cs_comment_child])
cs_thread = make_minimal_cs_thread({
"id": self.thread_id,
"course_id": unicode(self.course.id),
"children": [cs_comment],
})
self.register_get_thread_response(cs_thread)
self.register_get_comment_response(cs_comment)
expected_response_data = {
"id": "test_child_comment",
"parent_id": self.comment_id,
"thread_id": self.thread_id,
"author": self.user.username,
"author_label": None,
"raw_body": "Original body",
"rendered_body": "<p>Original body</p>",
"created_at": "2015-06-03T00:00:00Z",
"updated_at": "2015-06-03T00:00:00Z",
"children": [],
"endorsed_at": None,
"endorsed": False,
"endorsed_by": None,
"endorsed_by_label": None,
"voted": False,
"vote_count": 0,
"abuse_flagged": False,
"editable_fields": ["abuse_flagged", "raw_body", "voted"]
}
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(json.loads(response.content)['results'][0], expected_response_data)
def test_retrieve_nonexistent_comment(self):
self.register_get_comment_error_response(self.comment_id, 404)
response = self.client.get(self.url)
self.assertEqual(response.status_code, 404)
...@@ -18,6 +18,7 @@ from discussion_api.api import ( ...@@ -18,6 +18,7 @@ from discussion_api.api import (
delete_thread, delete_thread,
delete_comment, delete_comment,
get_comment_list, get_comment_list,
get_response_comments,
get_course, get_course,
get_course_topics, get_course_topics,
get_thread, get_thread,
...@@ -25,7 +26,7 @@ from discussion_api.api import ( ...@@ -25,7 +26,7 @@ from discussion_api.api import (
update_comment, update_comment,
update_thread, update_thread,
) )
from discussion_api.forms import CommentListGetForm, ThreadListGetForm from discussion_api.forms import CommentListGetForm, ThreadListGetForm, _PaginationForm
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
...@@ -301,6 +302,8 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): ...@@ -301,6 +302,8 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet):
GET /api/discussion/v1/comments/?thread_id=0123456789abcdef01234567 GET /api/discussion/v1/comments/?thread_id=0123456789abcdef01234567
GET /api/discussion/v1/comments/2123456789abcdef01234555
POST /api/discussion/v1/comments/ POST /api/discussion/v1/comments/
{ {
"thread_id": "0123456789abcdef01234567", "thread_id": "0123456789abcdef01234567",
...@@ -312,7 +315,7 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): ...@@ -312,7 +315,7 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet):
DELETE /api/discussion/v1/comments/comment_id DELETE /api/discussion/v1/comments/comment_id
**GET Parameters**: **GET Comment List Parameters**:
* thread_id (required): The thread to retrieve comments for * thread_id (required): The thread to retrieve comments for
...@@ -327,6 +330,15 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): ...@@ -327,6 +330,15 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet):
* mark_as_read: Will mark the thread of the comments as read. (default * mark_as_read: Will mark the thread of the comments as read. (default
is False) is False)
**GET Child Comment List Parameters**:
* comment_id (required): The comment to retrieve child comments for
* page: The (1-indexed) page to retrieve (default is 1)
* page_size: The number of items per page (default is 10, max is 100)
**POST Parameters**: **POST Parameters**:
* thread_id (required): The thread to post the comment in * thread_id (required): The thread to post the comment in
...@@ -422,6 +434,22 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): ...@@ -422,6 +434,22 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet):
) )
) )
def retrieve(self, request, comment_id=None):
"""
Implements the GET method for comments against response ID
"""
form = _PaginationForm(request.GET)
if not form.is_valid():
raise ValidationError(form.errors)
return Response(
get_response_comments(
request,
comment_id,
form.cleaned_data["page"],
form.cleaned_data["page_size"]
)
)
def create(self, request): def create(self, request):
""" """
Implements the POST method for the list endpoint as described in the Implements the POST method for the list endpoint as described in the
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment