Move comment_client into django_comment_common

This commit is contained in:
Nimisha Asthagiri
2019-04-29 00:26:02 -04:00
parent 38aeafefb0
commit 16edae0822
33 changed files with 76 additions and 74 deletions

View File

@@ -14,7 +14,6 @@ Discussions related functionality is scattered across a number of places and sho
* ``lms/djangoapps/django_comment_client``
* ``lms/djangoapps/notification_prefs``
* ``lms/djangoapps/notifier_api``
* ``lms/lib/comment_client``
* ``openedx/core/lib/xblock_builtin/xblock_discussion``
Ideally, what we want in the long term is for all of this extracted into a new repository that holds the code for both the inline discussion XBlock as well as all the Django apps. Use of the notifier API should be replaced with edx-ace.

View File

@@ -11,14 +11,14 @@ from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from celery_utils.logged_task import LoggedTask
import django_comment_common.comment_client as cc
from django_comment_common.models import DiscussionsIdMapping
from edx_ace import ace
from edx_ace.utils import date
from edx_ace.recipient import Recipient
from eventtracking import tracker
from opaque_keys.edx.keys import CourseKey
from lms.djangoapps.django_comment_client.utils import permalink, get_accessible_discussion_xblocks_by_course_id
import lms.lib.comment_client as cc
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.ace_common.template_context import get_base_template_context

View File

@@ -9,7 +9,7 @@ import ddt
from django.contrib.sites.models import Site
import mock
import lms.lib.comment_client as cc
import django_comment_common.comment_client as cc
from django_comment_common.models import ForumsConfig
from django_comment_common.signals import comment_created

View File

@@ -29,6 +29,7 @@ from django_comment_client.tests.utils import (
topic_name_to_id
)
from django_comment_client.utils import strip_none
from django_comment_common.comment_client.utils import CommentClientPaginatedResult
from django_comment_common.models import (
CourseDiscussionSettings,
ForumsConfig,
@@ -40,7 +41,6 @@ from lms.djangoapps.discussion import views
from lms.djangoapps.discussion.views import _get_discussion_default_topic_id
from lms.djangoapps.discussion.views import course_discussions_settings_handler
from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMembershipFactory
from lms.lib.comment_client.utils import CommentClientPaginatedResult
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
from openedx.core.djangoapps.course_groups.tests.helpers import config_course_cohorts
from openedx.core.djangoapps.course_groups.tests.test_views import CohortViewsTestCase
@@ -559,7 +559,7 @@ class SingleCohortedThreadTestCase(CohortedTestCase):
self.assertRegexpMatches(html, r'"group_name": "student_cohort"')
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class SingleThreadAccessTestCase(CohortedTestCase):
def call_view(self, mock_request, commentable_id, user, group_id, thread_group_id=None, pass_group_id=True):
@@ -647,7 +647,7 @@ class SingleThreadAccessTestCase(CohortedTestCase):
self.assertEqual(resp.status_code, 200)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class SingleThreadGroupIdTestCase(CohortedTestCase, GroupIdAssertionMixin):
cs_endpoint = "/threads/dummy_thread_id"
@@ -888,7 +888,7 @@ class SingleThreadContentGroupTestCase(ForumsEnableMixin, UrlResetMixin, Content
self.assert_can_access(self.beta_user, self.alpha_module.discussion_id, thread_id, True)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class InlineDiscussionContextTestCase(ForumsEnableMixin, ModuleStoreTestCase):
def setUp(self):
@@ -925,7 +925,7 @@ class InlineDiscussionContextTestCase(ForumsEnableMixin, ModuleStoreTestCase):
self.assertEqual(json_response['discussion_data'][0]['context'], ThreadContext.STANDALONE)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class InlineDiscussionGroupIdTestCase(
CohortedTestCase,
CohortedTopicGroupIdTestMixin,
@@ -976,7 +976,7 @@ class InlineDiscussionGroupIdTestCase(
)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class ForumFormDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupIdTestMixin):
cs_endpoint = "/threads"
@@ -1022,7 +1022,7 @@ class ForumFormDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupIdT
)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupIdTestMixin):
cs_endpoint = "/active_threads"
@@ -1183,7 +1183,7 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
verify_group_id_not_present(profiled_user=self.moderator, pass_group_id=False)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class FollowedThreadsDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupIdTestMixin):
cs_endpoint = "/subscribed_threads"
@@ -1220,7 +1220,7 @@ class FollowedThreadsDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGr
)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class InlineDiscussionTestCase(ForumsEnableMixin, ModuleStoreTestCase):
def setUp(self):
@@ -1480,7 +1480,7 @@ class InlineDiscussionUnicodeTestCase(ForumsEnableMixin, SharedModuleStoreTestCa
def setUp(self):
super(InlineDiscussionUnicodeTestCase, self).setUp()
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
mock_request.side_effect = make_mock_request_impl(course=self.course, text=text)
request = RequestFactory().get("dummy_url")
@@ -1513,7 +1513,7 @@ class ForumFormDiscussionUnicodeTestCase(ForumsEnableMixin, SharedModuleStoreTes
def setUp(self):
super(ForumFormDiscussionUnicodeTestCase, self).setUp()
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
mock_request.side_effect = make_mock_request_impl(course=self.course, text=text)
request = RequestFactory().get("dummy_url")
@@ -1528,7 +1528,7 @@ class ForumFormDiscussionUnicodeTestCase(ForumsEnableMixin, SharedModuleStoreTes
@ddt.ddt
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class ForumDiscussionXSSTestCase(ForumsEnableMixin, UrlResetMixin, ModuleStoreTestCase):
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
@@ -1601,7 +1601,7 @@ class ForumDiscussionSearchUnicodeTestCase(ForumsEnableMixin, SharedModuleStoreT
def setUp(self):
super(ForumDiscussionSearchUnicodeTestCase, self).setUp()
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
mock_request.side_effect = make_mock_request_impl(course=self.course, text=text)
data = {
@@ -1637,7 +1637,7 @@ class SingleThreadUnicodeTestCase(ForumsEnableMixin, SharedModuleStoreTestCase,
def setUp(self):
super(SingleThreadUnicodeTestCase, self).setUp()
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
thread_id = "test_thread_id"
mock_request.side_effect = make_mock_request_impl(course=self.course, text=text, thread_id=thread_id)
@@ -1670,7 +1670,7 @@ class UserProfileUnicodeTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, U
def setUp(self):
super(UserProfileUnicodeTestCase, self).setUp()
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
mock_request.side_effect = make_mock_request_impl(course=self.course, text=text)
request = RequestFactory().get("dummy_url")
@@ -1702,7 +1702,7 @@ class FollowedThreadsUnicodeTestCase(ForumsEnableMixin, SharedModuleStoreTestCas
def setUp(self):
super(FollowedThreadsUnicodeTestCase, self).setUp()
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
mock_request.side_effect = make_mock_request_impl(course=self.course, text=text)
request = RequestFactory().get("dummy_url")
@@ -1729,7 +1729,7 @@ class EnrollmentTestCase(ForumsEnableMixin, ModuleStoreTestCase):
self.student = UserFactory.create()
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def test_unenrolled(self, mock_request):
mock_request.side_effect = make_mock_request_impl(course=self.course, text='dummy')
request = RequestFactory().get('dummy_url')
@@ -2111,7 +2111,7 @@ class ThreadViewedEventTestCase(EventTestMixin, ForumsEnableMixin, UrlResetMixin
self.client.login(username=self.student.username, password=PASSWORD)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
@patch('lms.lib.comment_client.utils.perform_request')
@patch('django_comment_common.comment_client.utils.perform_request')
def test_thread_viewed_event(self, mock_perform_request):
mock_perform_request.side_effect = make_mock_perform_request_impl(
course=self.course,

View File

@@ -24,8 +24,8 @@ from rest_framework import status
from web_fragments.fragment import Fragment
import django_comment_client.utils as utils
import django_comment_common.comment_client as cc
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
import lms.lib.comment_client as cc
from courseware.access import has_access
from courseware.courses import get_course_with_access
from courseware.views.views import CourseTabView

View File

@@ -26,6 +26,9 @@ from discussion_api.permissions import (
from discussion_api.serializers import CommentSerializer, DiscussionTopicSerializer, ThreadSerializer, get_context
from django_comment_client.base.views import track_comment_created_event, track_thread_created_event, track_voted_event
from django_comment_client.utils import get_accessible_discussion_xblocks, get_group_id_for_user, is_commentable_divided
from django_comment_common.comment_client.comment import Comment
from django_comment_common.comment_client.thread import Thread
from django_comment_common.comment_client.utils import CommentClientRequestError
from django_comment_common.signals import (
comment_created,
comment_deleted,
@@ -39,9 +42,6 @@ from django_comment_common.signals import (
from django_comment_common.utils import get_course_discussion_settings
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect
from lms.djangoapps.discussion_api.pagination import DiscussionAPIPagination
from lms.lib.comment_client.comment import Comment
from lms.lib.comment_client.thread import Thread
from lms.lib.comment_client.utils import CommentClientRequestError
from openedx.core.djangoapps.user_api.accounts.views import AccountViewSet
from openedx.core.lib.exceptions import CourseNotFoundError, DiscussionNotFoundError, PageNotFoundError

View File

@@ -1,8 +1,8 @@
"""
Discussion API permission logic
"""
from lms.lib.comment_client.comment import Comment
from lms.lib.comment_client.thread import Thread
from django_comment_common.comment_client.comment import Comment
from django_comment_common.comment_client.thread import Thread
def _is_author(cc_content, context):

View File

@@ -19,12 +19,12 @@ from django_comment_common.models import (
FORUM_ROLE_MODERATOR,
Role,
)
from django_comment_common.comment_client.comment import Comment
from django_comment_common.comment_client.thread import Thread
from django_comment_common.comment_client.user import User as CommentClientUser
from django_comment_common.comment_client.utils import CommentClientRequestError
from django_comment_common.utils import get_course_discussion_settings
from lms.djangoapps.django_comment_client.utils import course_discussion_division_enabled, get_group_names_by_id
from lms.lib.comment_client.comment import Comment
from lms.lib.comment_client.thread import Thread
from lms.lib.comment_client.user import User as CommentClientUser
from lms.lib.comment_client.utils import CommentClientRequestError
from student.models import get_user_by_username_or_email

View File

@@ -11,9 +11,9 @@ from discussion_api.permissions import (
get_initializable_comment_fields,
get_initializable_thread_fields
)
from lms.lib.comment_client.comment import Comment
from lms.lib.comment_client.thread import Thread
from lms.lib.comment_client.user import User
from django_comment_common.comment_client.comment import Comment
from django_comment_common.comment_client.thread import Thread
from django_comment_common.comment_client.user import User
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory

View File

@@ -12,6 +12,8 @@ from django.test.client import RequestFactory
from discussion_api.serializers import CommentSerializer, ThreadSerializer, get_context
from discussion_api.tests.utils import CommentsServiceMockMixin, make_minimal_cs_comment, make_minimal_cs_thread
from django_comment_client.tests.utils import ForumsEnableMixin
from django_comment_common.comment_client.comment import Comment
from django_comment_common.comment_client.thread import Thread
from django_comment_common.models import (
FORUM_ROLE_ADMINISTRATOR,
FORUM_ROLE_COMMUNITY_TA,
@@ -19,8 +21,6 @@ from django_comment_common.models import (
FORUM_ROLE_STUDENT,
Role
)
from lms.lib.comment_client.comment import Comment
from lms.lib.comment_client.thread import Thread
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
from student.tests.factories import UserFactory
from util.testing import UrlResetMixin

View File

@@ -18,11 +18,11 @@ from rest_framework.viewsets import ViewSet
from six import text_type
from django_comment_client.utils import available_division_schemes
from django_comment_common import comment_client
from django_comment_common.models import Role
from django_comment_common.utils import get_course_discussion_settings, set_course_discussion_settings
from instructor.access import update_forum_role
from lms.lib import comment_client
from discussion.views import get_divided_discussions
from discussion_api.api import (
create_comment,

View File

@@ -26,6 +26,7 @@ from django_comment_client.tests.group_id import (
)
from django_comment_client.tests.unicode import UnicodeTestMixin
from django_comment_client.tests.utils import CohortedTestCase, ForumsEnableMixin
from django_comment_common.comment_client import Thread
from django_comment_common.models import (
assign_role,
CourseDiscussionSettings,
@@ -34,7 +35,6 @@ from django_comment_common.models import (
)
from django_comment_common.utils import ThreadContext, seed_permissions_roles, set_course_discussion_settings
from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMembershipFactory
from lms.lib.comment_client import Thread
from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES
@@ -72,7 +72,7 @@ class MockRequestSetupMixin(object):
mock_request.return_value = self._create_response_mock(data)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class CreateThreadGroupIdTestCase(
MockRequestSetupMixin,
CohortedTestCase,
@@ -107,7 +107,7 @@ class CreateThreadGroupIdTestCase(
self._assert_json_response_contains_group_info(response)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
@disable_signal(views, 'thread_edited')
@disable_signal(views, 'thread_voted')
@disable_signal(views, 'thread_deleted')
@@ -365,7 +365,7 @@ class ViewsTestCaseMixin(object):
@ddt.ddt
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
@disable_signal(views, 'thread_created')
@disable_signal(views, 'thread_edited')
class ViewsQueryCountTestCase(
@@ -418,7 +418,7 @@ class ViewsQueryCountTestCase(
@ddt.ddt
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
class ViewsTestCase(
ForumsEnableMixin,
UrlResetMixin,
@@ -1062,7 +1062,7 @@ class ViewsTestCase(
self.assertEqual(response.status_code, 200)
@patch("lms.lib.comment_client.utils.requests.request", autospec=True)
@patch("django_comment_common.comment_client.utils.requests.request", autospec=True)
@disable_signal(views, 'comment_endorsed')
class ViewPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStoreTestCase, MockRequestSetupMixin):
@@ -1191,7 +1191,7 @@ class CreateThreadUnicodeTestCase(
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request,):
"""
Test to make sure unicode data in a thread doesn't break it.
@@ -1234,7 +1234,7 @@ class UpdateThreadUnicodeTestCase(
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('django_comment_client.utils.get_discussion_categories_ids', return_value=["test_commentable"])
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request, mock_get_discussion_id_map):
self._set_mock_request_data(mock_request, {
"user_id": str(self.student.id),
@@ -1275,7 +1275,7 @@ class CreateCommentUnicodeTestCase(
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
commentable_id = "non_team_dummy_id"
self._set_mock_request_data(mock_request, {
@@ -1322,7 +1322,7 @@ class UpdateCommentUnicodeTestCase(
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
self._set_mock_request_data(mock_request, {
"user_id": str(self.student.id),
@@ -1362,7 +1362,7 @@ class CreateSubCommentUnicodeTestCase(
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
"""
Create a comment with unicode in it.
@@ -1390,7 +1390,7 @@ class CreateSubCommentUnicodeTestCase(
@ddt.ddt
@patch("lms.lib.comment_client.utils.requests.request", autospec=True)
@patch("django_comment_common.comment_client.utils.requests.request", autospec=True)
@disable_signal(views, 'thread_voted')
@disable_signal(views, 'thread_edited')
@disable_signal(views, 'comment_created')
@@ -1757,7 +1757,7 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
CourseAccessRoleFactory(course_id=cls.course.id, user=cls.student, role='Wizard')
@patch('eventtracking.tracker.emit')
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def test_thread_created_event(self, __, mock_emit):
request = RequestFactory().post(
"dummy_url", {
@@ -1786,7 +1786,7 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
self.assertEquals(event['anonymous_to_peers'], False)
@patch('eventtracking.tracker.emit')
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def test_response_event(self, mock_request, mock_emit):
"""
Check to make sure an event is fired when a user responds to a thread.
@@ -1812,7 +1812,7 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
self.assertEqual(event['options']['followed'], True)
@patch('eventtracking.tracker.emit')
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def test_comment_event(self, mock_request, mock_emit):
"""
Ensure an event is fired when someone comments on a response.
@@ -1839,7 +1839,7 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
self.assertEqual(event['options']['followed'], False)
@patch('eventtracking.tracker.emit')
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
@ddt.data((
'create_thread',
'edx.forum.thread.created', {
@@ -1891,7 +1891,7 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
)
@ddt.unpack
@patch('eventtracking.tracker.emit')
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def test_thread_voted_event(self, view_name, obj_id_name, obj_type, mock_request, mock_emit):
undo = view_name.startswith('undo')
@@ -1953,7 +1953,7 @@ class UsersEndpointTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockRe
request.view_name = "users"
return views.users(request, course_id=text_type(course_id))
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def test_finds_exact_match(self, mock_request):
self.set_post_counts(mock_request)
response = self.make_request(username="other")
@@ -1963,7 +1963,7 @@ class UsersEndpointTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockRe
[{"id": self.other_user.id, "username": self.other_user.username}]
)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def test_finds_no_match(self, mock_request):
self.set_post_counts(mock_request)
response = self.make_request(username="othor")
@@ -2000,7 +2000,7 @@ class UsersEndpointTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockRe
self.assertIn("errors", content)
self.assertNotIn("users", content)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
@patch('django_comment_common.comment_client.utils.requests.request', autospec=True)
def test_requires_matched_user_has_forum_content(self, mock_request):
self.set_post_counts(mock_request, 0, 0)
response = self.make_request(username="other")

View File

@@ -21,7 +21,7 @@ from opaque_keys.edx.keys import CourseKey
from six import text_type
import django_comment_client.settings as cc_settings
import lms.lib.comment_client as cc
import django_comment_common.comment_client as cc
from courseware.access import has_access
from courseware.courses import get_course_by_id, get_course_overview_with_access, get_course_with_access
from django_comment_client.permissions import check_permissions_by_view, get_team, has_permission

View File

@@ -3,7 +3,7 @@ Reload forum (comment client) users from existing users.
"""
from __future__ import print_function
import lms.lib.comment_client as cc
import django_comment_common.comment_client as cc
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand

View File

@@ -3,7 +3,7 @@ One-off script to sync all user information to the
discussion service (later info will be synced automatically)
"""
import lms.lib.comment_client as cc
import django_comment_common.comment_client as cc
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand

View File

@@ -4,7 +4,7 @@ import logging
from six import text_type
from django_comment_client.utils import JsonError
from lms.lib.comment_client import CommentClientRequestError
from django_comment_common.comment_client import CommentClientRequestError
log = logging.getLogger(__name__)

View File

@@ -8,10 +8,10 @@ from types import NoneType
from edx_django_utils.cache import DEFAULT_REQUEST_CACHE
from opaque_keys.edx.keys import CourseKey
from django_comment_common.comment_client import Thread
from django_comment_common.models import CourseDiscussionSettings, all_permissions_for_user_in_course
from django_comment_common.utils import get_course_discussion_settings
from lms.djangoapps.teams.models import CourseTeam
from lms.lib.comment_client import Thread
from openedx.core.lib.cache_utils import request_cached

View File

@@ -5,7 +5,7 @@ from django.test import TestCase
from six import text_type
import django_comment_client.middleware as middleware
import lms.lib.comment_client
import django_comment_common.comment_client
class AjaxExceptionTestCase(TestCase):
@@ -15,9 +15,9 @@ class AjaxExceptionTestCase(TestCase):
self.a = middleware.AjaxExceptionMiddleware()
self.request1 = django.http.HttpRequest()
self.request0 = django.http.HttpRequest()
self.exception1 = lms.lib.comment_client.CommentClientRequestError('{}', 401)
self.exception2 = lms.lib.comment_client.CommentClientRequestError('Foo!', 404)
self.exception0 = lms.lib.comment_client.CommentClient500Error("Holy crap the server broke!")
self.exception1 = django_comment_common.comment_client.CommentClientRequestError('{}', 401)
self.exception2 = django_comment_common.comment_client.CommentClientRequestError('Foo!', 404)
self.exception0 = django_comment_common.comment_client.CommentClient500Error("Holy crap the server broke!")
self.request1.META['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest"
self.request0.META['HTTP_X_REQUESTED_WITH'] = "SHADOWFAX"

View File

@@ -21,6 +21,7 @@ from django_comment_client.constants import TYPE_ENTRY, TYPE_SUBCATEGORY
from django_comment_client.tests.factories import RoleFactory
from django_comment_client.tests.unicode import UnicodeTestMixin
from django_comment_client.tests.utils import config_course_discussions, topic_name_to_id
from django_comment_common.comment_client.utils import CommentClientMaintenanceError, perform_request
from django_comment_common.models import (
CourseDiscussionSettings,
ForumsConfig,
@@ -33,7 +34,6 @@ from django_comment_common.utils import (
set_course_discussion_settings
)
from lms.djangoapps.teams.tests.factories import CourseTeamFactory
from lms.lib.comment_client.utils import CommentClientMaintenanceError, perform_request
from openedx.core.djangoapps.course_groups import cohorts
from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory, config_course_cohorts

View File

@@ -1 +0,0 @@
See ``lms/djangoapps/discussion/README.rst``

View File

@@ -1,5 +0,0 @@
from .comment_client import *
from .utils import (
CommentClientError, CommentClientRequestError,
CommentClient500Error, CommentClientMaintenanceError
)

View File

@@ -1,110 +0,0 @@
from lms.lib.comment_client import models, settings
from .thread import Thread, _url_for_flag_abuse_thread, _url_for_unflag_abuse_thread
from .utils import CommentClientRequestError, perform_request
class Comment(models.Model):
accessible_fields = [
'id', 'body', 'anonymous', 'anonymous_to_peers', 'course_id',
'endorsed', 'parent_id', 'thread_id', 'username', 'votes', 'user_id',
'closed', 'created_at', 'updated_at', 'depth', 'at_position_list',
'type', 'commentable_id', 'abuse_flaggers', 'endorsement',
'child_count',
]
updatable_fields = [
'body', 'anonymous', 'anonymous_to_peers', 'course_id', 'closed',
'user_id', 'endorsed', 'endorsement_user_id',
]
initializable_fields = updatable_fields
metrics_tag_fields = ['course_id', 'endorsed', 'closed']
base_url = "{prefix}/comments".format(prefix=settings.PREFIX)
type = 'comment'
def __init__(self, *args, **kwargs):
super(Comment, self).__init__(*args, **kwargs)
self._cached_thread = None
@property
def thread(self):
if not self._cached_thread:
self._cached_thread = Thread(id=self.thread_id, type='thread')
return self._cached_thread
@property
def context(self):
"""Return the context of the thread which this comment belongs to."""
return self.thread.context
@classmethod
def url_for_comments(cls, params={}):
if params.get('parent_id'):
return _url_for_comment(params['parent_id'])
else:
return _url_for_thread_comments(params['thread_id'])
@classmethod
def url(cls, action, params={}):
if action in ['post']:
return cls.url_for_comments(params)
else:
return super(Comment, cls).url(action, params)
def flagAbuse(self, user, voteable):
if voteable.type == 'thread':
url = _url_for_flag_abuse_thread(voteable.id)
elif voteable.type == 'comment':
url = _url_for_flag_abuse_comment(voteable.id)
else:
raise CommentClientRequestError("Can only flag/unflag threads or comments")
params = {'user_id': user.id}
response = perform_request(
'put',
url,
params,
metric_tags=self._metric_tags,
metric_action='comment.abuse.flagged'
)
voteable._update_from_response(response)
def unFlagAbuse(self, user, voteable, removeAll):
if voteable.type == 'thread':
url = _url_for_unflag_abuse_thread(voteable.id)
elif voteable.type == 'comment':
url = _url_for_unflag_abuse_comment(voteable.id)
else:
raise CommentClientRequestError("Can flag/unflag for threads or comments")
params = {'user_id': user.id}
if removeAll:
params['all'] = True
response = perform_request(
'put',
url,
params,
metric_tags=self._metric_tags,
metric_action='comment.abuse.unflagged'
)
voteable._update_from_response(response)
def _url_for_thread_comments(thread_id):
return "{prefix}/threads/{thread_id}/comments".format(prefix=settings.PREFIX, thread_id=thread_id)
def _url_for_comment(comment_id):
return "{prefix}/comments/{comment_id}".format(prefix=settings.PREFIX, comment_id=comment_id)
def _url_for_flag_abuse_comment(comment_id):
return "{prefix}/comments/{comment_id}/abuse_flag".format(prefix=settings.PREFIX, comment_id=comment_id)
def _url_for_unflag_abuse_comment(comment_id):
return "{prefix}/comments/{comment_id}/abuse_unflag".format(prefix=settings.PREFIX, comment_id=comment_id)

View File

@@ -1,6 +0,0 @@
"""Import other classes here so they can be imported from here."""
# pylint: disable=unused-import
from .comment import Comment
from .commentable import Commentable
from .thread import Thread
from .user import User

View File

@@ -1,18 +0,0 @@
"""Provides base Commentable model class"""
from lms.lib.comment_client import models, settings
class Commentable(models.Model):
accessible_fields = ['id', 'commentable_id']
base_url = "{prefix}/commentables".format(prefix=settings.PREFIX)
type = 'commentable'
def retrieve(self, *args, **kwargs):
"""
Override default behavior because commentables don't actually exist in the comment service.
"""
self.attributes["commentable_id"] = self.attributes["id"]
self.retrieved = True
return self

View File

@@ -1,182 +0,0 @@
import logging
from .utils import CommentClientRequestError, extract, perform_request
log = logging.getLogger(__name__)
class Model(object):
accessible_fields = ['id']
updatable_fields = ['id']
initializable_fields = ['id']
base_url = None
default_retrieve_params = {}
metric_tag_fields = []
DEFAULT_ACTIONS_WITH_ID = ['get', 'put', 'delete']
DEFAULT_ACTIONS_WITHOUT_ID = ['get_all', 'post']
DEFAULT_ACTIONS = DEFAULT_ACTIONS_WITH_ID + DEFAULT_ACTIONS_WITHOUT_ID
def __init__(self, *args, **kwargs):
self.attributes = extract(kwargs, self.accessible_fields)
self.retrieved = False
def __getattr__(self, name):
if name == 'id':
return self.attributes.get('id', None)
try:
return self.attributes[name]
except KeyError:
if self.retrieved or self.id is None:
raise AttributeError(u"Field {0} does not exist".format(name))
self.retrieve()
return self.__getattr__(name)
def __setattr__(self, name, value):
if name == 'attributes' or name not in self.accessible_fields + self.updatable_fields:
super(Model, self).__setattr__(name, value)
else:
self.attributes[name] = value
def __getitem__(self, key):
if key not in self.accessible_fields:
raise KeyError(u"Field {0} does not exist".format(key))
return self.attributes.get(key)
def __setitem__(self, key, value):
if key not in self.accessible_fields + self.updatable_fields:
raise KeyError(u"Field {0} does not exist".format(key))
self.attributes.__setitem__(key, value)
def items(self, *args, **kwargs):
return self.attributes.items(*args, **kwargs)
def get(self, *args, **kwargs):
return self.attributes.get(*args, **kwargs)
def to_dict(self):
self.retrieve()
return self.attributes
def retrieve(self, *args, **kwargs):
if not self.retrieved:
self._retrieve(*args, **kwargs)
self.retrieved = True
return self
def _retrieve(self, *args, **kwargs):
url = self.url(action='get', params=self.attributes)
response = perform_request(
'get',
url,
self.default_retrieve_params,
metric_tags=self._metric_tags,
metric_action='model.retrieve'
)
self._update_from_response(response)
@property
def _metric_tags(self):
"""
Returns a list of tags to be used when recording metrics about this model.
Each field named in ``self.metric_tag_fields`` is used as a tag value,
under the key ``<class>.<metric_field>``. The tag model_class is used to
record the class name of the model.
"""
tags = [
u'{}.{}:{}'.format(self.__class__.__name__, attr, self[attr])
for attr in self.metric_tag_fields
if attr in self.attributes
]
tags.append(u'model_class:{}'.format(self.__class__.__name__))
return tags
@classmethod
def find(cls, id):
return cls(id=id)
def _update_from_response(self, response_data):
for k, v in response_data.items():
if k in self.accessible_fields:
self.__setattr__(k, v)
else:
log.warning(
u"Unexpected field {field_name} in model {model_name}".format(
field_name=k,
model_name=self.__class__.__name__
)
)
def updatable_attributes(self):
return extract(self.attributes, self.updatable_fields)
def initializable_attributes(self):
return extract(self.attributes, self.initializable_fields)
@classmethod
def before_save(cls, instance):
pass
@classmethod
def after_save(cls, instance):
pass
def save(self, params=None):
"""
Invokes Forum's POST/PUT service to create/update thread
"""
self.before_save(self)
if self.id: # if we have id already, treat this as an update
request_params = self.updatable_attributes()
if params:
request_params.update(params)
url = self.url(action='put', params=self.attributes)
response = perform_request(
'put',
url,
request_params,
metric_tags=self._metric_tags,
metric_action='model.update'
)
else: # otherwise, treat this as an insert
url = self.url(action='post', params=self.attributes)
response = perform_request(
'post',
url,
self.initializable_attributes(),
metric_tags=self._metric_tags,
metric_action='model.insert'
)
self.retrieved = True
self._update_from_response(response)
self.after_save(self)
def delete(self):
url = self.url(action='delete', params=self.attributes)
response = perform_request('delete', url, metric_tags=self._metric_tags, metric_action='model.delete')
self.retrieved = True
self._update_from_response(response)
@classmethod
def url_with_id(cls, params={}):
return cls.base_url + '/' + str(params['id'])
@classmethod
def url_without_id(cls, params={}):
return cls.base_url
@classmethod
def url(cls, action, params={}):
if cls.base_url is None:
raise CommentClientRequestError("Must provide base_url when using default url function")
if action not in cls.DEFAULT_ACTIONS:
raise ValueError(u"Invalid action {0}. The supported action must be in {1}".format(action, str(cls.DEFAULT_ACTIONS)))
elif action in cls.DEFAULT_ACTIONS_WITH_ID:
try:
return cls.url_with_id(params)
except KeyError:
raise CommentClientRequestError(u"Cannot perform action {0} without id".format(action))
else: # action must be in DEFAULT_ACTIONS_WITHOUT_ID now
return cls.url_without_id()

View File

@@ -1 +0,0 @@
requests

View File

@@ -1,8 +0,0 @@
from django.conf import settings
if hasattr(settings, "COMMENTS_SERVICE_URL"):
SERVICE_HOST = settings.COMMENTS_SERVICE_URL
else:
SERVICE_HOST = 'http://localhost:4567'
PREFIX = SERVICE_HOST + '/api/v1'

View File

@@ -1,235 +0,0 @@
import logging
import settings
import models
from eventtracking import tracker
import utils
log = logging.getLogger(__name__)
class Thread(models.Model):
# accessible_fields can be set and retrieved on the model
accessible_fields = [
'id', 'title', 'body', 'anonymous', 'anonymous_to_peers', 'course_id',
'closed', 'tags', 'votes', 'commentable_id', 'username', 'user_id',
'created_at', 'updated_at', 'comments_count', 'unread_comments_count',
'at_position_list', 'children', 'type', 'highlighted_title',
'highlighted_body', 'endorsed', 'read', 'group_id', 'group_name', 'pinned',
'abuse_flaggers', 'resp_skip', 'resp_limit', 'resp_total', 'thread_type',
'endorsed_responses', 'non_endorsed_responses', 'non_endorsed_resp_total',
'context', 'last_activity_at',
]
# updateable_fields are sent in PUT requests
updatable_fields = [
'title', 'body', 'anonymous', 'anonymous_to_peers', 'course_id', 'read',
'closed', 'user_id', 'commentable_id', 'group_id', 'group_name', 'pinned', 'thread_type'
]
# metric_tag_fields are used by Datadog to record metrics about the model
metric_tag_fields = [
'course_id', 'group_id', 'pinned', 'closed', 'anonymous', 'anonymous_to_peers',
'endorsed', 'read'
]
# initializable_fields are sent in POST requests
initializable_fields = updatable_fields + ['thread_type', 'context']
base_url = "{prefix}/threads".format(prefix=settings.PREFIX)
default_retrieve_params = {'recursive': False}
type = 'thread'
@classmethod
def search(cls, query_params):
# NOTE: Params 'recursive' and 'with_responses' are currently not used by
# either the 'search' or 'get_all' actions below. Both already use
# with_responses=False internally in the comment service, so no additional
# optimization is required.
params = {
'page': 1,
'per_page': 20,
'course_id': query_params['course_id'],
}
params.update(
utils.strip_blank(utils.strip_none(query_params))
)
if query_params.get('text'):
url = cls.url(action='search')
else:
url = cls.url(action='get_all', params=utils.extract(params, 'commentable_id'))
if params.get('commentable_id'):
del params['commentable_id']
response = utils.perform_request(
'get',
url,
params,
metric_tags=[u'course_id:{}'.format(query_params['course_id'])],
metric_action='thread.search',
paged_results=True
)
if query_params.get('text'):
search_query = query_params['text']
course_id = query_params['course_id']
group_id = query_params['group_id'] if 'group_id' in query_params else None
requested_page = params['page']
total_results = response.get('total_results')
corrected_text = response.get('corrected_text')
# Record search result metric to allow search quality analysis.
# course_id is already included in the context for the event tracker
tracker.emit(
'edx.forum.searched',
{
'query': search_query,
'corrected_text': corrected_text,
'group_id': group_id,
'page': requested_page,
'total_results': total_results,
}
)
log.info(
u'forum_text_search query="{search_query}" corrected_text="{corrected_text}" course_id={course_id} group_id={group_id} page={requested_page} total_results={total_results}'.format(
search_query=search_query,
corrected_text=corrected_text,
course_id=course_id,
group_id=group_id,
requested_page=requested_page,
total_results=total_results
)
)
return utils.CommentClientPaginatedResult(
collection=response.get('collection', []),
page=response.get('page', 1),
num_pages=response.get('num_pages', 1),
thread_count=response.get('thread_count', 0),
corrected_text=response.get('corrected_text', None)
)
@classmethod
def url_for_threads(cls, params={}):
if params.get('commentable_id'):
return u"{prefix}/{commentable_id}/threads".format(prefix=settings.PREFIX, commentable_id=params['commentable_id'])
else:
return u"{prefix}/threads".format(prefix=settings.PREFIX)
@classmethod
def url_for_search_threads(cls, params={}):
return "{prefix}/search/threads".format(prefix=settings.PREFIX)
@classmethod
def url(cls, action, params={}):
if action in ['get_all', 'post']:
return cls.url_for_threads(params)
elif action == 'search':
return cls.url_for_search_threads(params)
else:
return super(Thread, cls).url(action, params)
# TODO: This is currently overriding Model._retrieve only to add parameters
# for the request. Model._retrieve should be modified to handle this such
# that subclasses don't need to override for this.
def _retrieve(self, *args, **kwargs):
url = self.url(action='get', params=self.attributes)
request_params = {
'recursive': kwargs.get('recursive'),
'with_responses': kwargs.get('with_responses', False),
'user_id': kwargs.get('user_id'),
'mark_as_read': kwargs.get('mark_as_read', True),
'resp_skip': kwargs.get('response_skip'),
'resp_limit': kwargs.get('response_limit'),
}
request_params = utils.strip_none(request_params)
response = utils.perform_request(
'get',
url,
request_params,
metric_action='model.retrieve',
metric_tags=self._metric_tags
)
self._update_from_response(response)
def flagAbuse(self, user, voteable):
if voteable.type == 'thread':
url = _url_for_flag_abuse_thread(voteable.id)
elif voteable.type == 'comment':
url = _url_for_flag_comment(voteable.id)
else:
raise utils.CommentClientRequestError("Can only flag/unflag threads or comments")
params = {'user_id': user.id}
response = utils.perform_request(
'put',
url,
params,
metric_action='thread.abuse.flagged',
metric_tags=self._metric_tags
)
voteable._update_from_response(response)
def unFlagAbuse(self, user, voteable, removeAll):
if voteable.type == 'thread':
url = _url_for_unflag_abuse_thread(voteable.id)
elif voteable.type == 'comment':
url = _url_for_unflag_comment(voteable.id)
else:
raise utils.CommentClientRequestError("Can only flag/unflag for threads or comments")
params = {'user_id': user.id}
#if you're an admin, when you unflag, remove ALL flags
if removeAll:
params['all'] = True
response = utils.perform_request(
'put',
url,
params,
metric_tags=self._metric_tags,
metric_action='thread.abuse.unflagged'
)
voteable._update_from_response(response)
def pin(self, user, thread_id):
url = _url_for_pin_thread(thread_id)
params = {'user_id': user.id}
response = utils.perform_request(
'put',
url,
params,
metric_tags=self._metric_tags,
metric_action='thread.pin'
)
self._update_from_response(response)
def un_pin(self, user, thread_id):
url = _url_for_un_pin_thread(thread_id)
params = {'user_id': user.id}
response = utils.perform_request(
'put',
url,
params,
metric_tags=self._metric_tags,
metric_action='thread.unpin'
)
self._update_from_response(response)
def _url_for_flag_abuse_thread(thread_id):
return "{prefix}/threads/{thread_id}/abuse_flag".format(prefix=settings.PREFIX, thread_id=thread_id)
def _url_for_unflag_abuse_thread(thread_id):
return "{prefix}/threads/{thread_id}/abuse_unflag".format(prefix=settings.PREFIX, thread_id=thread_id)
def _url_for_pin_thread(thread_id):
return "{prefix}/threads/{thread_id}/pin".format(prefix=settings.PREFIX, thread_id=thread_id)
def _url_for_un_pin_thread(thread_id):
return "{prefix}/threads/{thread_id}/unpin".format(prefix=settings.PREFIX, thread_id=thread_id)

View File

@@ -1,233 +0,0 @@
""" User model wrapper for comment service"""
from six import text_type
import settings
import models
import utils
class User(models.Model):
accessible_fields = [
'username', 'follower_ids', 'upvoted_ids', 'downvoted_ids',
'id', 'external_id', 'subscribed_user_ids', 'children', 'course_id',
'group_id', 'subscribed_thread_ids', 'subscribed_commentable_ids',
'subscribed_course_ids', 'threads_count', 'comments_count',
'default_sort_key'
]
updatable_fields = ['username', 'external_id', 'default_sort_key']
initializable_fields = updatable_fields
metric_tag_fields = ['course_id']
base_url = "{prefix}/users".format(prefix=settings.PREFIX)
default_retrieve_params = {'complete': True}
type = 'user'
@classmethod
def from_django_user(cls, user):
return cls(id=str(user.id),
external_id=str(user.id),
username=user.username)
def read(self, source):
"""
Calls cs_comments_service to mark thread as read for the user
"""
params = {'source_type': source.type, 'source_id': source.id}
utils.perform_request(
'post',
_url_for_read(self.id),
params,
metric_action='user.read',
metric_tags=self._metric_tags + ['target.type:{}'.format(source.type)],
)
def follow(self, source):
params = {'source_type': source.type, 'source_id': source.id}
response = utils.perform_request(
'post',
_url_for_subscription(self.id),
params,
metric_action='user.follow',
metric_tags=self._metric_tags + ['target.type:{}'.format(source.type)],
)
def unfollow(self, source):
params = {'source_type': source.type, 'source_id': source.id}
response = utils.perform_request(
'delete',
_url_for_subscription(self.id),
params,
metric_action='user.unfollow',
metric_tags=self._metric_tags + ['target.type:{}'.format(source.type)],
)
def vote(self, voteable, value):
if voteable.type == 'thread':
url = _url_for_vote_thread(voteable.id)
elif voteable.type == 'comment':
url = _url_for_vote_comment(voteable.id)
else:
raise utils.CommentClientRequestError("Can only vote / unvote for threads or comments")
params = {'user_id': self.id, 'value': value}
response = utils.perform_request(
'put',
url,
params,
metric_action='user.vote',
metric_tags=self._metric_tags + ['target.type:{}'.format(voteable.type)],
)
voteable._update_from_response(response)
def unvote(self, voteable):
if voteable.type == 'thread':
url = _url_for_vote_thread(voteable.id)
elif voteable.type == 'comment':
url = _url_for_vote_comment(voteable.id)
else:
raise utils.CommentClientRequestError("Can only vote / unvote for threads or comments")
params = {'user_id': self.id}
response = utils.perform_request(
'delete',
url,
params,
metric_action='user.unvote',
metric_tags=self._metric_tags + ['target.type:{}'.format(voteable.type)],
)
voteable._update_from_response(response)
def active_threads(self, query_params={}):
if not self.course_id:
raise utils.CommentClientRequestError("Must provide course_id when retrieving active threads for the user")
url = _url_for_user_active_threads(self.id)
params = {'course_id': text_type(self.course_id)}
params.update(query_params)
response = utils.perform_request(
'get',
url,
params,
metric_action='user.active_threads',
metric_tags=self._metric_tags,
paged_results=True,
)
return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1)
def subscribed_threads(self, query_params={}):
if not self.course_id:
raise utils.CommentClientRequestError("Must provide course_id when retrieving subscribed threads for the user")
url = _url_for_user_subscribed_threads(self.id)
params = {'course_id': text_type(self.course_id)}
params.update(query_params)
response = utils.perform_request(
'get',
url,
params,
metric_action='user.subscribed_threads',
metric_tags=self._metric_tags,
paged_results=True
)
return utils.CommentClientPaginatedResult(
collection=response.get('collection', []),
page=response.get('page', 1),
num_pages=response.get('num_pages', 1),
thread_count=response.get('thread_count', 0)
)
def _retrieve(self, *args, **kwargs):
url = self.url(action='get', params=self.attributes)
retrieve_params = self.default_retrieve_params.copy()
retrieve_params.update(kwargs)
if self.attributes.get('course_id'):
retrieve_params['course_id'] = text_type(self.course_id)
if self.attributes.get('group_id'):
retrieve_params['group_id'] = self.group_id
try:
response = utils.perform_request(
'get',
url,
retrieve_params,
metric_action='model.retrieve',
metric_tags=self._metric_tags,
)
except utils.CommentClientRequestError as e:
if e.status_code == 404:
# attempt to gracefully recover from a previous failure
# to sync this user to the comments service.
self.save()
response = utils.perform_request(
'get',
url,
retrieve_params,
metric_action='model.retrieve',
metric_tags=self._metric_tags,
)
else:
raise
self._update_from_response(response)
def retire(self, retired_username):
url = _url_for_retire(self.id)
params = {'retired_username': retired_username}
utils.perform_request(
'post',
url,
params,
raw=True,
metric_action='user.retire',
metric_tags=self._metric_tags
)
def replace_username(self, new_username):
url = _url_for_username_replacement(self.id)
params = {"new_username": new_username}
utils.perform_request(
'post',
url,
params,
raw=True,
)
def _url_for_vote_comment(comment_id):
return "{prefix}/comments/{comment_id}/votes".format(prefix=settings.PREFIX, comment_id=comment_id)
def _url_for_vote_thread(thread_id):
return "{prefix}/threads/{thread_id}/votes".format(prefix=settings.PREFIX, thread_id=thread_id)
def _url_for_subscription(user_id):
return "{prefix}/users/{user_id}/subscriptions".format(prefix=settings.PREFIX, user_id=user_id)
def _url_for_user_active_threads(user_id):
return "{prefix}/users/{user_id}/active_threads".format(prefix=settings.PREFIX, user_id=user_id)
def _url_for_user_subscribed_threads(user_id):
return "{prefix}/users/{user_id}/subscribed_threads".format(prefix=settings.PREFIX, user_id=user_id)
def _url_for_read(user_id):
"""
Returns cs_comments_service url endpoint to mark thread as read for given user_id
"""
return "{prefix}/users/{user_id}/read".format(prefix=settings.PREFIX, user_id=user_id)
def _url_for_retire(user_id):
"""
Returns cs_comments_service url endpoint to retire a user (remove all post content, etc.)
"""
return "{prefix}/users/{user_id}/retire".format(prefix=settings.PREFIX, user_id=user_id)
def _url_for_username_replacement(user_id):
"""
Returns cs_comments_servuce url endpoint to replace the username of a user
"""
return "{prefix}/users/{user_id}/replace_username".format(prefix=settings.PREFIX, user_id=user_id)

View File

@@ -1,155 +0,0 @@
"""" Common utilities for comment client wrapper """
import logging
from contextlib import contextmanager
from time import time
from uuid import uuid4
import requests
from django.utils.translation import get_language
from .settings import SERVICE_HOST as COMMENTS_SERVICE
log = logging.getLogger(__name__)
def strip_none(dic):
return dict([(k, v) for k, v in dic.iteritems() if v is not None])
def strip_blank(dic):
def _is_blank(v):
return isinstance(v, str) and len(v.strip()) == 0
return dict([(k, v) for k, v in dic.iteritems() if not _is_blank(v)])
def extract(dic, keys):
if isinstance(keys, str):
return strip_none({keys: dic.get(keys)})
else:
return strip_none({k: dic.get(k) for k in keys})
def perform_request(method, url, data_or_params=None, raw=False,
metric_action=None, metric_tags=None, paged_results=False):
# To avoid dependency conflict
from django_comment_common.models import ForumsConfig
config = ForumsConfig.current()
if not config.enabled:
raise CommentClientMaintenanceError('service disabled')
if metric_tags is None:
metric_tags = []
metric_tags.append(u'method:{}'.format(method))
if metric_action:
metric_tags.append(u'action:{}'.format(metric_action))
if data_or_params is None:
data_or_params = {}
headers = {
'X-Edx-Api-Key': config.api_key,
'Accept-Language': get_language(),
}
request_id = uuid4()
request_id_dict = {'request_id': request_id}
if method in ['post', 'put', 'patch']:
data = data_or_params
params = request_id_dict
else:
data = None
params = data_or_params.copy()
params.update(request_id_dict)
response = requests.request(
method,
url,
data=data,
params=params,
headers=headers,
timeout=config.connection_timeout
)
metric_tags.append(u'status_code:{}'.format(response.status_code))
if response.status_code > 200:
metric_tags.append(u'result:failure')
else:
metric_tags.append(u'result:success')
if 200 < response.status_code < 500:
raise CommentClientRequestError(response.text, response.status_code)
# Heroku returns a 503 when an application is in maintenance mode
elif response.status_code == 503:
raise CommentClientMaintenanceError(response.text)
elif response.status_code == 500:
raise CommentClient500Error(response.text)
else:
if raw:
return response.text
else:
try:
data = response.json()
except ValueError:
raise CommentClientError(
u"Invalid JSON response for request {request_id}; first 100 characters: '{content}'".format(
request_id=request_id,
content=response.text[:100]
)
)
return data
class CommentClientError(Exception):
pass
class CommentClientRequestError(CommentClientError):
def __init__(self, msg, status_codes=400):
super(CommentClientRequestError, self).__init__(msg)
self.status_code = status_codes
class CommentClient500Error(CommentClientError):
pass
class CommentClientMaintenanceError(CommentClientError):
pass
class CommentClientPaginatedResult(object):
""" class for paginated results returned from comment services"""
def __init__(self, collection, page, num_pages, thread_count=0, corrected_text=None):
self.collection = collection
self.page = page
self.num_pages = num_pages
self.thread_count = thread_count
self.corrected_text = corrected_text
def check_forum_heartbeat():
"""
Check the forum connection via its built-in heartbeat service and create an answer which can be used in the LMS
heartbeat django application.
This function can be connected to the LMS heartbeat checker through the HEARTBEAT_CHECKS variable.
"""
# To avoid dependency conflict
from django_comment_common.models import ForumsConfig
config = ForumsConfig.current()
if not config.enabled:
# If this check is enabled but forums disabled, don't connect, just report no error
return 'forum', True, 'OK'
try:
res = requests.get(
'%s/heartbeat' % COMMENTS_SERVICE,
timeout=config.connection_timeout
).json()
if res['OK']:
return 'forum', True, 'OK'
else:
return 'forum', False, res.get('check', 'Forum heartbeat failed')
except Exception as fail:
return 'forum', False, unicode(fail)