def run_should_not_send_email_test(self, thread, comment_dict): """ assert email is not sent """ self.mock_request.side_effect = make_mock_responder( subscribed_thread_ids=[self.discussion_id], comment_data=comment_dict, thread_data=thread, ) user = mock.Mock() comment = cc.Comment.find(id=comment_dict['id']).retrieve() comment_created.send(sender=None, user=user, post=comment) actual_result = _should_send_message({ 'thread_author_id': self.thread_author.id, 'course_id': self.course.id, 'comment_id': comment_dict['id'], 'thread_id': thread['id'], }) self.assertEqual(actual_result, False) self.assertFalse(self.mock_ace_send.called)
def test_send_discussion_email_notification(self, user_subscribed): if user_subscribed: non_matching_id = 'not-a-match' # with per_page left with a default value of 1, this ensures # that we test a multiple page result when calling # comment_client.User.subscribed_threads() subscribed_thread_ids = [non_matching_id, self.discussion_id] else: subscribed_thread_ids = [] self.mock_request.side_effect = make_mock_responder( subscribed_thread_ids=subscribed_thread_ids, comment_data=self.comment, thread_data=self.thread, ) user = mock.Mock() comment = cc.Comment.find(id=self.comment['id']).retrieve() site = Site.objects.get_current() site_config = SiteConfigurationFactory.create(site=site) site_config.values[ENABLE_FORUM_NOTIFICATIONS_FOR_SITE_KEY] = True site_config.save() with mock.patch('lms.djangoapps.discussion.signals.handlers.get_current_site', return_value=site): comment_created.send(sender=None, user=user, post=comment) if user_subscribed: expected_message_context = get_base_template_context(site) expected_message_context.update({ 'comment_author_id': self.comment_author.id, 'comment_body': self.comment['body'], 'comment_created_at': ONE_HOUR_AGO, 'comment_id': self.comment['id'], 'comment_username': self.comment_author.username, 'course_id': self.course.id, 'thread_author_id': self.thread_author.id, 'thread_created_at': TWO_HOURS_AGO, 'thread_id': self.discussion_id, 'thread_title': 'thread-title', 'thread_username': self.thread_author.username, 'thread_commentable_id': self.thread['commentable_id'], 'post_link': self.mock_permalink.return_value, 'site': site, 'site_id': site.id }) expected_recipient = Recipient(self.thread_author.username, self.thread_author.email) actual_message = self.mock_ace_send.call_args_list[0][0][0] self.assertEqual(expected_message_context, actual_message.context) self.assertEqual(expected_recipient, actual_message.recipient) self.assertEqual(self.course.language, actual_message.language) self._assert_rendered_email(actual_message) else: self.assertFalse(self.mock_ace_send.called)
def _create_comment(request, course_key, thread_id=None, parent_id=None): """ given a course_key, thread_id, and parent_id, create a comment, called from create_comment to do the actual creation """ assert isinstance(course_key, CourseKey) post = request.POST user = request.user if 'body' not in post or not post['body'].strip(): return JsonError(_("Body can't be empty")) course = get_course_with_access(user, 'load', course_key) if course.allow_anonymous: anonymous = post.get('anonymous', 'false').lower() == 'true' else: anonymous = False if course.allow_anonymous_to_peers: anonymous_to_peers = post.get('anonymous_to_peers', 'false').lower() == 'true' else: anonymous_to_peers = False comment = cc.Comment(anonymous=anonymous, anonymous_to_peers=anonymous_to_peers, user_id=user.id, course_id=course_key.to_deprecated_string(), thread_id=thread_id, parent_id=parent_id, body=post["body"]) comment.save() comment_created.send(sender=None, user=user, post=comment) followed = post.get('auto_subscribe', 'false').lower() == 'true' if followed: cc_user = cc.User.from_django_user(request.user) cc_user.follow(comment.thread) event_name = get_comment_created_event_name(comment) event_data = get_comment_created_event_data(comment, comment.thread.commentable_id, followed) track_forum_event(request, event_name, course, comment, event_data) if request.is_ajax(): return ajax_content_response(request, course_key, comment.to_dict()) else: return JsonResponse(prepare_content(comment.to_dict(), course.id))
def _create_comment(request, course_key, thread_id=None, parent_id=None): """ given a course_key, thread_id, and parent_id, create a comment, called from create_comment to do the actual creation """ assert isinstance(course_key, CourseKey) post = request.POST user = request.user if 'body' not in post or not post['body'].strip(): return JsonError(_("Body can't be empty")) course = get_course_with_access(user, 'load', course_key) if course.allow_anonymous: anonymous = post.get('anonymous', 'false').lower() == 'true' else: anonymous = False if course.allow_anonymous_to_peers: anonymous_to_peers = post.get('anonymous_to_peers', 'false').lower() == 'true' else: anonymous_to_peers = False comment = cc.Comment( anonymous=anonymous, anonymous_to_peers=anonymous_to_peers, user_id=user.id, course_id=course_key.to_deprecated_string(), thread_id=thread_id, parent_id=parent_id, body=post["body"] ) comment.save() comment_created.send(sender=None, user=user, post=comment) followed = post.get('auto_subscribe', 'false').lower() == 'true' if followed: cc_user = cc.User.from_django_user(request.user) cc_user.follow(comment.thread) event_name = get_comment_created_event_name(comment) event_data = get_comment_created_event_data(comment, comment.thread.commentable_id, followed) track_forum_event(request, event_name, course, comment, event_data) if request.is_ajax(): return ajax_content_response(request, course_key, comment.to_dict()) else: return JsonResponse(prepare_content(comment.to_dict(), course.id))
def create_comment(request, comment_data): """ Create a comment. Arguments: request: The django request object used for build_absolute_uri and determining the requesting user. comment_data: The data for the created comment. Returns: The created comment; see discussion_api.views.CommentViewSet for more detail. """ thread_id = comment_data.get("thread_id") if not thread_id: raise ValidationError({"thread_id": ["This field is required."]}) try: cc_thread, context = _get_thread_and_context(request, thread_id) except Http404: raise ValidationError({"thread_id": ["Invalid value."]}) # if a thread is closed; no new comments could be made to it if cc_thread['closed']: raise PermissionDenied _check_initializable_comment_fields(comment_data, context) serializer = CommentSerializer(data=comment_data, context=context) actions_form = CommentActionsForm(comment_data) if not (serializer.is_valid() and actions_form.is_valid()): raise ValidationError( dict(serializer.errors.items() + actions_form.errors.items())) serializer.save() cc_comment = serializer.instance comment_created.send(sender=None, user=request.user, post=cc_comment) api_comment = serializer.data _do_extra_actions(api_comment, cc_comment, comment_data.keys(), actions_form, context) track_comment_created_event(request, context["course"], cc_comment, cc_thread["commentable_id"], followed=False) return api_comment
def run_should_not_send_email_test(self, comment_dict): self.mock_request.side_effect = make_mock_responder( subscribed_thread_ids=[self.discussion_id], comment_data=comment_dict, thread_data=self.thread, ) user = mock.Mock() comment = cc.Comment.find(id=comment_dict['id']).retrieve() comment_created.send(sender=None, user=user, post=comment) actual_result = _should_send_message({ 'thread_author_id': self.thread_author.id, 'course_id': self.course.id, 'comment_id': comment_dict['id'], 'thread_id': self.thread['id'], }) self.assertEqual(actual_result, False) self.assertFalse(self.mock_ace_send.called)
def create_comment(request, comment_data): """ Create a comment. Arguments: request: The django request object used for build_absolute_uri and determining the requesting user. comment_data: The data for the created comment. Returns: The created comment; see discussion_api.views.CommentViewSet for more detail. """ thread_id = comment_data.get("thread_id") if not thread_id: raise ValidationError({"thread_id": ["This field is required."]}) try: cc_thread, context = _get_thread_and_context(request, thread_id) except Http404: raise ValidationError({"thread_id": ["Invalid value."]}) _check_initializable_comment_fields(comment_data, context) serializer = CommentSerializer(data=comment_data, context=context) actions_form = CommentActionsForm(comment_data) if not (serializer.is_valid() and actions_form.is_valid()): raise ValidationError(dict(serializer.errors.items() + actions_form.errors.items())) serializer.save() cc_comment = serializer.object comment_created.send(sender=None, user=request.user, post=cc_comment) api_comment = serializer.data _do_extra_actions(api_comment, cc_comment, comment_data.keys(), actions_form, context) track_forum_event( request, get_comment_created_event_name(cc_comment), context["course"], cc_comment, get_comment_created_event_data(cc_comment, cc_thread["commentable_id"], followed=False) ) return api_comment
def _create_comment(request, course_key, thread_id=None, parent_id=None): """ given a course_key, thread_id, and parent_id, create a comment, called from create_comment to do the actual creation """ assert isinstance(course_key, CourseKey) post = request.POST user = request.user if 'body' not in post or not post['body'].strip(): return JsonError(_("Body can't be empty")) course = get_course_with_access(user, 'load', course_key) if course.allow_anonymous: anonymous = post.get('anonymous', 'false').lower() == 'true' else: anonymous = False if course.allow_anonymous_to_peers: anonymous_to_peers = post.get('anonymous_to_peers', 'false').lower() == 'true' else: anonymous_to_peers = False comment = cc.Comment(anonymous=anonymous, anonymous_to_peers=anonymous_to_peers, user_id=user.id, course_id=course_key.to_deprecated_string(), thread_id=thread_id, parent_id=parent_id, body=post["body"]) comment.save() comment_created.send(sender=None, user=user, post=comment) followed = post.get('auto_subscribe', 'false').lower() == 'true' if followed: cc_user = cc.User.from_django_user(request.user) cc_user.follow(comment.thread) track_comment_created_event(request, course, comment, comment.thread.commentable_id, followed) # # Send notification # # Feature Flag to check that notifications are enabled or not. if settings.FEATURES.get("ENABLE_NOTIFICATIONS", False): action_user_id = request.user.id is_comment = not thread_id and parent_id replying_to_id = None # keep track of who we are replying to if is_comment: # If creating a comment, then we don't have the original thread_id # so we have to get it from the parent comment = cc.Comment.find(parent_id) thread_id = comment.thread_id replying_to_id = comment.user_id thread = cc.Thread.find(thread_id) # IMPORTANT: we have to use getattr here as # otherwise the property will not get fetched # from cs_comment_service thread_user_id = int(getattr(thread, 'user_id', 0)) if not replying_to_id: # we must be creating a Reponse on a thread, # so the original poster is the author of the thread replying_to_id = thread_user_id # # IMPORTANT: We have to use getattr() here so that the # object is fully hydrated. This is a known limitation. # group_id = getattr(thread, 'group_id') if group_id: # We always send a notification to the whole cohort # when someone posts a comment, except the poster _send_discussion_notification( 'open-edx.lms.discussions.cohorted-comment-added', unicode(course_key), thread, request.user, excerpt=_get_excerpt(post["body"]), recipient_group_id=thread.get('group_id'), recipient_exclude_user_ids=[request.user.id], is_anonymous_user=anonymous or anonymous_to_peers) elif parent_id is None and action_user_id != replying_to_id: # we have to only send the notifications when # the user commenting the thread is not # the same user who created the thread # parent_id is None: publish notification only when creating the comment on # the thread not replying on the comment. When the user replied on the comment # the parent_id is not None at that time _send_discussion_notification( 'open-edx.lms.discussions.reply-to-thread', unicode(course_key), thread, request.user, excerpt=_get_excerpt(post["body"]), recipient_user_id=replying_to_id, is_anonymous_user=anonymous or anonymous_to_peers) if request.is_ajax(): return ajax_content_response(request, course_key, comment.to_dict()) else: return JsonResponse(prepare_content(comment.to_dict(), course.id))
def test_send_discussion_email_notification(self, user_subscribed): with mock_the_things() as mocked_items: mock_request, mock_ace_send, mock_permalink = mocked_items if user_subscribed: non_matching_id = 'not-a-match' # with per_page left with a default value of 1, this ensures # that we test a multiple page result when calling # comment_client.User.subscribed_threads() mock_request.side_effect = make_mock_responder([non_matching_id, self.discussion_id]) else: mock_request.side_effect = make_mock_responder([]) now = datetime.utcnow() one_hour_ago = now - timedelta(hours=1) thread = mock.Mock( id=self.discussion_id, course_id=self.course.id, created_at=one_hour_ago, title='thread-title', user_id=self.thread_author.id, username=self.thread_author.username, commentable_id='thread-commentable-id' ) comment = mock.Mock( id='comment-id', body='comment-body', created_at=now, thread=thread, user_id=self.comment_author.id, username=self.comment_author.username ) user = mock.Mock() with waffle().override(FORUM_RESPONSE_NOTIFICATIONS): comment_created.send(sender=None, user=user, post=comment) if user_subscribed: expected_message_context = get_base_template_context(Site.objects.get_current()) expected_message_context.update({ 'comment_author_id': self.comment_author.id, 'comment_body': 'comment-body', 'comment_created_at': now, 'comment_id': 'comment-id', 'comment_username': self.comment_author.username, 'course_id': self.course.id, 'thread_author_id': self.thread_author.id, 'thread_created_at': one_hour_ago, 'thread_id': self.discussion_id, 'thread_title': 'thread-title', 'thread_username': self.thread_author.username, 'thread_commentable_id': 'thread-commentable-id', 'post_link': urljoin(Site.objects.get_current().domain, mock_permalink.return_value), 'site': Site.objects.get_current(), 'site_id': Site.objects.get_current().id, }) ga_tracking_pixel_url = _generate_ga_pixel_url(expected_message_context) expected_message_context.update({'ga_tracking_pixel_url': ga_tracking_pixel_url}) expected_recipient = Recipient(self.thread_author.username, self.thread_author.email) actual_message = mock_ace_send.call_args_list[0][0][0] self.assertEqual(expected_message_context, actual_message.context) self.assertEqual(expected_recipient, actual_message.recipient) self.assertEqual(self.course.language, actual_message.language) else: self.assertFalse(mock_ace_send.called)
def _create_comment(request, course_key, thread_id=None, parent_id=None): """ given a course_key, thread_id, and parent_id, create a comment, called from create_comment to do the actual creation """ assert isinstance(course_key, CourseKey) post = request.POST user = request.user if 'body' not in post or not post['body'].strip(): return JsonError(_("Body can't be empty")) course = get_course_with_access(user, 'load', course_key) if course.allow_anonymous: anonymous = post.get('anonymous', 'false').lower() == 'true' else: anonymous = False if course.allow_anonymous_to_peers: anonymous_to_peers = post.get('anonymous_to_peers', 'false').lower() == 'true' else: anonymous_to_peers = False comment = cc.Comment( anonymous=anonymous, anonymous_to_peers=anonymous_to_peers, user_id=user.id, course_id=course_key.to_deprecated_string(), thread_id=thread_id, parent_id=parent_id, body=post["body"] ) comment.save() comment_created.send(sender=None, user=user, post=comment) followed = post.get('auto_subscribe', 'false').lower() == 'true' if followed: cc_user = cc.User.from_django_user(request.user) cc_user.follow(comment.thread) track_comment_created_event(request, course, comment, comment.thread.commentable_id, followed) # # Send notification # # Feature Flag to check that notifications are enabled or not. if settings.FEATURES.get("ENABLE_NOTIFICATIONS", False): action_user_id = request.user.id is_comment = not thread_id and parent_id replying_to_id = None # keep track of who we are replying to if is_comment: # If creating a comment, then we don't have the original thread_id # so we have to get it from the parent parent_comment = cc.Comment.find(parent_id) thread_id = parent_comment.thread_id replying_to_id = parent_comment.user_id thread = cc.Thread.find(thread_id) # IMPORTANT: we have to use getattr here as # otherwise the property will not get fetched # from cs_comment_service thread_user_id = int(getattr(thread, 'user_id', 0)) if not replying_to_id: # we must be creating a Reponse on a thread, # so the original poster is the author of the thread replying_to_id = thread_user_id # # IMPORTANT: We have to use getattr() here so that the # object is fully hydrated. This is a known limitation. # group_id = getattr(thread, 'group_id') if group_id: # We always send a notification to the whole cohort # when someone posts a comment, except the poster _send_discussion_notification( 'open-edx.lms.discussions.cohorted-comment-added', unicode(course_key), thread, request.user, excerpt=_get_excerpt(post["body"]), recipient_group_id=thread.get('group_id'), recipient_exclude_user_ids=[request.user.id], is_anonymous_user=anonymous or anonymous_to_peers ) elif parent_id is None and action_user_id != replying_to_id: # we have to only send the notifications when # the user commenting the thread is not # the same user who created the thread # parent_id is None: publish notification only when creating the comment on # the thread not replying on the comment. When the user replied on the comment # the parent_id is not None at that time _send_discussion_notification( 'open-edx.lms.discussions.reply-to-thread', unicode(course_key), thread, request.user, excerpt=_get_excerpt(post["body"]), recipient_user_id=replying_to_id, is_anonymous_user=anonymous or anonymous_to_peers ) if request.is_ajax(): return ajax_content_response(request, course_key, comment.to_dict()) else: return JsonResponse(prepare_content(comment.to_dict(), course.id))