diff --git a/lms/djangoapps/django_comment_client/base/tests.py b/lms/djangoapps/django_comment_client/base/tests.py index 3e0dfd8129..9f14f2daa4 100644 --- a/lms/djangoapps/django_comment_client/base/tests.py +++ b/lms/djangoapps/django_comment_client/base/tests.py @@ -1641,6 +1641,40 @@ class ForumEventTestCase(ModuleStoreTestCase, MockRequestSetupMixin): self.assertEqual(name, event_name) self.assertEqual(event['team_id'], team.team_id) + @ddt.data( + ('vote_for_thread', 'thread_id', 'thread'), + ('undo_vote_for_thread', 'thread_id', 'thread'), + ('vote_for_comment', 'comment_id', 'response'), + ('undo_vote_for_comment', 'comment_id', 'response'), + ) + @ddt.unpack + @patch('eventtracking.tracker.emit') + @patch('lms.lib.comment_client.utils.requests.request') + def test_thread_voted_event(self, view_name, obj_id_name, obj_type, mock_request, mock_emit): + undo = view_name.startswith('undo') + + self._set_mock_request_data(mock_request, { + 'closed': False, + 'commentable_id': 'test_commentable_id', + 'username': 'gumprecht', + }) + request = RequestFactory().post('dummy_url', {}) + request.user = self.student + request.view_name = view_name + view_function = getattr(views, view_name) + kwargs = dict(course_id=unicode(self.course.id)) + kwargs[obj_id_name] = obj_id_name + if not undo: + kwargs.update(value='up') + view_function(request, **kwargs) + + self.assertTrue(mock_emit.called) + event_name, event = mock_emit.call_args[0] + self.assertEqual(event_name, 'edx.forum.{}.voted'.format(obj_type)) + self.assertEqual(event['target_username'], 'gumprecht') + self.assertEqual(event['undo_vote'], undo) + self.assertEqual(event['vote_value'], 'up') + class UsersEndpointTestCase(ModuleStoreTestCase, MockRequestSetupMixin): diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index b84fffc460..e480ff62a3 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -82,6 +82,9 @@ def track_forum_event(request, event_name, course, obj, data, id_map=None): def track_created_event(request, event_name, course, obj, data): + """ + Send analytics event for a newly created thread, response or comment. + """ if len(obj.body) > TRACKING_MAX_FORUM_BODY: data['truncated'] = True else: @@ -91,6 +94,9 @@ def track_created_event(request, event_name, course, obj, data): def track_thread_created_event(request, course, thread, followed): + """ + Send analytics event for a newly created thread. + """ event_name = _EVENT_NAME_TEMPLATE.format(obj_type='thread', action_name='created') event_data = { 'commentable_id': thread.commentable_id, @@ -109,6 +115,9 @@ def track_thread_created_event(request, course, thread, followed): def track_comment_created_event(request, course, comment, commentable_id, followed): + """ + Send analytics event for a newly created response or comment. + """ obj_type = 'comment' if comment.get("parent_id") else 'response' event_name = _EVENT_NAME_TEMPLATE.format(obj_type=obj_type, action_name='created') event_data = { @@ -123,6 +132,9 @@ def track_comment_created_event(request, course, comment, commentable_id, follow def track_voted_event(request, course, obj, vote_value, undo_vote=False): + """ + Send analytics event for a vote on a thread or response. + """ if isinstance(obj, cc.Thread): obj_type = 'thread' else: @@ -137,10 +149,19 @@ def track_voted_event(request, course, obj, vote_value, undo_vote=False): track_forum_event(request, event_name, course, obj, event_data) -def permitted(fn): - @functools.wraps(fn) +def permitted(func): + """ + View decorator to verify the user is authorized to access this endpoint. + """ + @functools.wraps(func) def wrapper(request, *args, **kwargs): + """ + Wrapper for the view that only calls the view if the user is authorized. + """ def fetch_content(): + """ + Extract the forum object from the keyword arguments to the view. + """ if "thread_id" in kwargs: content = cc.Thread.find(kwargs["thread_id"]).to_dict() elif "comment_id" in kwargs: @@ -152,13 +173,16 @@ def permitted(fn): return content course_key = SlashSeparatedCourseKey.from_deprecated_string(kwargs['course_id']) if check_permissions_by_view(request.user, course_key, fetch_content(), request.view_name): - return fn(request, *args, **kwargs) + return func(request, *args, **kwargs) else: return JsonError("unauthorized", status=401) return wrapper def ajax_content_response(request, course_key, content): + """ + Standard AJAX response returning the content hierarchy of the current thread. + """ user_info = cc.User.from_django_user(request.user).to_dict() annotated_content_info = get_annotated_content_info(course_key, content, request.user, user_info) return JsonResponse({ @@ -479,7 +503,7 @@ def _vote_or_unvote(request, course_id, obj, value='up', undo_vote=False): @permitted def vote_for_comment(request, course_id, comment_id, value): """ - given a course_id and comment_id, + Given a course_id and comment_id, vote for this response. AJAX only. """ comment = cc.Comment.find(comment_id) result = _vote_or_unvote(request, course_id, comment, value)