From c83f56153ef9bfcc522f8e4d28cf8fbee459ab3b Mon Sep 17 00:00:00 2001 From: Greg Price Date: Wed, 20 May 2015 17:31:18 -0400 Subject: [PATCH] Add comment list URLs to discussion api threads --- lms/djangoapps/discussion_api/api.py | 4 +- lms/djangoapps/discussion_api/serializers.py | 37 ++++++++++++++++++- .../discussion_api/tests/test_api.py | 14 ++++++- .../discussion_api/tests/test_serializers.py | 29 +++++++++++++-- .../discussion_api/tests/test_views.py | 3 ++ 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/lms/djangoapps/discussion_api/api.py b/lms/djangoapps/discussion_api/api.py index 5b28eba8cf..d73b53d540 100644 --- a/lms/djangoapps/discussion_api/api.py +++ b/lms/djangoapps/discussion_api/api.py @@ -107,7 +107,7 @@ def get_thread_list(request, course_key, page, page_size): discussion_api.views.ThreadViewSet for more detail. """ 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({ "course_id": unicode(course.id), "group_id": ( @@ -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 = _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 if not context["is_requester_privileged"] and cc_thread["group_id"]: diff --git a/lms/djangoapps/discussion_api/serializers.py b/lms/djangoapps/discussion_api/serializers.py index bd595f351a..779158f6f1 100644 --- a/lms/djangoapps/discussion_api/serializers.py +++ b/lms/djangoapps/discussion_api/serializers.py @@ -1,7 +1,11 @@ """ Discussion API serializers """ +from urllib import urlencode +from urlparse import urlunparse + from django.contrib.auth.models import User as DjangoUser +from django.core.urlresolvers import reverse from rest_framework import serializers @@ -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 -def get_context(course, requester, thread=None): +def get_context(course, request, thread=None): """ Returns a context appropriate for use with ThreadSerializer or (if thread is provided) CommentSerializer. @@ -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 user in role.users.all() } + requester = request.user return { # For now, the only groups are cohorts + "request": request, "group_ids_to_names": get_cohort_names(course), "is_requester_privileged": requester.id in staff_user_ids or requester.id in ta_user_ids, "staff_user_ids": staff_user_ids, @@ -137,6 +143,9 @@ class ThreadSerializer(_ContentSerializer): following = serializers.SerializerMethodField("get_following") comment_count = serializers.IntegerField(source="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): super(ThreadSerializer, self).__init__(*args, **kwargs) @@ -155,6 +164,32 @@ class ThreadSerializer(_ContentSerializer): """ 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): """ diff --git a/lms/djangoapps/discussion_api/tests/test_api.py b/lms/djangoapps/discussion_api/tests/test_api.py index 03a4268ad8..bfff7153c0 100644 --- a/lms/djangoapps/discussion_api/tests/test_api.py +++ b/lms/djangoapps/discussion_api/tests/test_api.py @@ -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.tests.helpers import CohortFactory from student.tests.factories import CourseEnrollmentFactory, UserFactory +from util.testing import UrlResetMixin from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -356,8 +357,9 @@ class GetCourseTopicsTest(ModuleStoreTestCase): @ddt.ddt -class GetThreadListTest(CommentsServiceMockMixin, ModuleStoreTestCase): +class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestCase): """Test for get_thread_list""" + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(GetThreadListTest, self).setUp() httpretty.reset() @@ -485,6 +487,9 @@ class GetThreadListTest(CommentsServiceMockMixin, ModuleStoreTestCase): "vote_count": 4, "comment_count": 5, "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", @@ -507,6 +512,13 @@ class GetThreadListTest(CommentsServiceMockMixin, ModuleStoreTestCase): "vote_count": 9, "comment_count": 18, "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( diff --git a/lms/djangoapps/discussion_api/tests/test_serializers.py b/lms/djangoapps/discussion_api/tests/test_serializers.py index b0d4d27de8..3b7c2c12cb 100644 --- a/lms/djangoapps/discussion_api/tests/test_serializers.py +++ b/lms/djangoapps/discussion_api/tests/test_serializers.py @@ -5,6 +5,9 @@ import itertools import ddt import httpretty +import mock + +from django.test.client import RequestFactory from discussion_api.serializers import CommentSerializer, ThreadSerializer, get_context from discussion_api.tests.utils import ( @@ -20,13 +23,15 @@ from django_comment_common.models import ( Role, ) from student.tests.factories import UserFactory +from util.testing import UrlResetMixin from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory @ddt.ddt -class SerializerTestMixin(CommentsServiceMockMixin): +class SerializerTestMixin(CommentsServiceMockMixin, UrlResetMixin): + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(SerializerTestMixin, self).setUp() httpretty.reset() @@ -35,6 +40,8 @@ class SerializerTestMixin(CommentsServiceMockMixin): self.maxDiff = None # pylint: disable=invalid-name self.user = UserFactory.create() self.register_get_user_response(self.user) + self.request = RequestFactory().get("/dummy") + self.request.user = self.user self.course = CourseFactory.create() self.author = UserFactory.create() @@ -137,7 +144,7 @@ class ThreadSerializerTest(SerializerTestMixin, ModuleStoreTestCase): Create a serializer with an appropriate context and use it to serialize 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): thread = { @@ -182,9 +189,25 @@ class ThreadSerializerTest(SerializerTestMixin, ModuleStoreTestCase): "vote_count": 4, "comment_count": 5, "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) + 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): cohort = CohortFactory.create(course_id=self.course.id) serialized = self.serialize(self.make_cs_content({"group_id": cohort.id})) @@ -227,7 +250,7 @@ class CommentSerializerTest(SerializerTestMixin, ModuleStoreTestCase): Create a serializer with an appropriate context and use it to serialize 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 def test_basic(self): diff --git a/lms/djangoapps/discussion_api/tests/test_views.py b/lms/djangoapps/discussion_api/tests/test_views.py index ec5bed1b9a..fca7cbef3b 100644 --- a/lms/djangoapps/discussion_api/tests/test_views.py +++ b/lms/djangoapps/discussion_api/tests/test_views.py @@ -158,6 +158,9 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): "vote_count": 4, "comment_count": 5, "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) response = self.client.get(self.url, {"course_id": unicode(self.course.id)})