diff --git a/lms/djangoapps/discussion_api/api.py b/lms/djangoapps/discussion_api/api.py index 15b35d5ac0..0b54ec0ccf 100644 --- a/lms/djangoapps/discussion_api/api.py +++ b/lms/djangoapps/discussion_api/api.py @@ -415,7 +415,7 @@ def get_comment_list(request, thread_id, endorsed, page, page_size, mark_as_read raise Http404 num_pages = (resp_total + page_size - 1) / page_size if resp_total else 1 - results = [CommentSerializer(response, context=context).data for response in responses] + results = [CommentSerializer(response, remove_fields=["children"], context=context).data for response in responses] return get_paginated_data(request, results, page, num_pages) @@ -718,6 +718,50 @@ def get_thread(request, thread_id): 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"]) + if cc_thread["thread_type"] == "question": + thread_responses = 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): """ Delete a thread. diff --git a/lms/djangoapps/discussion_api/serializers.py b/lms/djangoapps/discussion_api/serializers.py index 4309531c12..18a8989a40 100644 --- a/lms/djangoapps/discussion_api/serializers.py +++ b/lms/djangoapps/discussion_api/serializers.py @@ -290,6 +290,15 @@ class CommentSerializer(_ContentSerializer): 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): """ Returns the username of the endorsing user, if the information is diff --git a/lms/djangoapps/discussion_api/tests/test_api.py b/lms/djangoapps/discussion_api/tests/test_api.py index acb06ff2fc..ca14dfbec9 100644 --- a/lms/djangoapps/discussion_api/tests/test_api.py +++ b/lms/djangoapps/discussion_api/tests/test_api.py @@ -1146,7 +1146,6 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase): "abuse_flagged": False, "voted": False, "vote_count": 4, - "children": [], "editable_fields": ["abuse_flagged", "voted"], }, { @@ -1166,7 +1165,6 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase): "abuse_flagged": True, "voted": False, "vote_count": 7, - "children": [], "editable_fields": ["abuse_flagged", "voted"], }, ] diff --git a/lms/djangoapps/discussion_api/tests/test_views.py b/lms/djangoapps/discussion_api/tests/test_views.py index 27287a8c7a..915cfc89ec 100644 --- a/lms/djangoapps/discussion_api/tests/test_views.py +++ b/lms/djangoapps/discussion_api/tests/test_views.py @@ -662,7 +662,6 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): "endorsed": False, "abuse_flaggers": [], "votes": {"up_count": 4}, - "children": [], }] expected_comments = [{ "id": "test_comment", @@ -681,7 +680,6 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): "abuse_flagged": False, "voted": True, "vote_count": 4, - "children": [], "editable_fields": ["abuse_flagged", "voted"], }] self.register_get_thread_response({ @@ -1025,3 +1023,73 @@ class ThreadViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase) self.register_get_thread_error_response(self.thread_id, 404) response = self.client.get(self.url) self.assertEqual(response.status_code, 404) + + +@httpretty.activate +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": "
Original body
", + "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) diff --git a/lms/djangoapps/discussion_api/views.py b/lms/djangoapps/discussion_api/views.py index 7852a4abe8..13de43b16f 100644 --- a/lms/djangoapps/discussion_api/views.py +++ b/lms/djangoapps/discussion_api/views.py @@ -18,6 +18,7 @@ from discussion_api.api import ( delete_thread, delete_comment, get_comment_list, + get_response_comments, get_course, get_course_topics, get_thread, @@ -25,7 +26,7 @@ from discussion_api.api import ( update_comment, 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 @@ -299,6 +300,8 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): GET /api/discussion/v1/comments/?thread_id=0123456789abcdef01234567 + GET /api/discussion/v1/comments/2123456789abcdef01234555 + POST /api/discussion/v1/comments/ { "thread_id": "0123456789abcdef01234567", @@ -310,7 +313,7 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): DELETE /api/discussion/v1/comments/comment_id - **GET Parameters**: + **GET Comment List Parameters**: * thread_id (required): The thread to retrieve comments for @@ -325,6 +328,15 @@ class CommentViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): * mark_as_read: Will mark the thread of the comments as read. (default 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**: * thread_id (required): The thread to post the comment in @@ -420,6 +432,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): """ Implements the POST method for the list endpoint as described in the