added GET method for Threads in Dicussion API
This commit is contained in:
@@ -39,7 +39,7 @@ from django_comment_common.signals import (
|
||||
comment_created,
|
||||
comment_edited,
|
||||
comment_voted,
|
||||
comment_deleted
|
||||
comment_deleted,
|
||||
)
|
||||
from django_comment_client.utils import get_accessible_discussion_modules, is_commentable_cohorted
|
||||
from lms.lib.comment_client.comment import Comment
|
||||
@@ -697,6 +697,23 @@ def update_comment(request, comment_id, update_data):
|
||||
return api_comment
|
||||
|
||||
|
||||
def get_thread(request, thread_id):
|
||||
"""
|
||||
Retrieve a thread.
|
||||
|
||||
Arguments:
|
||||
|
||||
request: The django request object used for build_absolute_uri and
|
||||
determining the requesting user.
|
||||
|
||||
thread_id: The id for the thread to retrieve
|
||||
|
||||
"""
|
||||
cc_thread, context = _get_thread_and_context(request, thread_id)
|
||||
serializer = ThreadSerializer(cc_thread, context=context)
|
||||
return serializer.data
|
||||
|
||||
|
||||
def delete_thread(request, thread_id):
|
||||
"""
|
||||
Delete a thread.
|
||||
|
||||
@@ -33,6 +33,7 @@ from discussion_api.api import (
|
||||
get_thread_list,
|
||||
update_comment,
|
||||
update_thread,
|
||||
get_thread,
|
||||
)
|
||||
from discussion_api.tests.utils import (
|
||||
CommentsServiceMockMixin,
|
||||
@@ -2786,3 +2787,173 @@ class DeleteCommentTest(
|
||||
self.assertFalse(expected_error)
|
||||
except Http404:
|
||||
self.assertTrue(expected_error)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class RetrieveThreadTest(
|
||||
CommentsServiceMockMixin,
|
||||
UrlResetMixin,
|
||||
SharedModuleStoreTestCase
|
||||
):
|
||||
"""Tests for get_thread"""
|
||||
@classmethod
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUpClass(cls):
|
||||
super(RetrieveThreadTest, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super(RetrieveThreadTest, self).setUp()
|
||||
httpretty.reset()
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.disable)
|
||||
self.thread_author = UserFactory.create()
|
||||
self.register_get_user_response(self.thread_author)
|
||||
self.request = RequestFactory().get("/test_path")
|
||||
self.request.user = self.thread_author
|
||||
self.thread_id = "test_thread"
|
||||
CourseEnrollmentFactory.create(user=self.thread_author, course_id=self.course.id)
|
||||
|
||||
def register_thread(self, overrides=None):
|
||||
"""
|
||||
Make a thread with appropriate data overridden by the overrides
|
||||
parameter and register mock responses for GET on its
|
||||
endpoint.
|
||||
"""
|
||||
cs_data = make_minimal_cs_thread({
|
||||
"id": self.thread_id,
|
||||
"course_id": unicode(self.course.id),
|
||||
"commentable_id": "test_topic",
|
||||
"username": self.thread_author.username,
|
||||
"user_id": str(self.thread_author.id),
|
||||
"title": "Test Title",
|
||||
"body": "Test body",
|
||||
"created_at": "2015-05-29T00:00:00Z",
|
||||
"updated_at": "2015-05-29T00:00:00Z"
|
||||
})
|
||||
cs_data.update(overrides or {})
|
||||
self.register_get_thread_response(cs_data)
|
||||
|
||||
def test_basic(self):
|
||||
expected_response_data = {
|
||||
"author": self.thread_author.username,
|
||||
"author_label": None,
|
||||
"created_at": "2015-05-29T00:00:00Z",
|
||||
"updated_at": "2015-05-29T00:00:00Z",
|
||||
"raw_body": "Test body",
|
||||
"rendered_body": "<p>Test body</p>",
|
||||
"abuse_flagged": False,
|
||||
"voted": False,
|
||||
"vote_count": 0,
|
||||
"editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"],
|
||||
"course_id": unicode(self.course.id),
|
||||
"topic_id": "test_topic",
|
||||
"group_id": None,
|
||||
"group_name": None,
|
||||
"title": "Test Title",
|
||||
"pinned": False,
|
||||
"closed": False,
|
||||
"following": False,
|
||||
"comment_count": 0,
|
||||
"unread_comment_count": 0,
|
||||
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread",
|
||||
"endorsed_comment_list_url": None,
|
||||
"non_endorsed_comment_list_url": None,
|
||||
"read": False,
|
||||
"has_endorsed": False,
|
||||
"id": "test_thread",
|
||||
"type": "discussion"
|
||||
}
|
||||
self.register_thread()
|
||||
self.assertEqual(get_thread(self.request, self.thread_id), expected_response_data)
|
||||
self.assertEqual(httpretty.last_request().method, "GET")
|
||||
|
||||
def test_thread_id_not_found(self):
|
||||
self.register_get_thread_error_response("missing_thread", 404)
|
||||
with self.assertRaises(Http404):
|
||||
get_thread(self.request, "missing_thread")
|
||||
|
||||
def test_nonauthor_enrolled_in_course(self):
|
||||
expected_response_data = {
|
||||
"author": self.thread_author.username,
|
||||
"author_label": None,
|
||||
"created_at": "2015-05-29T00:00:00Z",
|
||||
"updated_at": "2015-05-29T00:00:00Z",
|
||||
"raw_body": "Test body",
|
||||
"rendered_body": "<p>Test body</p>",
|
||||
"abuse_flagged": False,
|
||||
"voted": False,
|
||||
"vote_count": 0,
|
||||
"editable_fields": ["abuse_flagged", "following", "voted"],
|
||||
"course_id": unicode(self.course.id),
|
||||
"topic_id": "test_topic",
|
||||
"group_id": None,
|
||||
"group_name": None,
|
||||
"title": "Test Title",
|
||||
"pinned": False,
|
||||
"closed": False,
|
||||
"following": False,
|
||||
"comment_count": 0,
|
||||
"unread_comment_count": 0,
|
||||
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread",
|
||||
"endorsed_comment_list_url": None,
|
||||
"non_endorsed_comment_list_url": None,
|
||||
"read": False,
|
||||
"has_endorsed": False,
|
||||
"id": "test_thread",
|
||||
"type": "discussion"
|
||||
}
|
||||
non_author_user = UserFactory.create() # pylint: disable=attribute-defined-outside-init
|
||||
self.register_get_user_response(non_author_user)
|
||||
CourseEnrollmentFactory.create(user=non_author_user, course_id=self.course.id)
|
||||
self.register_thread()
|
||||
self.request.user = non_author_user
|
||||
self.assertEqual(get_thread(self.request, self.thread_id), expected_response_data)
|
||||
self.assertEqual(httpretty.last_request().method, "GET")
|
||||
|
||||
@ddt.data(
|
||||
*itertools.product(
|
||||
[
|
||||
FORUM_ROLE_ADMINISTRATOR,
|
||||
FORUM_ROLE_MODERATOR,
|
||||
FORUM_ROLE_COMMUNITY_TA,
|
||||
FORUM_ROLE_STUDENT,
|
||||
],
|
||||
[True, False],
|
||||
["no_group", "match_group", "different_group"],
|
||||
)
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_group_access(self, role_name, course_is_cohorted, thread_group_state):
|
||||
"""
|
||||
Tests group access for retrieving a thread
|
||||
|
||||
All privileged roles are able to retrieve a thread. A student role can
|
||||
only retrieve a thread if,
|
||||
the student role is the author and the thread is not in a cohort,
|
||||
the student role is the author and the thread is in the author's cohort.
|
||||
"""
|
||||
cohort_course = CourseFactory.create(cohort_config={"cohorted": course_is_cohorted})
|
||||
CourseEnrollmentFactory.create(user=self.thread_author, course_id=cohort_course.id)
|
||||
cohort = CohortFactory.create(course_id=cohort_course.id, users=[self.thread_author])
|
||||
role = Role.objects.create(name=role_name, course_id=cohort_course.id)
|
||||
role.users = [self.thread_author]
|
||||
self.register_thread({
|
||||
"course_id": unicode(cohort_course.id),
|
||||
"group_id": (
|
||||
None if thread_group_state == "no_group" else
|
||||
cohort.id if thread_group_state == "match_group" else
|
||||
cohort.id + 1
|
||||
),
|
||||
})
|
||||
expected_error = (
|
||||
role_name == FORUM_ROLE_STUDENT and
|
||||
course_is_cohorted and
|
||||
thread_group_state == "different_group"
|
||||
)
|
||||
try:
|
||||
get_thread(self.request, self.thread_id)
|
||||
self.assertFalse(expected_error)
|
||||
except Http404:
|
||||
self.assertTrue(expected_error)
|
||||
|
||||
@@ -588,6 +588,7 @@ class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
self.thread_id = "test_thread"
|
||||
|
||||
def test_basic(self):
|
||||
#from nose.tools import set_trace;set_trace()
|
||||
self.register_get_user_response(self.user)
|
||||
cs_thread = make_minimal_cs_thread({
|
||||
"id": self.thread_id,
|
||||
@@ -607,6 +608,7 @@ class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
self.assertEqual(httpretty.last_request().method, "DELETE")
|
||||
|
||||
def test_delete_nonexistent_thread(self):
|
||||
#from nose.tools import set_trace;set_trace()
|
||||
self.register_get_thread_error_response(self.thread_id, 404)
|
||||
response = self.client.delete(self.url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
@@ -951,3 +953,65 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_data = json.loads(response.content)
|
||||
self.assertEqual(response_data, expected_response_data)
|
||||
|
||||
|
||||
@httpretty.activate
|
||||
class ThreadViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"""Tests for ThreadViewSet Retrieve"""
|
||||
def setUp(self):
|
||||
super(ThreadViewSetRetrieveTest, self).setUp()
|
||||
self.url = reverse("thread-detail", kwargs={"thread_id": "test_thread"})
|
||||
self.thread_id = "test_thread"
|
||||
|
||||
def test_basic(self):
|
||||
self.register_get_user_response(self.user)
|
||||
cs_thread = make_minimal_cs_thread({
|
||||
"id": self.thread_id,
|
||||
"course_id": unicode(self.course.id),
|
||||
"commentable_id": "test_topic",
|
||||
"username": self.user.username,
|
||||
"user_id": str(self.user.id),
|
||||
"title": "Test Title",
|
||||
"body": "Test body",
|
||||
"created_at": "2015-05-29T00:00:00Z",
|
||||
"updated_at": "2015-05-29T00:00:00Z"
|
||||
})
|
||||
expected_response_data = {
|
||||
"author": self.user.username,
|
||||
"author_label": None,
|
||||
"created_at": "2015-05-29T00:00:00Z",
|
||||
"updated_at": "2015-05-29T00:00:00Z",
|
||||
"raw_body": "Test body",
|
||||
"rendered_body": "<p>Test body</p>",
|
||||
"abuse_flagged": False,
|
||||
"voted": False,
|
||||
"vote_count": 0,
|
||||
"editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"],
|
||||
"course_id": unicode(self.course.id),
|
||||
"topic_id": "test_topic",
|
||||
"group_id": None,
|
||||
"group_name": None,
|
||||
"title": "Test Title",
|
||||
"pinned": False,
|
||||
"closed": False,
|
||||
"following": False,
|
||||
"comment_count": 0,
|
||||
"unread_comment_count": 0,
|
||||
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread",
|
||||
"endorsed_comment_list_url": None,
|
||||
"non_endorsed_comment_list_url": None,
|
||||
"read": False,
|
||||
"has_endorsed": False,
|
||||
"id": "test_thread",
|
||||
"type": "discussion"
|
||||
}
|
||||
self.register_get_thread_response(cs_thread)
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(json.loads(response.content), expected_response_data)
|
||||
self.assertEqual(httpretty.last_request().method, "GET")
|
||||
|
||||
def test_retrieve_nonexistent_thread(self):
|
||||
self.register_get_thread_error_response(self.thread_id, 404)
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@@ -22,7 +22,7 @@ from discussion_api.api import (
|
||||
get_thread_list,
|
||||
update_comment,
|
||||
update_thread,
|
||||
)
|
||||
get_thread)
|
||||
from discussion_api.forms import CommentListGetForm, ThreadListGetForm
|
||||
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
|
||||
|
||||
@@ -257,6 +257,12 @@ class ThreadViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet):
|
||||
)
|
||||
)
|
||||
|
||||
def retrieve(self, request, thread_id=None):
|
||||
"""
|
||||
Implements the GET method for thread ID
|
||||
"""
|
||||
return Response(get_thread(request, thread_id))
|
||||
|
||||
def create(self, request):
|
||||
"""
|
||||
Implements the POST method for the list endpoint as described in the
|
||||
|
||||
@@ -27,7 +27,7 @@ from django_comment_common.signals import (
|
||||
comment_edited,
|
||||
comment_voted,
|
||||
comment_deleted,
|
||||
comment_endorsed
|
||||
comment_endorsed,
|
||||
)
|
||||
from django_comment_common.utils import ThreadContext
|
||||
from django_comment_client.utils import (
|
||||
|
||||
Reference in New Issue
Block a user