Commit 09b00df7 by Greg Price

Merge pull request #8154 from edx/gprice/discussion-api-comment-list-refs

Add comment list URLs to discussion api threads
parents c48dccce c83f5615
...@@ -107,7 +107,7 @@ def get_thread_list(request, course_key, page, page_size): ...@@ -107,7 +107,7 @@ def get_thread_list(request, course_key, page, page_size):
discussion_api.views.ThreadViewSet for more detail. discussion_api.views.ThreadViewSet for more detail.
""" """
course = _get_course_or_404(course_key, request.user) course = _get_course_or_404(course_key, request.user)
context = get_context(course, request.user) context = get_context(course, request)
threads, result_page, num_pages, _ = Thread.search({ threads, result_page, num_pages, _ = Thread.search({
"course_id": unicode(course.id), "course_id": unicode(course.id),
"group_id": ( "group_id": (
...@@ -169,7 +169,7 @@ def get_comment_list(request, thread_id, endorsed, page, page_size): ...@@ -169,7 +169,7 @@ def get_comment_list(request, thread_id, endorsed, page, page_size):
course_key = CourseLocator.from_string(cc_thread["course_id"]) course_key = CourseLocator.from_string(cc_thread["course_id"])
course = _get_course_or_404(course_key, request.user) course = _get_course_or_404(course_key, request.user)
context = get_context(course, request.user, cc_thread) context = get_context(course, request, cc_thread)
# Ensure user has access to the thread # Ensure user has access to the thread
if not context["is_requester_privileged"] and cc_thread["group_id"]: if not context["is_requester_privileged"] and cc_thread["group_id"]:
......
""" """
Discussion API serializers Discussion API serializers
""" """
from urllib import urlencode
from urlparse import urlunparse
from django.contrib.auth.models import User as DjangoUser from django.contrib.auth.models import User as DjangoUser
from django.core.urlresolvers import reverse
from rest_framework import serializers from rest_framework import serializers
...@@ -15,7 +19,7 @@ from lms.lib.comment_client.user import User as CommentClientUser ...@@ -15,7 +19,7 @@ from lms.lib.comment_client.user import User as CommentClientUser
from openedx.core.djangoapps.course_groups.cohorts import get_cohort_names from openedx.core.djangoapps.course_groups.cohorts import get_cohort_names
def get_context(course, requester, thread=None): def get_context(course, request, thread=None):
""" """
Returns a context appropriate for use with ThreadSerializer or Returns a context appropriate for use with ThreadSerializer or
(if thread is provided) CommentSerializer. (if thread is provided) CommentSerializer.
...@@ -34,8 +38,10 @@ def get_context(course, requester, thread=None): ...@@ -34,8 +38,10 @@ def get_context(course, requester, thread=None):
for role in Role.objects.filter(name=FORUM_ROLE_COMMUNITY_TA, course_id=course.id) for role in Role.objects.filter(name=FORUM_ROLE_COMMUNITY_TA, course_id=course.id)
for user in role.users.all() for user in role.users.all()
} }
requester = request.user
return { return {
# For now, the only groups are cohorts # For now, the only groups are cohorts
"request": request,
"group_ids_to_names": get_cohort_names(course), "group_ids_to_names": get_cohort_names(course),
"is_requester_privileged": requester.id in staff_user_ids or requester.id in ta_user_ids, "is_requester_privileged": requester.id in staff_user_ids or requester.id in ta_user_ids,
"staff_user_ids": staff_user_ids, "staff_user_ids": staff_user_ids,
...@@ -137,6 +143,9 @@ class ThreadSerializer(_ContentSerializer): ...@@ -137,6 +143,9 @@ class ThreadSerializer(_ContentSerializer):
following = serializers.SerializerMethodField("get_following") following = serializers.SerializerMethodField("get_following")
comment_count = serializers.IntegerField(source="comments_count") comment_count = serializers.IntegerField(source="comments_count")
unread_comment_count = serializers.IntegerField(source="unread_comments_count") unread_comment_count = serializers.IntegerField(source="unread_comments_count")
comment_list_url = serializers.SerializerMethodField("get_comment_list_url")
endorsed_comment_list_url = serializers.SerializerMethodField("get_endorsed_comment_list_url")
non_endorsed_comment_list_url = serializers.SerializerMethodField("get_non_endorsed_comment_list_url")
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ThreadSerializer, self).__init__(*args, **kwargs) super(ThreadSerializer, self).__init__(*args, **kwargs)
...@@ -155,6 +164,32 @@ class ThreadSerializer(_ContentSerializer): ...@@ -155,6 +164,32 @@ class ThreadSerializer(_ContentSerializer):
""" """
return obj["id"] in self.context["cc_requester"]["subscribed_thread_ids"] return obj["id"] in self.context["cc_requester"]["subscribed_thread_ids"]
def get_comment_list_url(self, obj, endorsed=None):
"""
Returns the URL to retrieve the thread's comments, optionally including
the endorsed query parameter.
"""
if (
(obj["thread_type"] == "question" and endorsed is None) or
(obj["thread_type"] == "discussion" and endorsed is not None)
):
return None
path = reverse("comment-list")
query_dict = {"thread_id": obj["id"]}
if endorsed is not None:
query_dict["endorsed"] = endorsed
return self.context["request"].build_absolute_uri(
urlunparse(("", "", path, "", urlencode(query_dict), ""))
)
def get_endorsed_comment_list_url(self, obj):
"""Returns the URL to retrieve the thread's endorsed comments."""
return self.get_comment_list_url(obj, endorsed=True)
def get_non_endorsed_comment_list_url(self, obj):
"""Returns the URL to retrieve the thread's non-endorsed comments."""
return self.get_comment_list_url(obj, endorsed=False)
class CommentSerializer(_ContentSerializer): class CommentSerializer(_ContentSerializer):
""" """
......
...@@ -32,6 +32,7 @@ from django_comment_common.models import ( ...@@ -32,6 +32,7 @@ from django_comment_common.models import (
from openedx.core.djangoapps.course_groups.models import CourseUserGroupPartitionGroup from openedx.core.djangoapps.course_groups.models import CourseUserGroupPartitionGroup
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
from student.tests.factories import CourseEnrollmentFactory, UserFactory from student.tests.factories import CourseEnrollmentFactory, UserFactory
from util.testing import UrlResetMixin
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
...@@ -356,8 +357,9 @@ class GetCourseTopicsTest(ModuleStoreTestCase): ...@@ -356,8 +357,9 @@ class GetCourseTopicsTest(ModuleStoreTestCase):
@ddt.ddt @ddt.ddt
class GetThreadListTest(CommentsServiceMockMixin, ModuleStoreTestCase): class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestCase):
"""Test for get_thread_list""" """Test for get_thread_list"""
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self): def setUp(self):
super(GetThreadListTest, self).setUp() super(GetThreadListTest, self).setUp()
httpretty.reset() httpretty.reset()
...@@ -485,6 +487,9 @@ class GetThreadListTest(CommentsServiceMockMixin, ModuleStoreTestCase): ...@@ -485,6 +487,9 @@ class GetThreadListTest(CommentsServiceMockMixin, ModuleStoreTestCase):
"vote_count": 4, "vote_count": 4,
"comment_count": 5, "comment_count": 5,
"unread_comment_count": 3, "unread_comment_count": 3,
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread_id_0",
"endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None,
}, },
{ {
"id": "test_thread_id_1", "id": "test_thread_id_1",
...@@ -507,6 +512,13 @@ class GetThreadListTest(CommentsServiceMockMixin, ModuleStoreTestCase): ...@@ -507,6 +512,13 @@ class GetThreadListTest(CommentsServiceMockMixin, ModuleStoreTestCase):
"vote_count": 9, "vote_count": 9,
"comment_count": 18, "comment_count": 18,
"unread_comment_count": 0, "unread_comment_count": 0,
"comment_list_url": None,
"endorsed_comment_list_url": (
"http://testserver/api/discussion/v1/comments/?thread_id=test_thread_id_1&endorsed=True"
),
"non_endorsed_comment_list_url": (
"http://testserver/api/discussion/v1/comments/?thread_id=test_thread_id_1&endorsed=False"
),
}, },
] ]
self.assertEqual( self.assertEqual(
......
...@@ -5,6 +5,9 @@ import itertools ...@@ -5,6 +5,9 @@ import itertools
import ddt import ddt
import httpretty import httpretty
import mock
from django.test.client import RequestFactory
from discussion_api.serializers import CommentSerializer, ThreadSerializer, get_context from discussion_api.serializers import CommentSerializer, ThreadSerializer, get_context
from discussion_api.tests.utils import ( from discussion_api.tests.utils import (
...@@ -20,13 +23,15 @@ from django_comment_common.models import ( ...@@ -20,13 +23,15 @@ from django_comment_common.models import (
Role, Role,
) )
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from util.testing import UrlResetMixin
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
@ddt.ddt @ddt.ddt
class SerializerTestMixin(CommentsServiceMockMixin): class SerializerTestMixin(CommentsServiceMockMixin, UrlResetMixin):
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self): def setUp(self):
super(SerializerTestMixin, self).setUp() super(SerializerTestMixin, self).setUp()
httpretty.reset() httpretty.reset()
...@@ -35,6 +40,8 @@ class SerializerTestMixin(CommentsServiceMockMixin): ...@@ -35,6 +40,8 @@ class SerializerTestMixin(CommentsServiceMockMixin):
self.maxDiff = None # pylint: disable=invalid-name self.maxDiff = None # pylint: disable=invalid-name
self.user = UserFactory.create() self.user = UserFactory.create()
self.register_get_user_response(self.user) self.register_get_user_response(self.user)
self.request = RequestFactory().get("/dummy")
self.request.user = self.user
self.course = CourseFactory.create() self.course = CourseFactory.create()
self.author = UserFactory.create() self.author = UserFactory.create()
...@@ -137,7 +144,7 @@ class ThreadSerializerTest(SerializerTestMixin, ModuleStoreTestCase): ...@@ -137,7 +144,7 @@ class ThreadSerializerTest(SerializerTestMixin, ModuleStoreTestCase):
Create a serializer with an appropriate context and use it to serialize Create a serializer with an appropriate context and use it to serialize
the given thread, returning the result. the given thread, returning the result.
""" """
return ThreadSerializer(thread, context=get_context(self.course, self.user)).data return ThreadSerializer(thread, context=get_context(self.course, self.request)).data
def test_basic(self): def test_basic(self):
thread = { thread = {
...@@ -182,9 +189,25 @@ class ThreadSerializerTest(SerializerTestMixin, ModuleStoreTestCase): ...@@ -182,9 +189,25 @@ class ThreadSerializerTest(SerializerTestMixin, ModuleStoreTestCase):
"vote_count": 4, "vote_count": 4,
"comment_count": 5, "comment_count": 5,
"unread_comment_count": 3, "unread_comment_count": 3,
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread",
"endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None,
} }
self.assertEqual(self.serialize(thread), expected) self.assertEqual(self.serialize(thread), expected)
thread["thread_type"] = "question"
expected.update({
"type": "question",
"comment_list_url": None,
"endorsed_comment_list_url": (
"http://testserver/api/discussion/v1/comments/?thread_id=test_thread&endorsed=True"
),
"non_endorsed_comment_list_url": (
"http://testserver/api/discussion/v1/comments/?thread_id=test_thread&endorsed=False"
),
})
self.assertEqual(self.serialize(thread), expected)
def test_group(self): def test_group(self):
cohort = CohortFactory.create(course_id=self.course.id) cohort = CohortFactory.create(course_id=self.course.id)
serialized = self.serialize(self.make_cs_content({"group_id": cohort.id})) serialized = self.serialize(self.make_cs_content({"group_id": cohort.id}))
...@@ -227,7 +250,7 @@ class CommentSerializerTest(SerializerTestMixin, ModuleStoreTestCase): ...@@ -227,7 +250,7 @@ class CommentSerializerTest(SerializerTestMixin, ModuleStoreTestCase):
Create a serializer with an appropriate context and use it to serialize Create a serializer with an appropriate context and use it to serialize
the given comment, returning the result. the given comment, returning the result.
""" """
context = get_context(self.course, self.user, make_minimal_cs_thread(thread_data)) context = get_context(self.course, self.request, make_minimal_cs_thread(thread_data))
return CommentSerializer(comment, context=context).data return CommentSerializer(comment, context=context).data
def test_basic(self): def test_basic(self):
......
...@@ -158,6 +158,9 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -158,6 +158,9 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"vote_count": 4, "vote_count": 4,
"comment_count": 5, "comment_count": 5,
"unread_comment_count": 3, "unread_comment_count": 3,
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread",
"endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None,
}] }]
self.register_get_threads_response(source_threads, page=1, num_pages=2) self.register_get_threads_response(source_threads, page=1, num_pages=2)
response = self.client.get(self.url, {"course_id": unicode(self.course.id)}) response = self.client.get(self.url, {"course_id": unicode(self.course.id)})
......
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