feat: Integrate Forum V2 into edx-platform
This commit introduces the new Forum V2 application, allowing users to choose between the legacy Forum V1 and the new Forum V2 at the course level. Key Changes: - Added waffle flag `discussions.enable_forum_v2` to enable Forum V2 for selected courses, allowing coexistence with Forum V1. - Default data storage for Forum V2 is set to MongoDB, with an option to switch to MySQL using the waffle flag `forum_v2.enable_mysql_backend`. - Introduced management command `forum_migrate_course_from_mongodb_to_mysql` for per-course data migration from MongoDB to MySQL. Note: This PR does not include all unit tests for the Forum V2 native API due to ongoing migration efforts. Further updates will follow to ensure full test coverage before final release. Co-authored-by: [Muhammad Faraz Maqsood] <faraz.maqsood@arbisoft.com> Co-authored-by: [Ali Salman] <ali.salman@arbisoft.com>
This commit is contained in:
committed by
David Ormsbee
parent
0baf71ce01
commit
70b60ff256
@@ -82,6 +82,7 @@ class MockRequestSetupMixin:
|
||||
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
class CreateThreadGroupIdTestCase(
|
||||
MockRequestSetupMixin,
|
||||
CohortedTestCase,
|
||||
@@ -90,7 +91,21 @@ class CreateThreadGroupIdTestCase(
|
||||
):
|
||||
cs_endpoint = "/threads"
|
||||
|
||||
def call_view(self, mock_request, commentable_id, user, group_id, pass_group_id=True):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def call_view(self, mock_is_forum_v2_enabled, mock_request, commentable_id, user, group_id, pass_group_id=True):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {})
|
||||
request_data = {"body": "body", "title": "title", "thread_type": "discussion"}
|
||||
if pass_group_id:
|
||||
@@ -105,8 +120,9 @@ class CreateThreadGroupIdTestCase(
|
||||
commentable_id=commentable_id
|
||||
)
|
||||
|
||||
def test_group_info_in_response(self, mock_request):
|
||||
def test_group_info_in_response(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"cohorted_topic",
|
||||
self.student,
|
||||
@@ -116,6 +132,7 @@ class CreateThreadGroupIdTestCase(
|
||||
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
@disable_signal(views, 'thread_edited')
|
||||
@disable_signal(views, 'thread_voted')
|
||||
@disable_signal(views, 'thread_deleted')
|
||||
@@ -127,11 +144,18 @@ class ThreadActionGroupIdTestCase(
|
||||
def call_view(
|
||||
self,
|
||||
view_name,
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
user=None,
|
||||
post_params=None,
|
||||
view_args=None
|
||||
):
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(
|
||||
mock_request,
|
||||
{
|
||||
@@ -154,53 +178,58 @@ class ThreadActionGroupIdTestCase(
|
||||
**(view_args or {})
|
||||
)
|
||||
|
||||
def test_update(self, mock_request):
|
||||
def test_update(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
"update_thread",
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
post_params={"body": "body", "title": "title"}
|
||||
)
|
||||
self._assert_json_response_contains_group_info(response)
|
||||
|
||||
def test_delete(self, mock_request):
|
||||
response = self.call_view("delete_thread", mock_request)
|
||||
def test_delete(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view("delete_thread", mock_is_forum_v2_enabled, mock_request)
|
||||
self._assert_json_response_contains_group_info(response)
|
||||
|
||||
def test_vote(self, mock_request):
|
||||
def test_vote(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
"vote_for_thread",
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
view_args={"value": "up"}
|
||||
)
|
||||
self._assert_json_response_contains_group_info(response)
|
||||
response = self.call_view("undo_vote_for_thread", mock_request)
|
||||
response = self.call_view("undo_vote_for_thread", mock_is_forum_v2_enabled, mock_request)
|
||||
self._assert_json_response_contains_group_info(response)
|
||||
|
||||
def test_flag(self, mock_request):
|
||||
def test_flag(self, mock_is_forum_v2_enabled, mock_request):
|
||||
with mock.patch('openedx.core.djangoapps.django_comment_common.signals.thread_flagged.send') as signal_mock:
|
||||
response = self.call_view("flag_abuse_for_thread", mock_request)
|
||||
response = self.call_view("flag_abuse_for_thread", mock_is_forum_v2_enabled, mock_request)
|
||||
self._assert_json_response_contains_group_info(response)
|
||||
self.assertEqual(signal_mock.call_count, 1)
|
||||
response = self.call_view("un_flag_abuse_for_thread", mock_request)
|
||||
response = self.call_view("un_flag_abuse_for_thread", mock_is_forum_v2_enabled, mock_request)
|
||||
self._assert_json_response_contains_group_info(response)
|
||||
|
||||
def test_pin(self, mock_request):
|
||||
def test_pin(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
"pin_thread",
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
user=self.moderator
|
||||
)
|
||||
self._assert_json_response_contains_group_info(response)
|
||||
response = self.call_view(
|
||||
"un_pin_thread",
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
user=self.moderator
|
||||
)
|
||||
self._assert_json_response_contains_group_info(response)
|
||||
|
||||
def test_openclose(self, mock_request):
|
||||
def test_openclose(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
"openclose_thread",
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
user=self.moderator
|
||||
)
|
||||
@@ -280,10 +309,11 @@ class ViewsTestCaseMixin:
|
||||
data["depth"] = 0
|
||||
self._set_mock_request_data(mock_request, data)
|
||||
|
||||
def create_thread_helper(self, mock_request, extra_request_data=None, extra_response_data=None):
|
||||
def create_thread_helper(self, mock_is_forum_v2_enabled, mock_request, extra_request_data=None, extra_response_data=None):
|
||||
"""
|
||||
Issues a request to create a thread and verifies the result.
|
||||
"""
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"thread_type": "discussion",
|
||||
"title": "Hello",
|
||||
@@ -350,10 +380,11 @@ class ViewsTestCaseMixin:
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
def update_thread_helper(self, mock_request):
|
||||
def update_thread_helper(self, mock_is_forum_v2_enabled, mock_request):
|
||||
"""
|
||||
Issues a request to update a thread and verifies the result.
|
||||
"""
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._setup_mock_request(mock_request)
|
||||
# Mock out saving in order to test that content is correctly
|
||||
# updated. Otherwise, the call to thread.save() receives the
|
||||
@@ -376,6 +407,7 @@ class ViewsTestCaseMixin:
|
||||
|
||||
@ddt.ddt
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
@disable_signal(views, 'thread_created')
|
||||
@disable_signal(views, 'thread_edited')
|
||||
class ViewsQueryCountTestCase(
|
||||
@@ -393,6 +425,11 @@ class ViewsQueryCountTestCase(
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def count_queries(func): # pylint: disable=no-self-argument
|
||||
"""
|
||||
@@ -414,22 +451,23 @@ class ViewsQueryCountTestCase(
|
||||
)
|
||||
@ddt.unpack
|
||||
@count_queries
|
||||
def test_create_thread(self, mock_request):
|
||||
self.create_thread_helper(mock_request)
|
||||
def test_create_thread(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.create_thread_helper(mock_is_forum_v2_enabled, mock_request)
|
||||
|
||||
@ddt.data(
|
||||
(ModuleStoreEnum.Type.split, 3, 6, 41),
|
||||
)
|
||||
@ddt.unpack
|
||||
@count_queries
|
||||
def test_update_thread(self, mock_request):
|
||||
self.update_thread_helper(mock_request)
|
||||
def test_update_thread(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.update_thread_helper(mock_is_forum_v2_enabled, mock_request)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@disable_signal(views, 'comment_flagged')
|
||||
@disable_signal(views, 'thread_flagged')
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
class ViewsTestCase(
|
||||
ForumsEnableMixin,
|
||||
UrlResetMixin,
|
||||
@@ -464,7 +502,16 @@ class ViewsTestCase(
|
||||
# so we need to call super.setUp() which reloads urls.py (because
|
||||
# of the UrlResetMixin)
|
||||
super().setUp()
|
||||
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
# Patch the comment client user save method so it does not try
|
||||
# to create a new cc user when creating a django user
|
||||
with patch('common.djangoapps.student.models.user.cc.User.save'):
|
||||
@@ -497,11 +544,11 @@ class ViewsTestCase(
|
||||
with self.assert_signal_sent(views, signal, sender=None, user=user, exclude_args=('post',)):
|
||||
yield
|
||||
|
||||
def test_create_thread(self, mock_request):
|
||||
def test_create_thread(self, mock_is_forum_v2_enabled, mock_request):
|
||||
with self.assert_discussion_signals('thread_created'):
|
||||
self.create_thread_helper(mock_request)
|
||||
self.create_thread_helper(mock_is_forum_v2_enabled, mock_request)
|
||||
|
||||
def test_create_thread_standalone(self, mock_request):
|
||||
def test_create_thread_standalone(self, mock_is_forum_v2_enabled, mock_request):
|
||||
team = CourseTeamFactory.create(
|
||||
name="A Team",
|
||||
course_id=self.course_id,
|
||||
@@ -513,15 +560,15 @@ class ViewsTestCase(
|
||||
team.add_user(self.student)
|
||||
|
||||
# create_thread_helper verifies that extra data are passed through to the comments service
|
||||
self.create_thread_helper(mock_request, extra_response_data={'context': ThreadContext.STANDALONE})
|
||||
self.create_thread_helper(mock_is_forum_v2_enabled, mock_request, extra_response_data={'context': ThreadContext.STANDALONE})
|
||||
|
||||
@ddt.data(
|
||||
('follow_thread', 'thread_followed'),
|
||||
('unfollow_thread', 'thread_unfollowed'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_follow_unfollow_thread_signals(self, view_name, signal, mock_request):
|
||||
self.create_thread_helper(mock_request)
|
||||
def test_follow_unfollow_thread_signals(self, view_name, signal, mock_is_forum_v2_enabled, mock_request):
|
||||
self.create_thread_helper(mock_is_forum_v2_enabled, mock_request)
|
||||
|
||||
with self.assert_discussion_signals(signal):
|
||||
response = self.client.post(
|
||||
@@ -532,7 +579,8 @@ class ViewsTestCase(
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_delete_thread(self, mock_request):
|
||||
def test_delete_thread(self, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"user_id": str(self.student.id),
|
||||
"closed": False,
|
||||
@@ -551,7 +599,8 @@ class ViewsTestCase(
|
||||
assert response.status_code == 200
|
||||
assert mock_request.called
|
||||
|
||||
def test_delete_comment(self, mock_request):
|
||||
def test_delete_comment(self, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"user_id": str(self.student.id),
|
||||
"closed": False,
|
||||
@@ -573,12 +622,13 @@ class ViewsTestCase(
|
||||
assert args[0] == 'delete'
|
||||
assert args[1].endswith(f"/{test_comment_id}")
|
||||
|
||||
def _test_request_error(self, view_name, view_kwargs, data, mock_request):
|
||||
def _test_request_error(self, view_name, view_kwargs, data, mock_is_forum_v2_enabled, mock_request):
|
||||
"""
|
||||
Submit a request against the given view with the given data and ensure
|
||||
that the result is a 400 error and that no data was posted using
|
||||
mock_request
|
||||
"""
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._setup_mock_request(mock_request, include_depth=(view_name == "create_sub_comment"))
|
||||
|
||||
response = self.client.post(reverse(view_name, kwargs=view_kwargs), data=data)
|
||||
@@ -586,87 +636,97 @@ class ViewsTestCase(
|
||||
for call in mock_request.call_args_list:
|
||||
assert call[0][0].lower() == 'get'
|
||||
|
||||
def test_create_thread_no_title(self, mock_request):
|
||||
def test_create_thread_no_title(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"create_thread",
|
||||
{"commentable_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"body": "foo"},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_create_thread_empty_title(self, mock_request):
|
||||
def test_create_thread_empty_title(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"create_thread",
|
||||
{"commentable_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"body": "foo", "title": " "},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_create_thread_no_body(self, mock_request):
|
||||
def test_create_thread_no_body(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"create_thread",
|
||||
{"commentable_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"title": "foo"},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_create_thread_empty_body(self, mock_request):
|
||||
def test_create_thread_empty_body(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"create_thread",
|
||||
{"commentable_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"body": " ", "title": "foo"},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_update_thread_no_title(self, mock_request):
|
||||
def test_update_thread_no_title(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"update_thread",
|
||||
{"thread_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"body": "foo"},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_update_thread_empty_title(self, mock_request):
|
||||
def test_update_thread_empty_title(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"update_thread",
|
||||
{"thread_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"body": "foo", "title": " "},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_update_thread_no_body(self, mock_request):
|
||||
def test_update_thread_no_body(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"update_thread",
|
||||
{"thread_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"title": "foo"},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_update_thread_empty_body(self, mock_request):
|
||||
def test_update_thread_empty_body(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"update_thread",
|
||||
{"thread_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"body": " ", "title": "foo"},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_update_thread_course_topic(self, mock_request):
|
||||
def test_update_thread_course_topic(self, mock_is_forum_v2_enabled, mock_request):
|
||||
with self.assert_discussion_signals('thread_edited'):
|
||||
self.update_thread_helper(mock_request)
|
||||
self.update_thread_helper(mock_is_forum_v2_enabled, mock_request)
|
||||
|
||||
@patch(
|
||||
'lms.djangoapps.discussion.django_comment_client.utils.get_discussion_categories_ids',
|
||||
return_value=["test_commentable"],
|
||||
)
|
||||
def test_update_thread_wrong_commentable_id(self, mock_get_discussion_id_map, mock_request):
|
||||
def test_update_thread_wrong_commentable_id(self, mock_get_discussion_id_map, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"update_thread",
|
||||
{"thread_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"body": "foo", "title": "foo", "commentable_id": "wrong_commentable"},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_create_comment(self, mock_request):
|
||||
def test_create_comment(self, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._setup_mock_request(mock_request)
|
||||
with self.assert_discussion_signals('comment_created'):
|
||||
response = self.client.post(
|
||||
@@ -678,55 +738,62 @@ class ViewsTestCase(
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_create_comment_no_body(self, mock_request):
|
||||
def test_create_comment_no_body(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"create_comment",
|
||||
{"thread_id": "dummy", "course_id": str(self.course_id)},
|
||||
{},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_create_comment_empty_body(self, mock_request):
|
||||
def test_create_comment_empty_body(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"create_comment",
|
||||
{"thread_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"body": " "},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_create_sub_comment_no_body(self, mock_request):
|
||||
def test_create_sub_comment_no_body(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"create_sub_comment",
|
||||
{"comment_id": "dummy", "course_id": str(self.course_id)},
|
||||
{},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_create_sub_comment_empty_body(self, mock_request):
|
||||
def test_create_sub_comment_empty_body(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"create_sub_comment",
|
||||
{"comment_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"body": " "},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_update_comment_no_body(self, mock_request):
|
||||
def test_update_comment_no_body(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"update_comment",
|
||||
{"comment_id": "dummy", "course_id": str(self.course_id)},
|
||||
{},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_update_comment_empty_body(self, mock_request):
|
||||
def test_update_comment_empty_body(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._test_request_error(
|
||||
"update_comment",
|
||||
{"comment_id": "dummy", "course_id": str(self.course_id)},
|
||||
{"body": " "},
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request
|
||||
)
|
||||
|
||||
def test_update_comment_basic(self, mock_request):
|
||||
def test_update_comment_basic(self, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._setup_mock_request(mock_request)
|
||||
comment_id = "test_comment_id"
|
||||
updated_body = "updated body"
|
||||
@@ -748,13 +815,14 @@ class ViewsTestCase(
|
||||
data={"body": updated_body}
|
||||
)
|
||||
|
||||
def test_flag_thread_open(self, mock_request):
|
||||
self.flag_thread(mock_request, False)
|
||||
def test_flag_thread_open(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.flag_thread(mock_is_forum_v2_enabled, mock_request, False)
|
||||
|
||||
def test_flag_thread_close(self, mock_request):
|
||||
self.flag_thread(mock_request, True)
|
||||
def test_flag_thread_close(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.flag_thread(mock_is_forum_v2_enabled, mock_request, True)
|
||||
|
||||
def flag_thread(self, mock_request, is_closed):
|
||||
def flag_thread(self, mock_is_forum_v2_enabled, mock_request, is_closed):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"title": "Hello",
|
||||
"body": "this is a post",
|
||||
@@ -826,13 +894,14 @@ class ViewsTestCase(
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_un_flag_thread_open(self, mock_request):
|
||||
self.un_flag_thread(mock_request, False)
|
||||
def test_un_flag_thread_open(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.un_flag_thread(mock_is_forum_v2_enabled, mock_request, False)
|
||||
|
||||
def test_un_flag_thread_close(self, mock_request):
|
||||
self.un_flag_thread(mock_request, True)
|
||||
def test_un_flag_thread_close(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.un_flag_thread(mock_is_forum_v2_enabled, mock_request, True)
|
||||
|
||||
def un_flag_thread(self, mock_request, is_closed):
|
||||
def un_flag_thread(self, mock_is_forum_v2_enabled, mock_request, is_closed):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"title": "Hello",
|
||||
"body": "this is a post",
|
||||
@@ -905,13 +974,14 @@ class ViewsTestCase(
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_flag_comment_open(self, mock_request):
|
||||
self.flag_comment(mock_request, False)
|
||||
def test_flag_comment_open(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.flag_comment(mock_is_forum_v2_enabled, mock_request, False)
|
||||
|
||||
def test_flag_comment_close(self, mock_request):
|
||||
self.flag_comment(mock_request, True)
|
||||
def test_flag_comment_close(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.flag_comment(mock_is_forum_v2_enabled, mock_request, True)
|
||||
|
||||
def flag_comment(self, mock_request, is_closed):
|
||||
def flag_comment(self, mock_is_forum_v2_enabled, mock_request, is_closed):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"body": "this is a comment",
|
||||
"course_id": "MITx/999/Robot_Super_Course",
|
||||
@@ -976,13 +1046,14 @@ class ViewsTestCase(
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_un_flag_comment_open(self, mock_request):
|
||||
self.un_flag_comment(mock_request, False)
|
||||
def test_un_flag_comment_open(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.un_flag_comment(mock_is_forum_v2_enabled, mock_request, False)
|
||||
|
||||
def test_un_flag_comment_close(self, mock_request):
|
||||
self.un_flag_comment(mock_request, True)
|
||||
def test_un_flag_comment_close(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.un_flag_comment(mock_is_forum_v2_enabled, mock_request, True)
|
||||
|
||||
def un_flag_comment(self, mock_request, is_closed):
|
||||
def un_flag_comment(self, mock_is_forum_v2_enabled, mock_request, is_closed):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"body": "this is a comment",
|
||||
"course_id": "MITx/999/Robot_Super_Course",
|
||||
@@ -1054,7 +1125,8 @@ class ViewsTestCase(
|
||||
('downvote_comment', 'comment_id', 'comment_voted')
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_voting(self, view_name, item_id, signal, mock_request):
|
||||
def test_voting(self, view_name, item_id, signal, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._setup_mock_request(mock_request)
|
||||
with self.assert_discussion_signals(signal):
|
||||
response = self.client.post(
|
||||
@@ -1065,7 +1137,8 @@ class ViewsTestCase(
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_endorse_comment(self, mock_request):
|
||||
def test_endorse_comment(self, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._setup_mock_request(mock_request)
|
||||
self.client.login(username=self.moderator.username, password=self.password)
|
||||
with self.assert_discussion_signals('comment_endorsed', user=self.moderator):
|
||||
@@ -1079,6 +1152,7 @@ class ViewsTestCase(
|
||||
|
||||
|
||||
@patch("openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request", autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
@disable_signal(views, 'comment_endorsed')
|
||||
class ViewPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStoreTestCase, MockRequestSetupMixin):
|
||||
|
||||
@@ -1106,8 +1180,19 @@ class ViewPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStor
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_pin_thread_as_student(self, mock_request):
|
||||
def test_pin_thread_as_student(self, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {})
|
||||
self.client.login(username=self.student.username, password=self.password)
|
||||
response = self.client.post(
|
||||
@@ -1115,7 +1200,8 @@ class ViewPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStor
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_pin_thread_as_moderator(self, mock_request):
|
||||
def test_pin_thread_as_moderator(self, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {})
|
||||
self.client.login(username=self.moderator.username, password=self.password)
|
||||
response = self.client.post(
|
||||
@@ -1123,7 +1209,8 @@ class ViewPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStor
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_un_pin_thread_as_student(self, mock_request):
|
||||
def test_un_pin_thread_as_student(self, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {})
|
||||
self.client.login(username=self.student.username, password=self.password)
|
||||
response = self.client.post(
|
||||
@@ -1131,7 +1218,8 @@ class ViewPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStor
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_un_pin_thread_as_moderator(self, mock_request):
|
||||
def test_un_pin_thread_as_moderator(self, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {})
|
||||
self.client.login(username=self.moderator.username, password=self.password)
|
||||
response = self.client.post(
|
||||
@@ -1139,7 +1227,7 @@ class ViewPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStor
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
def _set_mock_request_thread_and_comment(self, mock_request, thread_data, comment_data):
|
||||
def _set_mock_request_thread_and_comment(self, mock_is_forum_v2_enabled, mock_request, thread_data, comment_data):
|
||||
def handle_request(*args, **kwargs):
|
||||
url = args[1]
|
||||
if "/threads/" in url:
|
||||
@@ -1148,10 +1236,12 @@ class ViewPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStor
|
||||
return self._create_response_mock(comment_data)
|
||||
else:
|
||||
raise ArgumentError("Bad url to mock request")
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
mock_request.side_effect = handle_request
|
||||
|
||||
def test_endorse_response_as_staff(self, mock_request):
|
||||
def test_endorse_response_as_staff(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._set_mock_request_thread_and_comment(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
{"type": "thread", "thread_type": "question", "user_id": str(self.student.id), "commentable_id": "course"},
|
||||
{"type": "comment", "thread_id": "dummy"}
|
||||
@@ -1162,8 +1252,9 @@ class ViewPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStor
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_endorse_response_as_student(self, mock_request):
|
||||
def test_endorse_response_as_student(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._set_mock_request_thread_and_comment(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
{"type": "thread", "thread_type": "question",
|
||||
"user_id": str(self.moderator.id), "commentable_id": "course"},
|
||||
@@ -1175,8 +1266,9 @@ class ViewPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStor
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_endorse_response_as_student_question_author(self, mock_request):
|
||||
def test_endorse_response_as_student_question_author(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self._set_mock_request_thread_and_comment(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
{"type": "thread", "thread_type": "question", "user_id": str(self.student.id), "commentable_id": "course"},
|
||||
{"type": "comment", "thread_id": "dummy"}
|
||||
@@ -1209,10 +1301,12 @@ class CreateThreadUnicodeTestCase(
|
||||
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
def _test_unicode_data(self, text, mock_request,):
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def _test_unicode_data(self, text, mock_is_forum_v2_enabled, mock_request,):
|
||||
"""
|
||||
Test to make sure unicode data in a thread doesn't break it.
|
||||
"""
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {})
|
||||
request = RequestFactory().post("dummy_url", {"thread_type": "discussion", "body": text, "title": text})
|
||||
request.user = self.student
|
||||
@@ -1235,6 +1329,13 @@ class UpdateThreadUnicodeTestCase(
|
||||
UnicodeTestMixin,
|
||||
MockRequestSetupMixin
|
||||
):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -1255,7 +1356,9 @@ class UpdateThreadUnicodeTestCase(
|
||||
return_value=["test_commentable"],
|
||||
)
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
def _test_unicode_data(self, text, mock_request, mock_get_discussion_id_map):
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def _test_unicode_data(self, text, mock_is_forum_v2_enabled, mock_request, mock_get_discussion_id_map):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"user_id": str(self.student.id),
|
||||
"closed": False,
|
||||
@@ -1280,6 +1383,13 @@ class CreateCommentUnicodeTestCase(
|
||||
UnicodeTestMixin,
|
||||
MockRequestSetupMixin
|
||||
):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -1296,7 +1406,9 @@ class CreateCommentUnicodeTestCase(
|
||||
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
def _test_unicode_data(self, text, mock_request):
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def _test_unicode_data(self, text, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
commentable_id = "non_team_dummy_id"
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"closed": False,
|
||||
@@ -1327,6 +1439,13 @@ class UpdateCommentUnicodeTestCase(
|
||||
UnicodeTestMixin,
|
||||
MockRequestSetupMixin
|
||||
):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -1343,7 +1462,9 @@ class UpdateCommentUnicodeTestCase(
|
||||
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
def _test_unicode_data(self, text, mock_request):
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def _test_unicode_data(self, text, mock_is_forum_v2_enabled, mock_request):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"user_id": str(self.student.id),
|
||||
"closed": False,
|
||||
@@ -1359,6 +1480,7 @@ class UpdateCommentUnicodeTestCase(
|
||||
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
class CommentActionTestCase(
|
||||
MockRequestSetupMixin,
|
||||
CohortedTestCase,
|
||||
@@ -1367,11 +1489,18 @@ class CommentActionTestCase(
|
||||
def call_view(
|
||||
self,
|
||||
view_name,
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
user=None,
|
||||
post_params=None,
|
||||
view_args=None
|
||||
):
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self._set_mock_request_data(
|
||||
mock_request,
|
||||
{
|
||||
@@ -1394,9 +1523,9 @@ class CommentActionTestCase(
|
||||
**(view_args or {})
|
||||
)
|
||||
|
||||
def test_flag(self, mock_request):
|
||||
def test_flag(self, mock_is_forum_v2_enabled, mock_request):
|
||||
with mock.patch('openedx.core.djangoapps.django_comment_common.signals.comment_flagged.send') as signal_mock:
|
||||
self.call_view("flag_abuse_for_comment", mock_request)
|
||||
self.call_view("flag_abuse_for_comment", mock_is_forum_v2_enabled, mock_request)
|
||||
self.assertEqual(signal_mock.call_count, 1)
|
||||
|
||||
|
||||
@@ -1410,6 +1539,14 @@ class CreateSubCommentUnicodeTestCase(
|
||||
"""
|
||||
Make sure comments under a response can handle unicode.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# pylint: disable=super-method-not-called
|
||||
@@ -1425,10 +1562,12 @@ class CreateSubCommentUnicodeTestCase(
|
||||
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
def _test_unicode_data(self, text, mock_request):
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def _test_unicode_data(self, text, mock_is_forum_v2_enabled, mock_request):
|
||||
"""
|
||||
Create a comment with unicode in it.
|
||||
"""
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"closed": False,
|
||||
"depth": 1,
|
||||
@@ -1453,6 +1592,7 @@ class CreateSubCommentUnicodeTestCase(
|
||||
|
||||
@ddt.ddt
|
||||
@patch("openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request", autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
@disable_signal(views, 'thread_voted')
|
||||
@disable_signal(views, 'thread_edited')
|
||||
@disable_signal(views, 'comment_created')
|
||||
@@ -1562,13 +1702,24 @@ class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleSto
|
||||
users=[cls.group_moderator, cls.cohorted]
|
||||
)
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
def _setup_mock(self, user, mock_request, data):
|
||||
def _setup_mock(self, user, mock_is_forum_v2_enabled, mock_request, data):
|
||||
user = getattr(self, user)
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, data)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.client.login(username=user.username, password=self.password)
|
||||
|
||||
@ddt.data(
|
||||
@@ -1593,7 +1744,7 @@ class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleSto
|
||||
('group_moderator', 'cohorted', 'course_commentable_id', 401, CourseDiscussionSettings.NONE)
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_update_thread(self, user, thread_author, commentable_id, status_code, division_scheme, mock_request):
|
||||
def test_update_thread(self, user, thread_author, commentable_id, status_code, division_scheme, mock_is_forum_v2_enabled, mock_request):
|
||||
"""
|
||||
Verify that update_thread is limited to thread authors and privileged users (team membership does not matter).
|
||||
"""
|
||||
@@ -1603,7 +1754,7 @@ class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleSto
|
||||
thread_author = getattr(self, thread_author)
|
||||
|
||||
self._setup_mock(
|
||||
user, mock_request, # user is the person making the request.
|
||||
user, mock_is_forum_v2_enabled, mock_request, # user is the person making the request.
|
||||
{
|
||||
"user_id": str(thread_author.id),
|
||||
"closed": False, "commentable_id": commentable_id,
|
||||
@@ -1643,12 +1794,12 @@ class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleSto
|
||||
('group_moderator', 'cohorted', 'team_commentable_id', 401, CourseDiscussionSettings.NONE)
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_delete_comment(self, user, comment_author, commentable_id, status_code, division_scheme, mock_request):
|
||||
def test_delete_comment(self, user, comment_author, commentable_id, status_code, division_scheme, mock_is_forum_v2_enabled, mock_request):
|
||||
commentable_id = getattr(self, commentable_id)
|
||||
comment_author = getattr(self, comment_author)
|
||||
self.change_divided_discussion_settings(division_scheme)
|
||||
|
||||
self._setup_mock(user, mock_request, {
|
||||
self._setup_mock(user, mock_is_forum_v2_enabled, mock_request, {
|
||||
"closed": False,
|
||||
"commentable_id": commentable_id,
|
||||
"user_id": str(comment_author.id),
|
||||
@@ -1671,12 +1822,12 @@ class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleSto
|
||||
|
||||
@ddt.data(*ddt_permissions_args)
|
||||
@ddt.unpack
|
||||
def test_create_comment(self, user, commentable_id, status_code, mock_request):
|
||||
def test_create_comment(self, user, commentable_id, status_code, mock_is_forum_v2_enabled, mock_request):
|
||||
"""
|
||||
Verify that create_comment is limited to members of the team or users with 'edit_content' permission.
|
||||
"""
|
||||
commentable_id = getattr(self, commentable_id)
|
||||
self._setup_mock(user, mock_request, {"closed": False, "commentable_id": commentable_id})
|
||||
self._setup_mock(user, mock_is_forum_v2_enabled, mock_request, {"closed": False, "commentable_id": commentable_id})
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
@@ -1692,13 +1843,13 @@ class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleSto
|
||||
|
||||
@ddt.data(*ddt_permissions_args)
|
||||
@ddt.unpack
|
||||
def test_create_sub_comment(self, user, commentable_id, status_code, mock_request):
|
||||
def test_create_sub_comment(self, user, commentable_id, status_code, mock_is_forum_v2_enabled, mock_request):
|
||||
"""
|
||||
Verify that create_subcomment is limited to members of the team or users with 'edit_content' permission.
|
||||
"""
|
||||
commentable_id = getattr(self, commentable_id)
|
||||
self._setup_mock(
|
||||
user, mock_request,
|
||||
user, mock_is_forum_v2_enabled, mock_request,
|
||||
{"closed": False, "commentable_id": commentable_id, "thread_id": "dummy_thread"},
|
||||
)
|
||||
response = self.client.post(
|
||||
@@ -1715,14 +1866,14 @@ class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleSto
|
||||
|
||||
@ddt.data(*ddt_permissions_args)
|
||||
@ddt.unpack
|
||||
def test_comment_actions(self, user, commentable_id, status_code, mock_request):
|
||||
def test_comment_actions(self, user, commentable_id, status_code, mock_is_forum_v2_enabled, mock_request):
|
||||
"""
|
||||
Verify that voting and flagging of comments is limited to members of the team or users with
|
||||
'edit_content' permission.
|
||||
"""
|
||||
commentable_id = getattr(self, commentable_id)
|
||||
self._setup_mock(
|
||||
user, mock_request,
|
||||
user, mock_is_forum_v2_enabled, mock_request,
|
||||
{
|
||||
"closed": False,
|
||||
"commentable_id": commentable_id,
|
||||
@@ -1742,14 +1893,14 @@ class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleSto
|
||||
|
||||
@ddt.data(*ddt_permissions_args)
|
||||
@ddt.unpack
|
||||
def test_threads_actions(self, user, commentable_id, status_code, mock_request):
|
||||
def test_threads_actions(self, user, commentable_id, status_code, mock_is_forum_v2_enabled, mock_request):
|
||||
"""
|
||||
Verify that voting, flagging, and following of threads is limited to members of the team or users with
|
||||
'edit_content' permission.
|
||||
"""
|
||||
commentable_id = getattr(self, commentable_id)
|
||||
self._setup_mock(
|
||||
user, mock_request,
|
||||
user, mock_is_forum_v2_enabled, mock_request,
|
||||
{"closed": False, "commentable_id": commentable_id, "body": "dummy body", "course_id": str(self.course.id)}
|
||||
)
|
||||
for action in ["upvote_thread", "downvote_thread", "un_flag_abuse_for_thread", "flag_abuse_for_thread",
|
||||
@@ -1772,6 +1923,19 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
|
||||
"""
|
||||
Forum actions are expected to launch analytics events. Test these here.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# pylint: disable=super-method-not-called
|
||||
@@ -1791,12 +1955,14 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
|
||||
|
||||
@patch('eventtracking.tracker.emit')
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
def test_response_event(self, mock_request, mock_emit):
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def test_response_event(self, mock_is_forum_v2_enabled, mock_request, mock_emit):
|
||||
"""
|
||||
Check to make sure an event is fired when a user responds to a thread.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
FORUM_THREAD_RESPONSE_CREATED.connect(event_receiver)
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"closed": False,
|
||||
"commentable_id": 'test_commentable_id',
|
||||
@@ -1833,12 +1999,14 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
|
||||
|
||||
@patch('eventtracking.tracker.emit')
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
def test_comment_event(self, mock_request, mock_emit):
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def test_comment_event(self, mock_is_forum_v2_enabled, mock_request, mock_emit):
|
||||
"""
|
||||
Ensure an event is fired when someone comments on a response.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
FORUM_RESPONSE_COMMENT_CREATED.connect(event_receiver)
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"closed": False,
|
||||
"depth": 1,
|
||||
@@ -1875,6 +2043,7 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
|
||||
|
||||
@patch('eventtracking.tracker.emit')
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
@ddt.data((
|
||||
'create_thread',
|
||||
'edx.forum.thread.created', {
|
||||
@@ -1896,7 +2065,7 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
|
||||
{'comment_id': 'dummy_comment_id'}
|
||||
))
|
||||
@ddt.unpack
|
||||
def test_team_events(self, view_name, event_name, view_data, view_kwargs, mock_request, mock_emit):
|
||||
def test_team_events(self, view_name, event_name, view_data, view_kwargs, mock_is_forum_v2_enabled, mock_request, mock_emit):
|
||||
user = self.student
|
||||
team = CourseTeamFactory.create(discussion_topic_id=TEAM_COMMENTABLE_ID)
|
||||
CourseTeamMembershipFactory.create(team=team, user=user)
|
||||
@@ -1905,6 +2074,7 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
|
||||
forum_event = views.TRACKING_LOG_TO_EVENT_MAPS.get(event_name)
|
||||
forum_event.connect(event_receiver)
|
||||
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
'closed': False,
|
||||
'commentable_id': TEAM_COMMENTABLE_ID,
|
||||
@@ -1943,9 +2113,11 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
|
||||
@ddt.unpack
|
||||
@patch('eventtracking.tracker.emit')
|
||||
@patch('openedx.core.djangoapps.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):
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def test_thread_voted_event(self, view_name, obj_id_name, obj_type, mock_is_forum_v2_enabled, mock_request, mock_emit):
|
||||
undo = view_name.startswith('undo')
|
||||
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
'closed': False,
|
||||
'commentable_id': 'test_commentable_id',
|
||||
@@ -1971,11 +2143,13 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
|
||||
@ddt.data('follow_thread', 'unfollow_thread',)
|
||||
@patch('eventtracking.tracker.emit')
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
def test_thread_followed_event(self, view_name, mock_request, mock_emit):
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def test_thread_followed_event(self, view_name, mock_is_forum_v2_enabled, mock_request, mock_emit):
|
||||
event_receiver = Mock()
|
||||
for signal in views.TRACKING_LOG_TO_EVENT_MAPS.values():
|
||||
signal.connect(event_receiver)
|
||||
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
'closed': False,
|
||||
'commentable_id': 'test_commentable_id',
|
||||
@@ -2025,10 +2199,11 @@ class UsersEndpointTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockRe
|
||||
cls.other_user = UserFactory.create(username="other")
|
||||
CourseEnrollmentFactory(user=cls.other_user, course_id=cls.course.id)
|
||||
|
||||
def set_post_counts(self, mock_request, threads_count=1, comments_count=1):
|
||||
def set_post_counts(self, mock_is_forum_v2_enabled, mock_request, threads_count=1, comments_count=1):
|
||||
"""
|
||||
sets up a mock response from the comments service for getting post counts for our other_user
|
||||
"""
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
self._set_mock_request_data(mock_request, {
|
||||
"threads_count": threads_count,
|
||||
"comments_count": comments_count,
|
||||
@@ -2042,15 +2217,17 @@ class UsersEndpointTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockRe
|
||||
return views.users(request, course_id=str(course_id))
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
def test_finds_exact_match(self, mock_request):
|
||||
self.set_post_counts(mock_request)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def test_finds_exact_match(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.set_post_counts(mock_is_forum_v2_enabled, mock_request)
|
||||
response = self.make_request(username="other")
|
||||
assert response.status_code == 200
|
||||
assert json.loads(response.content.decode('utf-8'))['users'] == [{'id': self.other_user.id, 'username': self.other_user.username}]
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
def test_finds_no_match(self, mock_request):
|
||||
self.set_post_counts(mock_request)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def test_finds_no_match(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.set_post_counts(mock_is_forum_v2_enabled, mock_request)
|
||||
response = self.make_request(username="othor")
|
||||
assert response.status_code == 200
|
||||
assert json.loads(response.content.decode('utf-8'))['users'] == []
|
||||
@@ -2086,8 +2263,9 @@ class UsersEndpointTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockRe
|
||||
assert 'users' not in content
|
||||
|
||||
@patch('openedx.core.djangoapps.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)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
def test_requires_matched_user_has_forum_content(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.set_post_counts(mock_is_forum_v2_enabled, mock_request, 0, 0)
|
||||
response = self.make_request(username="other")
|
||||
assert response.status_code == 200
|
||||
assert json.loads(response.content.decode('utf-8'))['users'] == []
|
||||
|
||||
@@ -562,7 +562,6 @@ def create_thread(request, course_id, commentable_id):
|
||||
params['context'] = ThreadContext.STANDALONE
|
||||
else:
|
||||
params['context'] = ThreadContext.COURSE
|
||||
|
||||
thread = cc.Thread(**params)
|
||||
|
||||
# Divide the thread if required
|
||||
|
||||
@@ -60,51 +60,76 @@ class CohortedTopicGroupIdTestMixin(GroupIdAssertionMixin):
|
||||
Provides test cases to verify that views pass the correct `group_id` to
|
||||
the comments service when requesting content in cohorted discussions.
|
||||
"""
|
||||
def call_view(self, mock_request, commentable_id, user, group_id, pass_group_id=True):
|
||||
def call_view(self, mock_is_forum_v2_enabled, mock_request, commentable_id, user, group_id, pass_group_id=True):
|
||||
"""
|
||||
Call the view for the implementing test class, constructing a request
|
||||
from the parameters.
|
||||
"""
|
||||
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
||||
|
||||
def test_cohorted_topic_student_without_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "cohorted_topic", self.student, '', pass_group_id=False)
|
||||
def test_cohorted_topic_student_without_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(mock_is_forum_v2_enabled, mock_request, "cohorted_topic", self.student, '', pass_group_id=False)
|
||||
self._assert_comments_service_called_with_group_id(mock_request, self.student_cohort.id)
|
||||
|
||||
def test_cohorted_topic_student_none_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "cohorted_topic", self.student, "")
|
||||
def test_cohorted_topic_student_none_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(mock_is_forum_v2_enabled, mock_request, "cohorted_topic", self.student, "")
|
||||
self._assert_comments_service_called_with_group_id(mock_request, self.student_cohort.id)
|
||||
|
||||
def test_cohorted_topic_student_with_own_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "cohorted_topic", self.student, self.student_cohort.id)
|
||||
def test_cohorted_topic_student_with_own_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(mock_is_forum_v2_enabled, mock_request, "cohorted_topic", self.student, self.student_cohort.id)
|
||||
self._assert_comments_service_called_with_group_id(mock_request, self.student_cohort.id)
|
||||
|
||||
def test_cohorted_topic_student_with_other_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "cohorted_topic", self.student, self.moderator_cohort.id)
|
||||
def test_cohorted_topic_student_with_other_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"cohorted_topic",
|
||||
self.student,
|
||||
self.moderator_cohort.id
|
||||
)
|
||||
self._assert_comments_service_called_with_group_id(mock_request, self.student_cohort.id)
|
||||
|
||||
def test_cohorted_topic_moderator_without_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "cohorted_topic", self.moderator, '', pass_group_id=False)
|
||||
def test_cohorted_topic_moderator_without_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"cohorted_topic",
|
||||
self.moderator,
|
||||
'',
|
||||
pass_group_id=False
|
||||
)
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_cohorted_topic_moderator_none_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "cohorted_topic", self.moderator, "")
|
||||
def test_cohorted_topic_moderator_none_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(mock_is_forum_v2_enabled, mock_request, "cohorted_topic", self.moderator, "")
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_cohorted_topic_moderator_with_own_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "cohorted_topic", self.moderator, self.moderator_cohort.id)
|
||||
def test_cohorted_topic_moderator_with_own_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"cohorted_topic",
|
||||
self.moderator,
|
||||
self.moderator_cohort.id
|
||||
)
|
||||
self._assert_comments_service_called_with_group_id(mock_request, self.moderator_cohort.id)
|
||||
|
||||
def test_cohorted_topic_moderator_with_other_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "cohorted_topic", self.moderator, self.student_cohort.id)
|
||||
def test_cohorted_topic_moderator_with_other_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"cohorted_topic",
|
||||
self.moderator,
|
||||
self.student_cohort.id
|
||||
)
|
||||
self._assert_comments_service_called_with_group_id(mock_request, self.student_cohort.id)
|
||||
|
||||
def test_cohorted_topic_moderator_with_invalid_group_id(self, mock_request):
|
||||
def test_cohorted_topic_moderator_with_invalid_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
invalid_id = self.student_cohort.id + self.moderator_cohort.id
|
||||
response = self.call_view(mock_request, "cohorted_topic", self.moderator, invalid_id) # lint-amnesty, pylint: disable=assignment-from-no-return
|
||||
response = self.call_view(mock_is_forum_v2_enabled, mock_request, "cohorted_topic", self.moderator, invalid_id) # lint-amnesty, pylint: disable=assignment-from-no-return
|
||||
assert response.status_code == 500
|
||||
|
||||
def test_cohorted_topic_enrollment_track_invalid_group_id(self, mock_request):
|
||||
def test_cohorted_topic_enrollment_track_invalid_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
CourseModeFactory.create(course_id=self.course.id, mode_slug=CourseMode.AUDIT)
|
||||
CourseModeFactory.create(course_id=self.course.id, mode_slug=CourseMode.VERIFIED)
|
||||
discussion_settings = CourseDiscussionSettings.get(self.course.id)
|
||||
@@ -115,7 +140,7 @@ class CohortedTopicGroupIdTestMixin(GroupIdAssertionMixin):
|
||||
})
|
||||
|
||||
invalid_id = -1000
|
||||
response = self.call_view(mock_request, "cohorted_topic", self.moderator, invalid_id) # lint-amnesty, pylint: disable=assignment-from-no-return
|
||||
response = self.call_view(mock_is_forum_v2_enabled, mock_request, "cohorted_topic", self.moderator, invalid_id) # lint-amnesty, pylint: disable=assignment-from-no-return
|
||||
assert response.status_code == 500
|
||||
|
||||
|
||||
@@ -124,57 +149,95 @@ class NonCohortedTopicGroupIdTestMixin(GroupIdAssertionMixin):
|
||||
Provides test cases to verify that views pass the correct `group_id` to
|
||||
the comments service when requesting content in non-cohorted discussions.
|
||||
"""
|
||||
def call_view(self, mock_request, commentable_id, user, group_id, pass_group_id=True):
|
||||
def call_view(self, mock_is_forum_v2_enabled, mock_request, commentable_id, user, group_id, pass_group_id=True):
|
||||
"""
|
||||
Call the view for the implementing test class, constructing a request
|
||||
from the parameters.
|
||||
"""
|
||||
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
||||
|
||||
def test_non_cohorted_topic_student_without_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "non_cohorted_topic", self.student, '', pass_group_id=False)
|
||||
def test_non_cohorted_topic_student_without_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"non_cohorted_topic",
|
||||
self.student,
|
||||
'',
|
||||
pass_group_id=False
|
||||
)
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_non_cohorted_topic_student_none_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "non_cohorted_topic", self.student, '')
|
||||
def test_non_cohorted_topic_student_none_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(mock_is_forum_v2_enabled, mock_request, "non_cohorted_topic", self.student, '')
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_non_cohorted_topic_student_with_own_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "non_cohorted_topic", self.student, self.student_cohort.id)
|
||||
def test_non_cohorted_topic_student_with_own_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"non_cohorted_topic",
|
||||
self.student,
|
||||
self.student_cohort.id
|
||||
)
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_non_cohorted_topic_student_with_other_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "non_cohorted_topic", self.student, self.moderator_cohort.id)
|
||||
def test_non_cohorted_topic_student_with_other_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"non_cohorted_topic",
|
||||
self.student,
|
||||
self.moderator_cohort.id
|
||||
)
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_non_cohorted_topic_moderator_without_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "non_cohorted_topic", self.moderator, '', pass_group_id=False)
|
||||
def test_non_cohorted_topic_moderator_without_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"non_cohorted_topic",
|
||||
self.moderator,
|
||||
"",
|
||||
pass_group_id=False,
|
||||
)
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_non_cohorted_topic_moderator_none_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "non_cohorted_topic", self.moderator, '')
|
||||
def test_non_cohorted_topic_moderator_none_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(mock_is_forum_v2_enabled, mock_request, "non_cohorted_topic", self.moderator, '')
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_non_cohorted_topic_moderator_with_own_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "non_cohorted_topic", self.moderator, self.moderator_cohort.id)
|
||||
def test_non_cohorted_topic_moderator_with_own_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"non_cohorted_topic",
|
||||
self.moderator,
|
||||
self.moderator_cohort.id,
|
||||
)
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_non_cohorted_topic_moderator_with_other_group_id(self, mock_request):
|
||||
self.call_view(mock_request, "non_cohorted_topic", self.moderator, self.student_cohort.id)
|
||||
def test_non_cohorted_topic_moderator_with_other_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"non_cohorted_topic",
|
||||
self.moderator,
|
||||
self.student_cohort.id,
|
||||
)
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_non_cohorted_topic_moderator_with_invalid_group_id(self, mock_request):
|
||||
def test_non_cohorted_topic_moderator_with_invalid_group_id(self, mock_is_forum_v2_enabled, mock_request):
|
||||
invalid_id = self.student_cohort.id + self.moderator_cohort.id
|
||||
self.call_view(mock_request, "non_cohorted_topic", self.moderator, invalid_id)
|
||||
self.call_view(mock_is_forum_v2_enabled, mock_request, "non_cohorted_topic", self.moderator, invalid_id)
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
def test_team_discussion_id_not_cohorted(self, mock_request):
|
||||
def test_team_discussion_id_not_cohorted(self, mock_is_forum_v2_enabled, mock_request):
|
||||
team = CourseTeamFactory(
|
||||
course_id=self.course.id,
|
||||
topic_id='topic-id'
|
||||
)
|
||||
|
||||
team.add_user(self.student)
|
||||
self.call_view(mock_request, team.discussion_topic_id, self.student, '')
|
||||
self.call_view(mock_is_forum_v2_enabled, mock_request, team.discussion_topic_id, self.student, '')
|
||||
|
||||
self._assert_comments_service_called_without_group_id(mock_request)
|
||||
|
||||
@@ -199,7 +199,7 @@ def _get_course(course_key: CourseKey, user: User, check_tab: bool = True) -> Co
|
||||
return course
|
||||
|
||||
|
||||
def _get_thread_and_context(request, thread_id, retrieve_kwargs=None):
|
||||
def _get_thread_and_context(request, thread_id, retrieve_kwargs=None, course_id=None):
|
||||
"""
|
||||
Retrieve the given thread and build a serializer context for it, returning
|
||||
both. This function also enforces access control for the thread (checking
|
||||
@@ -213,7 +213,7 @@ def _get_thread_and_context(request, thread_id, retrieve_kwargs=None):
|
||||
retrieve_kwargs["with_responses"] = False
|
||||
if "mark_as_read" not in retrieve_kwargs:
|
||||
retrieve_kwargs["mark_as_read"] = False
|
||||
cc_thread = Thread(id=thread_id).retrieve(**retrieve_kwargs)
|
||||
cc_thread = Thread(id=thread_id).retrieve(course_id=course_id, **retrieve_kwargs)
|
||||
course_key = CourseKey.from_string(cc_thread["course_id"])
|
||||
course = _get_course(course_key, request.user)
|
||||
context = get_context(course, request, cc_thread)
|
||||
@@ -1645,7 +1645,8 @@ def get_thread(request, thread_id, requested_fields=None, course_id=None):
|
||||
retrieve_kwargs={
|
||||
"with_responses": True,
|
||||
"user_id": str(request.user.id),
|
||||
}
|
||||
},
|
||||
course_id=course_id,
|
||||
)
|
||||
if course_id and course_id != cc_thread.course_id:
|
||||
raise ThreadNotFoundError("Thread not found.")
|
||||
|
||||
@@ -202,7 +202,7 @@ class DiscussionNotificationSender:
|
||||
|
||||
while has_more_subscribers:
|
||||
|
||||
subscribers = Subscription.fetch(self.thread.id, query_params={'page': page})
|
||||
subscribers = Subscription.fetch(self.thread.id, self.course.id, query_params={'page': page})
|
||||
if page <= subscribers.num_pages:
|
||||
for subscriber in subscribers.collection:
|
||||
# Check if the subscriber is not the thread creator or response creator
|
||||
|
||||
@@ -68,7 +68,7 @@ def get_context(course, request, thread=None):
|
||||
moderator_user_ids = get_moderator_users_list(course.id)
|
||||
ta_user_ids = get_course_ta_users_list(course.id)
|
||||
requester = request.user
|
||||
cc_requester = CommentClientUser.from_django_user(requester).retrieve()
|
||||
cc_requester = CommentClientUser.from_django_user(requester).retrieve(course_id=course.id)
|
||||
cc_requester["course_id"] = course.id
|
||||
course_discussion_settings = CourseDiscussionSettings.get(course.id)
|
||||
is_global_staff = GlobalStaff().has_user(requester)
|
||||
|
||||
@@ -1248,6 +1248,22 @@ class GetCommentListTest(ForumsEnableMixin, CommentsServiceMockMixin, SharedModu
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.maxDiff = None # pylint: disable=invalid-name
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
@@ -1872,6 +1888,12 @@ class CreateThreadTest(
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
self.request = RequestFactory().get("/test_path")
|
||||
@@ -2198,6 +2220,22 @@ class CreateCommentTest(
|
||||
self.course = CourseFactory.create()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
self.request = RequestFactory().get("/test_path")
|
||||
@@ -2589,6 +2627,17 @@ class UpdateThreadTest(
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
@@ -3153,6 +3202,22 @@ class UpdateCommentTest(
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
self.request = RequestFactory().get("/test_path")
|
||||
@@ -3670,6 +3735,22 @@ class DeleteThreadTest(
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
self.request = RequestFactory().get("/test_path")
|
||||
@@ -3823,6 +3904,22 @@ class DeleteCommentTest(
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
self.request = RequestFactory().get("/test_path")
|
||||
@@ -3991,6 +4088,17 @@ class RetrieveThreadTest(
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
self.request = RequestFactory().get("/test_path")
|
||||
|
||||
@@ -54,6 +54,12 @@ class SerializerTestMixin(ForumsEnableMixin, CommentsServiceMockMixin, UrlResetM
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.maxDiff = None # pylint: disable=invalid-name
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
@@ -571,6 +577,12 @@ class ThreadSerializerDeserializationTest(
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
self.request = RequestFactory().get("/dummy")
|
||||
@@ -802,6 +814,22 @@ class CommentSerializerDeserializationTest(ForumsEnableMixin, CommentsServiceMoc
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user = UserFactory.create()
|
||||
self.register_get_user_response(self.user)
|
||||
self.request = RequestFactory().get("/dummy")
|
||||
|
||||
@@ -58,10 +58,27 @@ class TestNewThreadCreatedNotification(DiscussionAPIViewTestMixin, ModuleStoreTe
|
||||
Setup test case
|
||||
"""
|
||||
super().setUp()
|
||||
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
# Creating a course
|
||||
self.course = CourseFactory.create()
|
||||
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread",
|
||||
return_value=self.course.id
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment",
|
||||
return_value=self.course.id
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
# Creating relative discussion and cohort settings
|
||||
CourseCohortsSettings.objects.create(course_id=str(self.course.id))
|
||||
CourseDiscussionSettings.objects.create(course_id=str(self.course.id), _divided_discussions='[]')
|
||||
@@ -250,8 +267,26 @@ class TestSendResponseNotifications(DiscussionAPIViewTestMixin, ModuleStoreTestC
|
||||
super().setUp()
|
||||
httpretty.reset()
|
||||
httpretty.enable()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
self.course = CourseFactory.create()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread",
|
||||
return_value=self.course.id
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment",
|
||||
return_value=self.course.id
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user_1 = UserFactory.create()
|
||||
CourseEnrollment.enroll(self.user_1, self.course.id)
|
||||
self.user_2 = UserFactory.create()
|
||||
@@ -536,8 +571,26 @@ class TestSendCommentNotification(DiscussionAPIViewTestMixin, ModuleStoreTestCas
|
||||
super().setUp()
|
||||
httpretty.reset()
|
||||
httpretty.enable()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
self.course = CourseFactory.create()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread",
|
||||
return_value=self.course.id
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment",
|
||||
return_value=self.course.id
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user_1 = UserFactory.create()
|
||||
CourseEnrollment.enroll(self.user_1, self.course.id)
|
||||
self.user_2 = UserFactory.create()
|
||||
@@ -603,8 +656,26 @@ class TestResponseEndorsedNotifications(DiscussionAPIViewTestMixin, ModuleStoreT
|
||||
super().setUp()
|
||||
httpretty.reset()
|
||||
httpretty.enable()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
self.course = CourseFactory.create()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread",
|
||||
return_value=self.course.id
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment",
|
||||
return_value=self.course.id
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.user_1 = UserFactory.create()
|
||||
CourseEnrollment.enroll(self.user_1, self.course.id)
|
||||
self.user_2 = UserFactory.create()
|
||||
|
||||
@@ -171,6 +171,12 @@ class UploadFileViewTest(ForumsEnableMixin, CommentsServiceMockMixin, UrlResetMi
|
||||
self.user = UserFactory.create(password=self.TEST_PASSWORD)
|
||||
self.course = CourseFactory.create(org='a', course='b', run='c', start=datetime.now(UTC))
|
||||
self.url = reverse("upload_file", kwargs={"course_id": str(self.course.id)})
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def user_login(self):
|
||||
"""
|
||||
@@ -301,6 +307,7 @@ class UploadFileViewTest(ForumsEnableMixin, CommentsServiceMockMixin, UrlResetMi
|
||||
|
||||
@ddt.ddt
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_FORUM_V2": False})
|
||||
class CommentViewSetListByUserTest(
|
||||
ForumsEnableMixin,
|
||||
CommentsServiceMockMixin,
|
||||
@@ -319,6 +326,12 @@ class CommentViewSetListByUserTest(
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
self.user = UserFactory.create(password=self.TEST_PASSWORD)
|
||||
self.register_get_user_response(self.user)
|
||||
@@ -500,6 +513,12 @@ class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.url = reverse("discussion_course", kwargs={"course_id": str(self.course.id)})
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_404(self):
|
||||
response = self.client.get(
|
||||
@@ -561,6 +580,12 @@ class RetireViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
self.superuser_client = APIClient()
|
||||
self.retired_username = get_retired_username_by_username(self.user.username)
|
||||
self.url = reverse("retire_discussion_user")
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def assert_response_correct(self, response, expected_status, expected_content):
|
||||
"""
|
||||
@@ -631,6 +656,12 @@ class ReplaceUsernamesViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
self.worker_client = APIClient()
|
||||
self.new_username = "test_username_replacement"
|
||||
self.url = reverse("replace_discussion_username")
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def assert_response_correct(self, response, expected_status, expected_content):
|
||||
"""
|
||||
@@ -733,6 +764,12 @@ class CourseTopicsViewTest(DiscussionAPIViewTestMixin, CommentsServiceMockMixin,
|
||||
"courseware-3": {"discussion": 7, "question": 2},
|
||||
}
|
||||
self.register_get_course_commentable_counts_response(self.course.id, self.thread_counts_map)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def create_course(self, blocks_count, module_store, topics):
|
||||
"""
|
||||
@@ -988,6 +1025,12 @@ class CourseTopicsViewV3Test(DiscussionAPIViewTestMixin, CommentsServiceMockMixi
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.url = reverse("course_topics_v3", kwargs={"course_id": str(self.course.id)})
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_basic(self):
|
||||
response = self.client.get(self.url)
|
||||
@@ -1024,6 +1067,12 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
|
||||
super().setUp()
|
||||
self.author = UserFactory.create()
|
||||
self.url = reverse("thread-list")
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def create_source_thread(self, overrides=None):
|
||||
"""
|
||||
@@ -1365,6 +1414,12 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.url = reverse("thread-list")
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_basic(self):
|
||||
self.register_get_user_response(self.user)
|
||||
@@ -1437,6 +1492,17 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest
|
||||
self.unsupported_media_type = JSONParser.media_type
|
||||
super().setUp()
|
||||
self.url = reverse("thread-detail", kwargs={"thread_id": "test_thread"})
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_basic(self):
|
||||
self.register_get_user_response(self.user)
|
||||
@@ -1581,6 +1647,17 @@ class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
super().setUp()
|
||||
self.url = reverse("thread-detail", kwargs={"thread_id": "test_thread"})
|
||||
self.thread_id = "test_thread"
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_basic(self):
|
||||
self.register_get_user_response(self.user)
|
||||
@@ -1681,6 +1758,12 @@ class LearnerThreadViewAPITest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
|
||||
]
|
||||
self.url = reverse("discussion_learner_threads", kwargs={'course_id': str(self.course.id)})
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def update_thread(self, thread):
|
||||
"""
|
||||
@@ -1923,6 +2006,17 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pr
|
||||
self.url = reverse("comment-list")
|
||||
self.thread_id = "test_thread"
|
||||
self.storage = get_profile_image_storage()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def create_source_comment(self, overrides=None):
|
||||
"""
|
||||
@@ -2377,6 +2471,22 @@ class CommentViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
super().setUp()
|
||||
self.url = reverse("comment-detail", kwargs={"comment_id": "test_comment"})
|
||||
self.comment_id = "test_comment"
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_basic(self):
|
||||
self.register_get_user_response(self.user)
|
||||
@@ -2416,6 +2526,23 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.url = reverse("comment-list")
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_basic(self):
|
||||
self.register_get_user_response(self.user)
|
||||
@@ -2518,6 +2645,22 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
|
||||
httpretty.enable()
|
||||
self.addCleanup(httpretty.reset)
|
||||
self.addCleanup(httpretty.disable)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.register_get_user_response(self.user)
|
||||
self.url = reverse("comment-detail", kwargs={"comment_id": "test_comment"})
|
||||
|
||||
@@ -2640,6 +2783,22 @@ class ThreadViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase,
|
||||
super().setUp()
|
||||
self.url = reverse("thread-detail", kwargs={"thread_id": "test_thread"})
|
||||
self.thread_id = "test_thread"
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_basic(self):
|
||||
self.register_get_user_response(self.user)
|
||||
@@ -2693,6 +2852,22 @@ class CommentViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase
|
||||
self.url = reverse("comment-detail", kwargs={"comment_id": "test_comment"})
|
||||
self.thread_id = "test_thread"
|
||||
self.comment_id = "test_comment"
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def make_comment_data(self, comment_id, parent_id=None, children=[]): # pylint: disable=W0102
|
||||
"""
|
||||
@@ -2838,6 +3013,12 @@ class CourseDiscussionSettingsAPIViewTest(APITestCase, UrlResetMixin, ModuleStor
|
||||
self.path = reverse('discussion_course_settings', kwargs={'course_id': str(self.course.id)})
|
||||
self.password = self.TEST_PASSWORD
|
||||
self.user = UserFactory(username='staff', password=self.password, is_staff=True)
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def _get_oauth_headers(self, user):
|
||||
"""Return the OAuth headers for testing OAuth authentication"""
|
||||
@@ -3127,6 +3308,12 @@ class CourseDiscussionRolesAPIViewTest(APITestCase, UrlResetMixin, ModuleStoreTe
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.course = CourseFactory.create(
|
||||
org="x",
|
||||
course="y",
|
||||
@@ -3318,6 +3505,12 @@ class CourseActivityStatsTest(ForumsEnableMixin, UrlResetMixin, CommentsServiceM
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.course = CourseFactory.create()
|
||||
self.course_key = str(self.course.id)
|
||||
seed_permissions_roles(self.course.id)
|
||||
|
||||
@@ -232,6 +232,22 @@ class TaskTestCase(ModuleStoreTestCase): # lint-amnesty, pylint: disable=missin
|
||||
thread_permalink = '/courses/discussion/dummy_discussion_id'
|
||||
self.permalink_patcher = mock.patch('lms.djangoapps.discussion.tasks.permalink', return_value=thread_permalink)
|
||||
self.mock_permalink = self.permalink_patcher.start()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
|
||||
@@ -4,6 +4,7 @@ Tests the forum notification views.
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from unittest import mock
|
||||
from unittest.mock import ANY, Mock, call, patch
|
||||
|
||||
import ddt
|
||||
@@ -109,9 +110,20 @@ class ViewsExceptionTestCase(UrlResetMixin, ModuleStoreTestCase): # lint-amnest
|
||||
config = ForumsConfig.current()
|
||||
config.enabled = True
|
||||
config.save()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@patch('common.djangoapps.student.models.user.cc.User.from_django_user')
|
||||
@patch('common.djangoapps.student.models.user.cc.User.active_threads')
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.user.User.active_threads')
|
||||
def test_user_profile_exception(self, mock_threads, mock_from_django_user):
|
||||
|
||||
# Mock the code that makes the HTTP requests to the cs_comment_service app
|
||||
@@ -323,6 +335,17 @@ class SingleThreadTestCase(ForumsEnableMixin, ModuleStoreTestCase): # lint-amne
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
self.course = CourseFactory.create(discussion_topics={'dummy discussion': {'id': 'dummy_discussion_id'}})
|
||||
self.student = UserFactory.create()
|
||||
@@ -513,6 +536,20 @@ class SingleThreadQueryCountTestCase(ForumsEnableMixin, ModuleStoreTestCase):
|
||||
Ensures the number of modulestore queries and number of sql queries are
|
||||
independent of the number of responses retrieved for a given discussion thread.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@ddt.data(
|
||||
# split mongo: 3 queries, regardless of thread response size.
|
||||
(False, 1, 2, 2, 21, 8),
|
||||
@@ -582,6 +619,20 @@ class SingleThreadQueryCountTestCase(ForumsEnableMixin, ModuleStoreTestCase):
|
||||
@patch('requests.request', autospec=True)
|
||||
class SingleCohortedThreadTestCase(CohortedTestCase): # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def _create_mock_cohorted_thread(self, mock_request): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
mock_text = "dummy content"
|
||||
mock_thread_id = "test_thread_id"
|
||||
@@ -644,6 +695,20 @@ class SingleCohortedThreadTestCase(CohortedTestCase): # lint-amnesty, pylint: d
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
class SingleThreadAccessTestCase(CohortedTestCase): # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def call_view(self, mock_request, commentable_id, user, group_id, thread_group_id=None, pass_group_id=True): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
thread_id = "test_thread_id"
|
||||
mock_request.side_effect = make_mock_request_impl(
|
||||
@@ -746,6 +811,20 @@ class SingleThreadAccessTestCase(CohortedTestCase): # lint-amnesty, pylint: dis
|
||||
class SingleThreadGroupIdTestCase(CohortedTestCase, GroupIdAssertionMixin): # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
cs_endpoint = "/threads/dummy_thread_id"
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def call_view(self, mock_request, commentable_id, user, group_id, pass_group_id=True, is_ajax=False): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
mock_request.side_effect = make_mock_request_impl(
|
||||
course=self.course, text="dummy context", group_id=self.student_cohort.id
|
||||
@@ -881,6 +960,22 @@ class SingleThreadContentGroupTestCase(ForumsEnableMixin, UrlResetMixin, Content
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def assert_can_access(self, user, discussion_id, thread_id, should_have_access):
|
||||
"""
|
||||
@@ -1046,6 +1141,7 @@ class InlineDiscussionContextTestCase(ForumsEnableMixin, ModuleStoreTestCase):
|
||||
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
class InlineDiscussionGroupIdTestCase( # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
CohortedTestCase,
|
||||
CohortedTopicGroupIdTestMixin,
|
||||
@@ -1056,8 +1152,22 @@ class InlineDiscussionGroupIdTestCase( # lint-amnesty, pylint: disable=missing-
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cohorted_commentable_id = 'cohorted_topic'
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def call_view(self, mock_request, commentable_id, user, group_id, pass_group_id=True):
|
||||
def call_view(
|
||||
self,
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
commentable_id,
|
||||
user,
|
||||
group_id,
|
||||
pass_group_id=True
|
||||
): # pylint: disable=arguments-differ
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
kwargs = {'commentable_id': self.cohorted_commentable_id}
|
||||
if group_id:
|
||||
# avoid causing a server error when the LMS chokes attempting
|
||||
@@ -1084,8 +1194,9 @@ class InlineDiscussionGroupIdTestCase( # lint-amnesty, pylint: disable=missing-
|
||||
commentable_id
|
||||
)
|
||||
|
||||
def test_group_info_in_ajax_response(self, mock_request):
|
||||
def test_group_info_in_ajax_response(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
self.cohorted_commentable_id,
|
||||
self.student,
|
||||
@@ -1097,10 +1208,29 @@ class InlineDiscussionGroupIdTestCase( # lint-amnesty, pylint: disable=missing-
|
||||
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
class ForumFormDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupIdTestMixin): # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
cs_endpoint = "/threads"
|
||||
|
||||
def call_view(self, mock_request, commentable_id, user, group_id, pass_group_id=True, is_ajax=False): # pylint: disable=arguments-differ
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def call_view(
|
||||
self,
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
commentable_id,
|
||||
user,
|
||||
group_id,
|
||||
pass_group_id=True,
|
||||
is_ajax=False
|
||||
): # pylint: disable=arguments-differ
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
kwargs = {}
|
||||
if group_id:
|
||||
kwargs['group_id'] = group_id
|
||||
@@ -1120,8 +1250,9 @@ class ForumFormDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupIdT
|
||||
**headers
|
||||
)
|
||||
|
||||
def test_group_info_in_html_response(self, mock_request):
|
||||
def test_group_info_in_html_response(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"cohorted_topic",
|
||||
self.student,
|
||||
@@ -1129,8 +1260,9 @@ class ForumFormDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupIdT
|
||||
)
|
||||
self._assert_html_response_contains_group_info(response)
|
||||
|
||||
def test_group_info_in_ajax_response(self, mock_request):
|
||||
def test_group_info_in_ajax_response(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"cohorted_topic",
|
||||
self.student,
|
||||
@@ -1143,16 +1275,38 @@ class ForumFormDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupIdT
|
||||
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupIdTestMixin): # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
cs_endpoint = "/active_threads"
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def call_view_for_profiled_user(
|
||||
self, mock_request, requesting_user, profiled_user, group_id, pass_group_id, is_ajax=False
|
||||
self,
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
requesting_user,
|
||||
profiled_user,
|
||||
group_id,
|
||||
pass_group_id,
|
||||
is_ajax=False
|
||||
):
|
||||
"""
|
||||
Calls "user_profile" view method on behalf of "requesting_user" to get information about
|
||||
the user "profiled_user".
|
||||
"""
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
kwargs = {}
|
||||
if group_id:
|
||||
kwargs['group_id'] = group_id
|
||||
@@ -1172,13 +1326,23 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
|
||||
**headers
|
||||
)
|
||||
|
||||
def call_view(self, mock_request, _commentable_id, user, group_id, pass_group_id=True, is_ajax=False): # pylint: disable=arguments-differ
|
||||
def call_view(
|
||||
self,
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
_commentable_id,
|
||||
user,
|
||||
group_id,
|
||||
pass_group_id=True,
|
||||
is_ajax=False
|
||||
): # pylint: disable=arguments-differ
|
||||
return self.call_view_for_profiled_user(
|
||||
mock_request, user, user, group_id, pass_group_id=pass_group_id, is_ajax=is_ajax
|
||||
mock_is_forum_v2_enabled, mock_request, user, user, group_id, pass_group_id=pass_group_id, is_ajax=is_ajax
|
||||
)
|
||||
|
||||
def test_group_info_in_html_response(self, mock_request):
|
||||
def test_group_info_in_html_response(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"cohorted_topic",
|
||||
self.student,
|
||||
@@ -1187,8 +1351,9 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
|
||||
)
|
||||
self._assert_html_response_contains_group_info(response)
|
||||
|
||||
def test_group_info_in_ajax_response(self, mock_request):
|
||||
def test_group_info_in_ajax_response(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"cohorted_topic",
|
||||
self.student,
|
||||
@@ -1200,7 +1365,14 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
|
||||
)
|
||||
|
||||
def _test_group_id_passed_to_user_profile(
|
||||
self, mock_request, expect_group_id_in_request, requesting_user, profiled_user, group_id, pass_group_id
|
||||
self,
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
expect_group_id_in_request,
|
||||
requesting_user,
|
||||
profiled_user,
|
||||
group_id,
|
||||
pass_group_id
|
||||
):
|
||||
"""
|
||||
Helper method for testing whether or not group_id was passed to the user_profile request.
|
||||
@@ -1221,10 +1393,11 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
|
||||
has_course_id = "course_id" in params
|
||||
if (for_specific_course and has_course_id) or (not for_specific_course and not has_course_id):
|
||||
return params
|
||||
pytest.fail("Did not find appropriate user_profile call for 'for_specific_course'=" + for_specific_course)
|
||||
pytest.fail(f"Did not find appropriate user_profile call for 'for_specific_course'={for_specific_course}")
|
||||
|
||||
mock_request.reset_mock()
|
||||
self.call_view_for_profiled_user(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
requesting_user,
|
||||
profiled_user,
|
||||
@@ -1243,7 +1416,7 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
|
||||
else:
|
||||
assert 'group_id' not in params_with_course_id
|
||||
|
||||
def test_group_id_passed_to_user_profile_student(self, mock_request):
|
||||
def test_group_id_passed_to_user_profile_student(self, mock_is_forum_v2_enabled, mock_request):
|
||||
"""
|
||||
Test that the group id is always included when requesting user profile information for a particular
|
||||
course if the requester does not have discussion moderation privileges.
|
||||
@@ -1254,7 +1427,13 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
|
||||
(non-privileged user).
|
||||
"""
|
||||
self._test_group_id_passed_to_user_profile(
|
||||
mock_request, True, self.student, profiled_user, self.student_cohort.id, pass_group_id
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
True,
|
||||
self.student,
|
||||
profiled_user,
|
||||
self.student_cohort.id,
|
||||
pass_group_id
|
||||
)
|
||||
|
||||
# In all these test cases, the requesting_user is the student (non-privileged user).
|
||||
@@ -1264,7 +1443,7 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
|
||||
verify_group_id_always_present(profiled_user=self.moderator, pass_group_id=True)
|
||||
verify_group_id_always_present(profiled_user=self.moderator, pass_group_id=False)
|
||||
|
||||
def test_group_id_user_profile_moderator(self, mock_request):
|
||||
def test_group_id_user_profile_moderator(self, mock_is_forum_v2_enabled, mock_request):
|
||||
"""
|
||||
Test that the group id is only included when a privileged user requests user profile information for a
|
||||
particular course and user if the group_id is explicitly passed in.
|
||||
@@ -1274,7 +1453,13 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
|
||||
Helper method to verify that group_id is present.
|
||||
"""
|
||||
self._test_group_id_passed_to_user_profile(
|
||||
mock_request, True, self.moderator, profiled_user, requested_cohort.id, pass_group_id
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
True,
|
||||
self.moderator,
|
||||
profiled_user,
|
||||
requested_cohort.id,
|
||||
pass_group_id
|
||||
)
|
||||
|
||||
def verify_group_id_not_present(profiled_user, pass_group_id, requested_cohort=self.moderator_cohort):
|
||||
@@ -1282,7 +1467,13 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
|
||||
Helper method to verify that group_id is not present.
|
||||
"""
|
||||
self._test_group_id_passed_to_user_profile(
|
||||
mock_request, False, self.moderator, profiled_user, requested_cohort.id, pass_group_id
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
False,
|
||||
self.moderator,
|
||||
profiled_user,
|
||||
requested_cohort.id,
|
||||
pass_group_id
|
||||
)
|
||||
|
||||
# In all these test cases, the requesting_user is the moderator (privileged user).
|
||||
@@ -1301,10 +1492,28 @@ class UserProfileDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupI
|
||||
|
||||
|
||||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
|
||||
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
|
||||
class FollowedThreadsDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGroupIdTestMixin): # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
cs_endpoint = "/subscribed_threads"
|
||||
|
||||
def call_view(self, mock_request, commentable_id, user, group_id, pass_group_id=True):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def call_view(
|
||||
self,
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
commentable_id,
|
||||
user,
|
||||
group_id,
|
||||
pass_group_id=True
|
||||
): # pylint: disable=arguments-differ
|
||||
mock_is_forum_v2_enabled.return_value = False
|
||||
kwargs = {}
|
||||
if group_id:
|
||||
kwargs['group_id'] = group_id
|
||||
@@ -1325,8 +1534,9 @@ class FollowedThreadsDiscussionGroupIdTestCase(CohortedTestCase, CohortedTopicGr
|
||||
user.id
|
||||
)
|
||||
|
||||
def test_group_info_in_ajax_response(self, mock_request):
|
||||
def test_group_info_in_ajax_response(self, mock_is_forum_v2_enabled, mock_request):
|
||||
response = self.call_view(
|
||||
mock_is_forum_v2_enabled,
|
||||
mock_request,
|
||||
"cohorted_topic",
|
||||
self.student,
|
||||
@@ -1528,6 +1738,22 @@ class CommentsServiceRequestHeadersTestCase(ForumsEnableMixin, UrlResetMixin, Mo
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment"
|
||||
)
|
||||
self.mock_get_course_id_by_comment = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
username = "foo"
|
||||
password = "bar"
|
||||
@@ -1742,6 +1968,20 @@ class SingleThreadUnicodeTestCase(ForumsEnableMixin, SharedModuleStoreTestCase,
|
||||
with super().setUpClassAndTestData():
|
||||
cls.course = CourseFactory.create(discussion_topics={'dummy_discussion_id': {'id': 'dummy_discussion_id'}})
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
super().setUpTestData()
|
||||
@@ -1858,7 +2098,17 @@ class EnterpriseConsentTestCase(EnterpriseTestConsentRequired, ForumsEnableMixin
|
||||
def setUp(self):
|
||||
# Invoke UrlResetMixin setUp
|
||||
super().setUp()
|
||||
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
username = "foo"
|
||||
password = "bar"
|
||||
|
||||
@@ -2195,6 +2445,17 @@ class ThreadViewedEventTestCase(EventTestMixin, ForumsEnableMixin, UrlResetMixin
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super().setUp('lms.djangoapps.discussion.django_comment_client.base.views.tracker')
|
||||
|
||||
patcher = mock.patch(
|
||||
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
|
||||
return_value=False
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher = mock.patch(
|
||||
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread"
|
||||
)
|
||||
self.mock_get_course_id_by_thread = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.course = CourseFactory.create(
|
||||
teams_configuration=TeamsConfig({
|
||||
'topics': [{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Discussions feature toggles
|
||||
"""
|
||||
|
||||
from openedx.core.djangoapps.discussions.config.waffle import WAFFLE_FLAG_NAMESPACE
|
||||
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
|
||||
|
||||
@@ -11,4 +12,6 @@ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
|
||||
# .. toggle_use_cases: temporary, open_edx
|
||||
# .. toggle_creation_date: 2021-11-05
|
||||
# .. toggle_target_removal_date: 2022-12-05
|
||||
ENABLE_DISCUSSIONS_MFE = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.enable_discussions_mfe', __name__)
|
||||
ENABLE_DISCUSSIONS_MFE = CourseWaffleFlag(
|
||||
f"{WAFFLE_FLAG_NAMESPACE}.enable_discussions_mfe", __name__
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
This module contains various configuration settings via
|
||||
waffle switches for the discussions app.
|
||||
"""
|
||||
|
||||
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
|
||||
|
||||
WAFFLE_FLAG_NAMESPACE = "discussions"
|
||||
@@ -43,3 +44,19 @@ ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND = CourseWaffleFlag(
|
||||
ENABLE_NEW_STRUCTURE_DISCUSSIONS = CourseWaffleFlag(
|
||||
f"{WAFFLE_FLAG_NAMESPACE}.enable_new_structure_discussions", __name__
|
||||
)
|
||||
|
||||
# .. toggle_name: discussions.enable_forum_v2
|
||||
# .. toggle_implementation: CourseWaffleFlag
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Waffle flag to use the forum v2 instead of v1(cs_comment_service)
|
||||
# .. toggle_use_cases: temporary, open_edx
|
||||
# .. toggle_creation_date: 2024-9-26
|
||||
# .. toggle_target_removal_date: 2025-12-05
|
||||
ENABLE_FORUM_V2 = CourseWaffleFlag(f"{WAFFLE_FLAG_NAMESPACE}.enable_forum_v2", __name__)
|
||||
|
||||
|
||||
def is_forum_v2_enabled(course_id):
|
||||
"""
|
||||
Returns a boolean if forum V2 is enabled on the course
|
||||
"""
|
||||
return ENABLE_FORUM_V2.is_enabled(course_id)
|
||||
|
||||
@@ -4,7 +4,9 @@ from bs4 import BeautifulSoup
|
||||
from openedx.core.djangoapps.django_comment_common.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
|
||||
from .utils import CommentClientRequestError, get_course_key, perform_request
|
||||
from forum import api as forum_api
|
||||
from openedx.core.djangoapps.discussions.config.waffle import is_forum_v2_enabled
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
@@ -68,14 +70,21 @@ class Comment(models.Model):
|
||||
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'
|
||||
)
|
||||
course_key = get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
if voteable.type == 'thread':
|
||||
response = forum_api.update_thread_flag(voteable.id, "flag", user.id, str(course_key))
|
||||
else:
|
||||
response = forum_api.update_comment_flag(voteable.id, "flag", user.id, str(course_key))
|
||||
else:
|
||||
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):
|
||||
@@ -85,18 +94,37 @@ class Comment(models.Model):
|
||||
url = _url_for_unflag_abuse_comment(voteable.id)
|
||||
else:
|
||||
raise CommentClientRequestError("Can flag/unflag for threads or comments")
|
||||
params = {'user_id': user.id}
|
||||
course_key = get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
if voteable.type == "thread":
|
||||
response = forum_api.update_thread_flag(
|
||||
thread_id=voteable.id,
|
||||
action="unflag",
|
||||
user_id=user.id,
|
||||
update_all=bool(removeAll),
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
response = forum_api.update_comment_flag(
|
||||
comment_id=voteable.id,
|
||||
action="unflag",
|
||||
user_id=user.id,
|
||||
update_all=bool(removeAll),
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
params = {'user_id': user.id}
|
||||
|
||||
if removeAll:
|
||||
params['all'] = True
|
||||
if removeAll:
|
||||
params['all'] = True
|
||||
|
||||
response = perform_request(
|
||||
'put',
|
||||
url,
|
||||
params,
|
||||
metric_tags=self._metric_tags,
|
||||
metric_action='comment.abuse.unflagged'
|
||||
)
|
||||
response = perform_request(
|
||||
'put',
|
||||
url,
|
||||
params,
|
||||
metric_tags=self._metric_tags,
|
||||
metric_action='comment.abuse.unflagged'
|
||||
)
|
||||
voteable._update_from_response(response)
|
||||
|
||||
@property
|
||||
|
||||
@@ -7,8 +7,10 @@ from typing import Dict, Optional
|
||||
from edx_django_utils.monitoring import function_trace
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from forum import api as forum_api
|
||||
from openedx.core.djangoapps.django_comment_common.comment_client import settings
|
||||
from openedx.core.djangoapps.django_comment_common.comment_client.utils import perform_request
|
||||
from openedx.core.djangoapps.discussions.config.waffle import is_forum_v2_enabled
|
||||
|
||||
|
||||
def get_course_commentable_counts(course_key: CourseKey) -> Dict[str, Dict[str, int]]:
|
||||
@@ -29,17 +31,20 @@ def get_course_commentable_counts(course_key: CourseKey) -> Dict[str, Dict[str,
|
||||
}
|
||||
|
||||
"""
|
||||
url = f"{settings.PREFIX}/commentables/{course_key}/counts"
|
||||
response = perform_request(
|
||||
'get',
|
||||
url,
|
||||
metric_tags=[
|
||||
f"course_key:{course_key}",
|
||||
"function:get_course_commentable_counts",
|
||||
],
|
||||
metric_action='commentable_stats.retrieve',
|
||||
)
|
||||
return response
|
||||
if is_forum_v2_enabled(course_key):
|
||||
commentable_stats = forum_api.get_commentables_stats(str(course_key))
|
||||
else:
|
||||
url = f"{settings.PREFIX}/commentables/{course_key}/counts"
|
||||
commentable_stats = perform_request(
|
||||
'get',
|
||||
url,
|
||||
metric_tags=[
|
||||
f"course_key:{course_key}",
|
||||
"function:get_course_commentable_counts",
|
||||
],
|
||||
metric_action='commentable_stats.retrieve',
|
||||
)
|
||||
return commentable_stats
|
||||
|
||||
|
||||
@function_trace("get_course_user_stats")
|
||||
@@ -76,17 +81,21 @@ def get_course_user_stats(course_key: CourseKey, params: Optional[Dict] = None)
|
||||
"""
|
||||
if params is None:
|
||||
params = {}
|
||||
url = f"{settings.PREFIX}/users/{course_key}/stats"
|
||||
return perform_request(
|
||||
'get',
|
||||
url,
|
||||
params,
|
||||
metric_action='user.course_stats',
|
||||
metric_tags=[
|
||||
f"course_key:{course_key}",
|
||||
"function:get_course_user_stats",
|
||||
],
|
||||
)
|
||||
if is_forum_v2_enabled(course_key):
|
||||
course_stats = forum_api.get_user_course_stats(str(course_key), **params)
|
||||
else:
|
||||
url = f"{settings.PREFIX}/users/{course_key}/stats"
|
||||
course_stats = perform_request(
|
||||
'get',
|
||||
url,
|
||||
params,
|
||||
metric_action='user.course_stats',
|
||||
metric_tags=[
|
||||
f"course_key:{course_key}",
|
||||
"function:get_course_user_stats",
|
||||
],
|
||||
)
|
||||
return course_stats
|
||||
|
||||
|
||||
@function_trace("update_course_users_stats")
|
||||
@@ -100,13 +109,17 @@ def update_course_users_stats(course_key: CourseKey) -> Dict:
|
||||
Returns:
|
||||
dict: data returned by API. Contains count of users updated.
|
||||
"""
|
||||
url = f"{settings.PREFIX}/users/{course_key}/update_stats"
|
||||
return perform_request(
|
||||
'post',
|
||||
url,
|
||||
metric_action='user.update_course_stats',
|
||||
metric_tags=[
|
||||
f"course_key:{course_key}",
|
||||
"function:update_course_users_stats",
|
||||
],
|
||||
)
|
||||
if is_forum_v2_enabled(course_key):
|
||||
course_stats = forum_api.update_users_in_course(str(course_key))
|
||||
else:
|
||||
url = f"{settings.PREFIX}/users/{course_key}/update_stats"
|
||||
course_stats = perform_request(
|
||||
'post',
|
||||
url,
|
||||
metric_action='user.update_course_stats',
|
||||
metric_tags=[
|
||||
f"course_key:{course_key}",
|
||||
"function:update_course_users_stats",
|
||||
],
|
||||
)
|
||||
return course_stats
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
import logging
|
||||
|
||||
from .utils import CommentClientRequestError, extract, perform_request
|
||||
from .utils import CommentClientRequestError, extract, perform_request, get_course_key
|
||||
from forum import api as forum_api
|
||||
from openedx.core.djangoapps.discussions.config.waffle import is_forum_v2_enabled
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -69,14 +71,25 @@ class Model:
|
||||
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'
|
||||
)
|
||||
course_id = self.attributes.get("course_id") or kwargs.get("course_id")
|
||||
if not course_id:
|
||||
course_id = forum_api.get_course_id_by_comment(self.id)
|
||||
course_key = get_course_key(course_id)
|
||||
response = None
|
||||
if is_forum_v2_enabled(course_key):
|
||||
if self.type == "comment":
|
||||
response = forum_api.get_parent_comment(comment_id=self.attributes["id"], course_id=str(course_key))
|
||||
if response is None:
|
||||
raise CommentClientRequestError("Forum v2 API call is missing")
|
||||
else:
|
||||
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
|
||||
@@ -151,33 +164,27 @@ class Model:
|
||||
"""
|
||||
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'
|
||||
)
|
||||
response = self.handle_update(params)
|
||||
else: # otherwise, treat this as an insert
|
||||
response = self.handle_create(params)
|
||||
|
||||
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')
|
||||
course_key = get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
response = None
|
||||
if self.type == "comment":
|
||||
response = forum_api.delete_comment(comment_id=self.attributes["id"], course_id=str(course_key))
|
||||
elif self.type == "thread":
|
||||
response = forum_api.delete_thread(thread_id=self.attributes["id"], course_id=str(course_key))
|
||||
if response is None:
|
||||
raise CommentClientRequestError("Forum v2 API call is missing")
|
||||
else:
|
||||
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)
|
||||
|
||||
@@ -208,3 +215,157 @@ class Model:
|
||||
raise CommentClientRequestError(f"Cannot perform action {action} without id") # lint-amnesty, pylint: disable=raise-missing-from
|
||||
else: # action must be in DEFAULT_ACTIONS_WITHOUT_ID now
|
||||
return cls.url_without_id()
|
||||
|
||||
def handle_update(self, params=None):
|
||||
request_params = self.updatable_attributes()
|
||||
if params:
|
||||
request_params.update(params)
|
||||
course_id = self.attributes.get("course_id") or request_params.get("course_id")
|
||||
course_key = get_course_key(course_id)
|
||||
if is_forum_v2_enabled(course_key):
|
||||
response = None
|
||||
if self.type == "comment":
|
||||
response = self.handle_update_comment(request_params, str(course_key))
|
||||
elif self.type == "thread":
|
||||
response = self.handle_update_thread(request_params, str(course_key))
|
||||
elif self.type == "user":
|
||||
response = self.handle_update_user(request_params, str(course_key))
|
||||
if response is None:
|
||||
raise CommentClientRequestError("Forum v2 API call is missing")
|
||||
else:
|
||||
response = self.perform_http_put_request(request_params)
|
||||
return response
|
||||
|
||||
def handle_update_user(self, request_params, course_id):
|
||||
try:
|
||||
username = request_params["username"]
|
||||
external_id = str(request_params["external_id"])
|
||||
except KeyError as e:
|
||||
raise e
|
||||
response = forum_api.update_user(
|
||||
external_id,
|
||||
username=username,
|
||||
course_id=course_id,
|
||||
)
|
||||
return response
|
||||
|
||||
def handle_update_comment(self, request_params, course_id):
|
||||
request_data = {
|
||||
"comment_id": self.attributes["id"],
|
||||
"body": request_params.get("body"),
|
||||
"course_id": request_params.get("course_id"),
|
||||
"user_id": request_params.get("user_id"),
|
||||
"anonymous": request_params.get("anonymous"),
|
||||
"anonymous_to_peers": request_params.get("anonymous_to_peers"),
|
||||
"endorsed": request_params.get("endorsed"),
|
||||
"closed": request_params.get("closed"),
|
||||
"editing_user_id": request_params.get("editing_user_id"),
|
||||
"edit_reason_code": request_params.get("edit_reason_code"),
|
||||
"endorsement_user_id": request_params.get("endorsement_user_id"),
|
||||
"course_key": course_id
|
||||
}
|
||||
request_data = {k: v for k, v in request_data.items() if v is not None}
|
||||
response = forum_api.update_comment(**request_data)
|
||||
return response
|
||||
|
||||
def handle_update_thread(self, request_params, course_id):
|
||||
request_data = {
|
||||
"thread_id": self.attributes["id"],
|
||||
"title": request_params.get("title"),
|
||||
"body": request_params.get("body"),
|
||||
"course_id": request_params.get("course_id"),
|
||||
"anonymous": request_params.get("anonymous"),
|
||||
"anonymous_to_peers": request_params.get("anonymous_to_peers"),
|
||||
"closed": request_params.get("closed"),
|
||||
"commentable_id": request_params.get("commentable_id"),
|
||||
"user_id": request_params.get("user_id"),
|
||||
"editing_user_id": request_params.get("editing_user_id"),
|
||||
"pinned": request_params.get("pinned"),
|
||||
"thread_type": request_params.get("thread_type"),
|
||||
"edit_reason_code": request_params.get("edit_reason_code"),
|
||||
"close_reason_code": request_params.get("close_reason_code"),
|
||||
"closing_user_id": request_params.get("closing_user_id"),
|
||||
"endorsed": request_params.get("endorsed"),
|
||||
"course_key": course_id
|
||||
}
|
||||
request_data = {k: v for k, v in request_data.items() if v is not None}
|
||||
response = forum_api.update_thread(**request_data)
|
||||
return response
|
||||
|
||||
def perform_http_put_request(self, request_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",
|
||||
)
|
||||
return response
|
||||
|
||||
def perform_http_post_request(self):
|
||||
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",
|
||||
)
|
||||
return response
|
||||
|
||||
def handle_create(self, params=None):
|
||||
course_id = self.attributes.get("course_id") or params.get("course_id")
|
||||
course_key = get_course_key(course_id)
|
||||
if is_forum_v2_enabled(course_key):
|
||||
response = None
|
||||
if self.type == "comment":
|
||||
response = self.handle_create_comment(str(course_key))
|
||||
elif self.type == "thread":
|
||||
response = self.handle_create_thread(str(course_key))
|
||||
if response is None:
|
||||
raise CommentClientRequestError("Forum v2 API call is missing")
|
||||
else:
|
||||
response = self.perform_http_post_request()
|
||||
return response
|
||||
|
||||
def handle_create_comment(self, course_id):
|
||||
request_data = self.initializable_attributes()
|
||||
body = request_data["body"]
|
||||
user_id = request_data["user_id"]
|
||||
course_id = course_id or str(request_data["course_id"])
|
||||
if parent_id := self.attributes.get("parent_id"):
|
||||
response = forum_api.create_child_comment(
|
||||
parent_id,
|
||||
body,
|
||||
user_id,
|
||||
course_id,
|
||||
request_data.get("anonymous", False),
|
||||
request_data.get("anonymous_to_peers", False),
|
||||
)
|
||||
else:
|
||||
response = forum_api.create_parent_comment(
|
||||
self.attributes["thread_id"],
|
||||
body,
|
||||
user_id,
|
||||
course_id,
|
||||
request_data.get("anonymous", False),
|
||||
request_data.get("anonymous_to_peers", False),
|
||||
)
|
||||
return response
|
||||
|
||||
def handle_create_thread(self, course_id):
|
||||
request_data = self.initializable_attributes()
|
||||
response = forum_api.create_thread(
|
||||
title=request_data["title"],
|
||||
body=request_data["body"],
|
||||
course_id=course_id or str(request_data["course_id"]),
|
||||
user_id=str(request_data["user_id"]),
|
||||
anonymous=request_data.get("anonymous", False),
|
||||
anonymous_to_peers=request_data.get("anonymous_to_peers", False),
|
||||
commentable_id=request_data.get("commentable_id", "course"),
|
||||
thread_type=request_data.get("thread_type", "discussion"),
|
||||
group_id=request_data.get("group_id", None),
|
||||
context=request_data.get("context", None),
|
||||
)
|
||||
return response
|
||||
|
||||
@@ -4,6 +4,8 @@ Subscription model is used to get users who are subscribed to the main thread/po
|
||||
import logging
|
||||
|
||||
from . import models, settings, utils
|
||||
from forum import api as forum_api
|
||||
from openedx.core.djangoapps.discussions.config.waffle import is_forum_v2_enabled
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -21,7 +23,7 @@ class Subscription(models.Model):
|
||||
base_url = f"{settings.PREFIX}/threads"
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, thread_id, query_params):
|
||||
def fetch(cls, thread_id, course_id, query_params):
|
||||
"""
|
||||
Fetches the subscriptions for a given thread_id
|
||||
"""
|
||||
@@ -33,14 +35,23 @@ class Subscription(models.Model):
|
||||
params.update(
|
||||
utils.strip_blank(utils.strip_none(query_params))
|
||||
)
|
||||
response = utils.perform_request(
|
||||
'get',
|
||||
cls.url(action='get', params=params) + "/subscriptions",
|
||||
params,
|
||||
metric_tags=[],
|
||||
metric_action='subscription.get',
|
||||
paged_results=True
|
||||
)
|
||||
course_key = utils.get_course_key(course_id)
|
||||
if is_forum_v2_enabled(course_key):
|
||||
response = forum_api.get_thread_subscriptions(
|
||||
thread_id=thread_id,
|
||||
page=params["page"],
|
||||
per_page=params["per_page"],
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
response = utils.perform_request(
|
||||
'get',
|
||||
cls.url(action='get', params=params) + "/subscriptions",
|
||||
params,
|
||||
metric_tags=[],
|
||||
metric_action='subscription.get',
|
||||
paged_results=True
|
||||
)
|
||||
return utils.SubscriptionsPaginatedResult(
|
||||
collection=response.get('collection', []),
|
||||
page=response.get('page', 1),
|
||||
|
||||
@@ -6,6 +6,8 @@ import logging
|
||||
from eventtracking import tracker
|
||||
|
||||
from . import models, settings, utils
|
||||
from forum import api as forum_api
|
||||
from openedx.core.djangoapps.discussions.config.waffle import is_forum_v2_enabled
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -59,14 +61,35 @@ class Thread(models.Model):
|
||||
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=['course_id:{}'.format(query_params['course_id'])],
|
||||
metric_action='thread.search',
|
||||
paged_results=True
|
||||
)
|
||||
|
||||
if is_forum_v2_enabled(utils.get_course_key(query_params['course_id'])):
|
||||
if query_params.get('text'):
|
||||
search_params = utils.strip_none(params)
|
||||
if user_id := search_params.get('user_id'):
|
||||
search_params['user_id'] = str(user_id)
|
||||
if group_ids := search_params.get('group_ids'):
|
||||
search_params['group_ids'] = [int(group_id) for group_id in group_ids.split(',')]
|
||||
elif group_id := search_params.get('group_id'):
|
||||
search_params['group_ids'] = [int(group_id)]
|
||||
search_params.pop('group_id', None)
|
||||
if commentable_ids := search_params.get('commentable_ids'):
|
||||
search_params['commentable_ids'] = commentable_ids.split(',')
|
||||
elif commentable_id := search_params.get('commentable_id'):
|
||||
search_params['commentable_ids'] = [commentable_id]
|
||||
search_params.pop('commentable_id', None)
|
||||
response = forum_api.search_threads(**search_params)
|
||||
else:
|
||||
response = forum_api.get_user_threads(**params)
|
||||
else:
|
||||
response = utils.perform_request(
|
||||
'get',
|
||||
url,
|
||||
params,
|
||||
metric_tags=['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']
|
||||
@@ -148,14 +171,26 @@ class Thread(models.Model):
|
||||
'merge_question_type_responses': kwargs.get('merge_question_type_responses', False)
|
||||
}
|
||||
request_params = utils.strip_none(request_params)
|
||||
|
||||
response = utils.perform_request(
|
||||
'get',
|
||||
url,
|
||||
request_params,
|
||||
metric_action='model.retrieve',
|
||||
metric_tags=self._metric_tags
|
||||
)
|
||||
course_id = kwargs.get("course_id")
|
||||
if not course_id:
|
||||
course_id = forum_api.get_course_id_by_thread(self.id)
|
||||
course_key = utils.get_course_key(course_id)
|
||||
if is_forum_v2_enabled(course_key):
|
||||
if user_id := request_params.get('user_id'):
|
||||
request_params['user_id'] = str(user_id)
|
||||
response = forum_api.get_thread(
|
||||
thread_id=self.id,
|
||||
params=request_params,
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
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):
|
||||
@@ -163,14 +198,18 @@ class Thread(models.Model):
|
||||
url = _url_for_flag_abuse_thread(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
|
||||
)
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
response = forum_api.update_thread_flag(voteable.id, "flag", user.id, str(course_key))
|
||||
else:
|
||||
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):
|
||||
@@ -178,42 +217,68 @@ class Thread(models.Model):
|
||||
url = _url_for_unflag_abuse_thread(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
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
response = forum_api.update_thread_flag(
|
||||
thread_id=voteable.id,
|
||||
action="unflag",
|
||||
user_id=user.id,
|
||||
update_all=bool(removeAll),
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
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'
|
||||
)
|
||||
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'
|
||||
)
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
response = forum_api.pin_thread(
|
||||
user_id=user.id,
|
||||
thread_id=thread_id,
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
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'
|
||||
)
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
response = forum_api.unpin_thread(
|
||||
user_id=user.id,
|
||||
thread_id=thread_id,
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# pylint: disable=missing-docstring,protected-access
|
||||
""" User model wrapper for comment service"""
|
||||
|
||||
|
||||
from . import models, settings, utils
|
||||
from forum import api as forum_api
|
||||
from forum.utils import ForumV2RequestError, str_to_bool
|
||||
from openedx.core.djangoapps.discussions.config.waffle import is_forum_v2_enabled
|
||||
|
||||
|
||||
class User(models.Model):
|
||||
@@ -34,34 +36,55 @@ class User(models.Model):
|
||||
"""
|
||||
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 + [f'target.type:{source.type}'],
|
||||
)
|
||||
course_id = self.attributes.get("course_id")
|
||||
course_key = utils.get_course_key(course_id)
|
||||
if is_forum_v2_enabled(course_key):
|
||||
forum_api.mark_thread_as_read(self.id, source.id, course_id=str(course_id))
|
||||
else:
|
||||
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 + [f'target.type:{source.type}'],
|
||||
)
|
||||
|
||||
def follow(self, source):
|
||||
params = {'source_type': source.type, 'source_id': source.id}
|
||||
utils.perform_request(
|
||||
'post',
|
||||
_url_for_subscription(self.id),
|
||||
params,
|
||||
metric_action='user.follow',
|
||||
metric_tags=self._metric_tags + [f'target.type:{source.type}'],
|
||||
)
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
forum_api.create_subscription(
|
||||
user_id=self.id,
|
||||
source_id=source.id,
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
params = {'source_type': source.type, 'source_id': source.id}
|
||||
utils.perform_request(
|
||||
'post',
|
||||
_url_for_subscription(self.id),
|
||||
params,
|
||||
metric_action='user.follow',
|
||||
metric_tags=self._metric_tags + [f'target.type:{source.type}'],
|
||||
)
|
||||
|
||||
def unfollow(self, source):
|
||||
params = {'source_type': source.type, 'source_id': source.id}
|
||||
utils.perform_request(
|
||||
'delete',
|
||||
_url_for_subscription(self.id),
|
||||
params,
|
||||
metric_action='user.unfollow',
|
||||
metric_tags=self._metric_tags + [f'target.type:{source.type}'],
|
||||
)
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
forum_api.delete_subscription(
|
||||
user_id=self.id,
|
||||
source_id=source.id,
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
params = {'source_type': source.type, 'source_id': source.id}
|
||||
utils.perform_request(
|
||||
'delete',
|
||||
_url_for_subscription(self.id),
|
||||
params,
|
||||
metric_action='user.unfollow',
|
||||
metric_tags=self._metric_tags + [f'target.type:{source.type}'],
|
||||
)
|
||||
|
||||
def vote(self, voteable, value):
|
||||
if voteable.type == 'thread':
|
||||
@@ -70,14 +93,31 @@ class User(models.Model):
|
||||
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 + [f'target.type:{voteable.type}'],
|
||||
)
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
if voteable.type == 'thread':
|
||||
response = forum_api.update_thread_votes(
|
||||
thread_id=voteable.id,
|
||||
user_id=self.id,
|
||||
value=value,
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
response = forum_api.update_comment_votes(
|
||||
comment_id=voteable.id,
|
||||
user_id=self.id,
|
||||
value=value,
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
params = {'user_id': self.id, 'value': value}
|
||||
response = utils.perform_request(
|
||||
'put',
|
||||
url,
|
||||
params,
|
||||
metric_action='user.vote',
|
||||
metric_tags=self._metric_tags + [f'target.type:{voteable.type}'],
|
||||
)
|
||||
voteable._update_from_response(response)
|
||||
|
||||
def unvote(self, voteable):
|
||||
@@ -87,14 +127,29 @@ class User(models.Model):
|
||||
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 + [f'target.type:{voteable.type}'],
|
||||
)
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
if voteable.type == 'thread':
|
||||
response = forum_api.delete_thread_vote(
|
||||
thread_id=voteable.id,
|
||||
user_id=self.id,
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
response = forum_api.delete_comment_vote(
|
||||
comment_id=voteable.id,
|
||||
user_id=self.id,
|
||||
course_id=str(course_key)
|
||||
)
|
||||
else:
|
||||
params = {'user_id': self.id}
|
||||
response = utils.perform_request(
|
||||
'delete',
|
||||
url,
|
||||
params,
|
||||
metric_action='user.unvote',
|
||||
metric_tags=self._metric_tags + [f'target.type:{voteable.type}'],
|
||||
)
|
||||
voteable._update_from_response(response)
|
||||
|
||||
def active_threads(self, query_params=None):
|
||||
@@ -105,14 +160,28 @@ class User(models.Model):
|
||||
url = _url_for_user_active_threads(self.id)
|
||||
params = {'course_id': str(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,
|
||||
)
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
if user_id := params.get("user_id"):
|
||||
params["user_id"] = str(user_id)
|
||||
if page := params.get("page"):
|
||||
params["page"] = int(page)
|
||||
if per_page := params.get("per_page"):
|
||||
params["per_page"] = int(per_page)
|
||||
if count_flagged := params.get("count_flagged", False):
|
||||
params["count_flagged"] = str_to_bool(count_flagged)
|
||||
if not params.get("course_id"):
|
||||
params["course_id"] = str(course_key)
|
||||
response = forum_api.get_user_active_threads(**params)
|
||||
else:
|
||||
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=None):
|
||||
@@ -125,14 +194,28 @@ class User(models.Model):
|
||||
url = _url_for_user_subscribed_threads(self.id)
|
||||
params = {'course_id': str(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
|
||||
)
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
if user_id := params.get("user_id"):
|
||||
params["user_id"] = str(user_id)
|
||||
if page := params.get("page"):
|
||||
params["page"] = int(page)
|
||||
if per_page := params.get("per_page"):
|
||||
params["per_page"] = int(per_page)
|
||||
if count_flagged := params.get("count_flagged", False):
|
||||
params["count_flagged"] = str_to_bool(count_flagged)
|
||||
if not params.get("course_id"):
|
||||
params["course_id"] = str(course_key)
|
||||
response = forum_api.get_user_threads(**params)
|
||||
else:
|
||||
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),
|
||||
@@ -144,23 +227,39 @@ class User(models.Model):
|
||||
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'] = str(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()
|
||||
|
||||
# course key -> id conversation
|
||||
course_id = retrieve_params.get('course_id')
|
||||
if course_id:
|
||||
course_id = str(course_id)
|
||||
retrieve_params['course_id'] = course_id
|
||||
course_key = utils.get_course_key(course_id)
|
||||
|
||||
if is_forum_v2_enabled(course_key):
|
||||
group_ids = [retrieve_params['group_id']] if 'group_id' in retrieve_params else []
|
||||
is_complete = retrieve_params['complete']
|
||||
try:
|
||||
response = forum_api.get_user(
|
||||
self.attributes["id"],
|
||||
group_ids=group_ids,
|
||||
course_id=course_id,
|
||||
complete=is_complete
|
||||
)
|
||||
except ForumV2RequestError as e:
|
||||
self.save({"course_id": course_id})
|
||||
response = forum_api.get_user(
|
||||
self.attributes["id"],
|
||||
group_ids=group_ids,
|
||||
course_id=course_id,
|
||||
complete=is_complete
|
||||
)
|
||||
else:
|
||||
try:
|
||||
response = utils.perform_request(
|
||||
'get',
|
||||
url,
|
||||
@@ -168,33 +267,52 @@ class User(models.Model):
|
||||
metric_action='model.retrieve',
|
||||
metric_tags=self._metric_tags,
|
||||
)
|
||||
else:
|
||||
raise
|
||||
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
|
||||
)
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
forum_api.retire_user(user_id=self.id, retired_username=retired_username, course_id=str(course_key))
|
||||
else:
|
||||
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}
|
||||
course_key = utils.get_course_key(self.attributes.get("course_id"))
|
||||
if is_forum_v2_enabled(course_key):
|
||||
forum_api.update_username(user_id=self.id, new_username=new_username, course_id=str(course_key))
|
||||
else:
|
||||
url = _url_for_username_replacement(self.id)
|
||||
params = {"new_username": new_username}
|
||||
|
||||
utils.perform_request(
|
||||
'post',
|
||||
url,
|
||||
params,
|
||||
raw=True,
|
||||
)
|
||||
utils.perform_request(
|
||||
'post',
|
||||
url,
|
||||
params,
|
||||
raw=True,
|
||||
)
|
||||
|
||||
|
||||
def _url_for_vote_comment(comment_id):
|
||||
|
||||
@@ -7,6 +7,7 @@ from uuid import uuid4
|
||||
|
||||
import requests
|
||||
from django.utils.translation import get_language
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from .settings import SERVICE_HOST as COMMENTS_SERVICE
|
||||
|
||||
@@ -167,3 +168,19 @@ def check_forum_heartbeat():
|
||||
return 'forum', False, res.get('check', 'Forum heartbeat failed')
|
||||
except Exception as fail:
|
||||
return 'forum', False, str(fail)
|
||||
|
||||
|
||||
def get_course_key(course_id: CourseKey | str | None) -> CourseKey | None:
|
||||
"""
|
||||
Returns a CourseKey if the provided course_id is a valid string representation of a CourseKey.
|
||||
If course_id is None or already a CourseKey object, it returns the course_id as is.
|
||||
Args:
|
||||
course_id (CourseKey | str | None): The course ID to be converted.
|
||||
Returns:
|
||||
CourseKey | None: The corresponding CourseKey object or None if the input is None.
|
||||
Raises:
|
||||
KeyError: If course_id is not a valid string representation of a CourseKey.
|
||||
"""
|
||||
if course_id and isinstance(course_id, str):
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
return course_id
|
||||
|
||||
@@ -57,7 +57,9 @@ backoff==1.10.0
|
||||
bcrypt==4.2.0
|
||||
# via paramiko
|
||||
beautifulsoup4==4.12.3
|
||||
# via pynliner
|
||||
# via
|
||||
# openedx-forum
|
||||
# pynliner
|
||||
billiard==4.2.1
|
||||
# via celery
|
||||
bleach[css]==6.1.0
|
||||
@@ -234,6 +236,7 @@ django==4.2.16
|
||||
# openedx-django-wiki
|
||||
# openedx-events
|
||||
# openedx-filters
|
||||
# openedx-forum
|
||||
# openedx-learning
|
||||
# ora2
|
||||
# social-auth-app-django
|
||||
@@ -383,6 +386,7 @@ djangorestframework==3.14.0
|
||||
# edx-organizations
|
||||
# edx-proctoring
|
||||
# edx-submissions
|
||||
# openedx-forum
|
||||
# openedx-learning
|
||||
# ora2
|
||||
# super-csv
|
||||
@@ -516,7 +520,9 @@ edx-rest-api-client==6.0.0
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
edx-search==4.1.1
|
||||
# via -r requirements/edx/kernel.in
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# openedx-forum
|
||||
edx-sga==0.25.0
|
||||
# via -r requirements/edx/bundled.in
|
||||
edx-submissions==3.8.2
|
||||
@@ -549,6 +555,7 @@ elasticsearch==7.9.1
|
||||
# via
|
||||
# -c requirements/edx/../common_constraints.txt
|
||||
# edx-search
|
||||
# openedx-forum
|
||||
enmerkar==0.7.1
|
||||
# via enmerkar-underscore
|
||||
enmerkar-underscore==2.3.1
|
||||
@@ -774,7 +781,9 @@ multidict==6.1.0
|
||||
# aiohttp
|
||||
# yarl
|
||||
mysqlclient==2.2.5
|
||||
# via -r requirements/edx/kernel.in
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# openedx-forum
|
||||
newrelic==10.2.0
|
||||
# via edx-django-utils
|
||||
nh3==0.2.18
|
||||
@@ -804,7 +813,9 @@ openai==0.28.1
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# edx-enterprise
|
||||
openedx-atlas==0.6.2
|
||||
# via -r requirements/edx/kernel.in
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# openedx-forum
|
||||
openedx-calc==3.1.2
|
||||
# via -r requirements/edx/kernel.in
|
||||
openedx-django-pyfs==3.7.0
|
||||
@@ -830,6 +841,8 @@ openedx-filters==1.11.0
|
||||
# -r requirements/edx/kernel.in
|
||||
# lti-consumer-xblock
|
||||
# ora2
|
||||
openedx-forum==0.1.2
|
||||
# via -r requirements/edx/kernel.in
|
||||
openedx-learning==0.18.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
@@ -963,6 +976,7 @@ pymongo==4.4.0
|
||||
# edx-opaque-keys
|
||||
# event-tracking
|
||||
# mongoengine
|
||||
# openedx-forum
|
||||
# openedx-mongodbproxy
|
||||
pynacl==1.5.0
|
||||
# via
|
||||
@@ -1072,6 +1086,7 @@ requests==2.32.3
|
||||
# mailsnake
|
||||
# meilisearch
|
||||
# openai
|
||||
# openedx-forum
|
||||
# optimizely-sdk
|
||||
# pyjwkest
|
||||
# pylti1p3
|
||||
|
||||
@@ -122,6 +122,7 @@ beautifulsoup4==4.12.3
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# openedx-forum
|
||||
# pydata-sphinx-theme
|
||||
# pynliner
|
||||
billiard==4.2.1
|
||||
@@ -406,6 +407,7 @@ django==4.2.16
|
||||
# openedx-django-wiki
|
||||
# openedx-events
|
||||
# openedx-filters
|
||||
# openedx-forum
|
||||
# openedx-learning
|
||||
# ora2
|
||||
# social-auth-app-django
|
||||
@@ -619,6 +621,7 @@ djangorestframework==3.14.0
|
||||
# edx-organizations
|
||||
# edx-proctoring
|
||||
# edx-submissions
|
||||
# openedx-forum
|
||||
# openedx-learning
|
||||
# ora2
|
||||
# super-csv
|
||||
@@ -815,6 +818,7 @@ edx-search==4.1.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# openedx-forum
|
||||
edx-sga==0.25.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -861,6 +865,7 @@ elasticsearch==7.9.1
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# edx-search
|
||||
# openedx-forum
|
||||
enmerkar==0.7.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -1304,6 +1309,7 @@ mysqlclient==2.2.5
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# openedx-forum
|
||||
newrelic==10.2.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -1354,6 +1360,7 @@ openedx-atlas==0.6.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# openedx-forum
|
||||
openedx-calc==3.1.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -1389,6 +1396,10 @@ openedx-filters==1.11.0
|
||||
# -r requirements/edx/testing.txt
|
||||
# lti-consumer-xblock
|
||||
# ora2
|
||||
openedx-forum==0.1.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
openedx-learning==0.18.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
@@ -1657,6 +1668,7 @@ pymongo==4.4.0
|
||||
# edx-opaque-keys
|
||||
# event-tracking
|
||||
# mongoengine
|
||||
# openedx-forum
|
||||
# openedx-mongodbproxy
|
||||
pynacl==1.5.0
|
||||
# via
|
||||
@@ -1853,6 +1865,7 @@ requests==2.32.3
|
||||
# mailsnake
|
||||
# meilisearch
|
||||
# openai
|
||||
# openedx-forum
|
||||
# optimizely-sdk
|
||||
# pact-python
|
||||
# pyjwkest
|
||||
|
||||
@@ -89,6 +89,7 @@ bcrypt==4.2.0
|
||||
beautifulsoup4==4.12.3
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-forum
|
||||
# pydata-sphinx-theme
|
||||
# pynliner
|
||||
billiard==4.2.1
|
||||
@@ -292,6 +293,7 @@ django==4.2.16
|
||||
# openedx-django-wiki
|
||||
# openedx-events
|
||||
# openedx-filters
|
||||
# openedx-forum
|
||||
# openedx-learning
|
||||
# ora2
|
||||
# social-auth-app-django
|
||||
@@ -457,6 +459,7 @@ djangorestframework==3.14.0
|
||||
# edx-organizations
|
||||
# edx-proctoring
|
||||
# edx-submissions
|
||||
# openedx-forum
|
||||
# openedx-learning
|
||||
# ora2
|
||||
# super-csv
|
||||
@@ -601,7 +604,9 @@ edx-rest-api-client==6.0.0
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
edx-search==4.1.1
|
||||
# via -r requirements/edx/base.txt
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-forum
|
||||
edx-sga==0.25.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-submissions==3.8.2
|
||||
@@ -637,6 +642,7 @@ elasticsearch==7.9.1
|
||||
# -c requirements/edx/../common_constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-search
|
||||
# openedx-forum
|
||||
enmerkar==0.7.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -937,7 +943,9 @@ multidict==6.1.0
|
||||
# aiohttp
|
||||
# yarl
|
||||
mysqlclient==2.2.5
|
||||
# via -r requirements/edx/base.txt
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-forum
|
||||
newrelic==10.2.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -973,7 +981,9 @@ openai==0.28.1
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
openedx-atlas==0.6.2
|
||||
# via -r requirements/edx/base.txt
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-forum
|
||||
openedx-calc==3.1.2
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-django-pyfs==3.7.0
|
||||
@@ -1000,6 +1010,8 @@ openedx-filters==1.11.0
|
||||
# -r requirements/edx/base.txt
|
||||
# lti-consumer-xblock
|
||||
# ora2
|
||||
openedx-forum==0.1.2
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-learning==0.18.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
@@ -1169,6 +1181,7 @@ pymongo==4.4.0
|
||||
# edx-opaque-keys
|
||||
# event-tracking
|
||||
# mongoengine
|
||||
# openedx-forum
|
||||
# openedx-mongodbproxy
|
||||
pynacl==1.5.0
|
||||
# via
|
||||
@@ -1294,6 +1307,7 @@ requests==2.32.3
|
||||
# mailsnake
|
||||
# meilisearch
|
||||
# openai
|
||||
# openedx-forum
|
||||
# optimizely-sdk
|
||||
# pyjwkest
|
||||
# pylti1p3
|
||||
|
||||
@@ -119,6 +119,7 @@ openedx-calc # Library supporting mathematical calculatio
|
||||
openedx-django-require
|
||||
openedx-events # Open edX Events from Hooks Extension Framework (OEP-50)
|
||||
openedx-filters # Open edX Filters from Hooks Extension Framework (OEP-50)
|
||||
openedx-forum # Open edX forum v2 application
|
||||
openedx-learning # Open edX Learning core (experimental)
|
||||
openedx-mongodbproxy
|
||||
openedx-django-wiki
|
||||
|
||||
@@ -87,6 +87,7 @@ beautifulsoup4==4.12.3
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# -r requirements/edx/testing.in
|
||||
# openedx-forum
|
||||
# pynliner
|
||||
billiard==4.2.1
|
||||
# via
|
||||
@@ -318,6 +319,7 @@ django==4.2.16
|
||||
# openedx-django-wiki
|
||||
# openedx-events
|
||||
# openedx-filters
|
||||
# openedx-forum
|
||||
# openedx-learning
|
||||
# ora2
|
||||
# social-auth-app-django
|
||||
@@ -483,6 +485,7 @@ djangorestframework==3.14.0
|
||||
# edx-organizations
|
||||
# edx-proctoring
|
||||
# edx-submissions
|
||||
# openedx-forum
|
||||
# openedx-learning
|
||||
# ora2
|
||||
# super-csv
|
||||
@@ -624,7 +627,9 @@ edx-rest-api-client==6.0.0
|
||||
# edx-enterprise
|
||||
# edx-proctoring
|
||||
edx-search==4.1.1
|
||||
# via -r requirements/edx/base.txt
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-forum
|
||||
edx-sga==0.25.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-submissions==3.8.2
|
||||
@@ -660,6 +665,7 @@ elasticsearch==7.9.1
|
||||
# -c requirements/edx/../common_constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-search
|
||||
# openedx-forum
|
||||
enmerkar==0.7.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -982,7 +988,9 @@ multidict==6.1.0
|
||||
# aiohttp
|
||||
# yarl
|
||||
mysqlclient==2.2.5
|
||||
# via -r requirements/edx/base.txt
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-forum
|
||||
newrelic==10.2.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -1018,7 +1026,9 @@ openai==0.28.1
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
openedx-atlas==0.6.2
|
||||
# via -r requirements/edx/base.txt
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-forum
|
||||
openedx-calc==3.1.2
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-django-pyfs==3.7.0
|
||||
@@ -1045,6 +1055,8 @@ openedx-filters==1.11.0
|
||||
# -r requirements/edx/base.txt
|
||||
# lti-consumer-xblock
|
||||
# ora2
|
||||
openedx-forum==0.1.2
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-learning==0.18.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
@@ -1249,6 +1261,7 @@ pymongo==4.4.0
|
||||
# edx-opaque-keys
|
||||
# event-tracking
|
||||
# mongoengine
|
||||
# openedx-forum
|
||||
# openedx-mongodbproxy
|
||||
pynacl==1.5.0
|
||||
# via
|
||||
@@ -1405,6 +1418,7 @@ requests==2.32.3
|
||||
# mailsnake
|
||||
# meilisearch
|
||||
# openai
|
||||
# openedx-forum
|
||||
# optimizely-sdk
|
||||
# pact-python
|
||||
# pyjwkest
|
||||
|
||||
Reference in New Issue
Block a user