Commit 8eb19002 by Alex Dusenbery Committed by Alex Dusenbery

EDUCATOR-1571 | Send discussion notification on first response; add a…

EDUCATOR-1571 | Send discussion notification on first response; add a CourseWaffleFlag for discussion notifications.
parent 3054ae43
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
This module contains various configuration settings via This module contains various configuration settings via
waffle switches for the Discussions app. waffle switches for the Discussions app.
""" """
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace, WaffleSwitchNamespace
# Namespace # Namespace
WAFFLE_NAMESPACE = u'discussions' WAFFLE_NAMESPACE = u'discussions'
...@@ -10,6 +10,12 @@ WAFFLE_NAMESPACE = u'discussions' ...@@ -10,6 +10,12 @@ WAFFLE_NAMESPACE = u'discussions'
# Switches # Switches
FORUM_RESPONSE_NOTIFICATIONS = u'forum_response_notifications' FORUM_RESPONSE_NOTIFICATIONS = u'forum_response_notifications'
SEND_NOTIFICATIONS_FOR_COURSE = CourseWaffleFlag(
waffle_namespace=WaffleFlagNamespace(name=WAFFLE_NAMESPACE),
flag_name=u'send_notifications_for_course',
flag_undefined_default=False
)
def waffle(): def waffle():
""" """
......
...@@ -3,12 +3,13 @@ Signal handlers related to discussions. ...@@ -3,12 +3,13 @@ Signal handlers related to discussions.
""" """
import logging import logging
from django.contrib.sites.models import Site
from django.dispatch import receiver from django.dispatch import receiver
from opaque_keys.edx.keys import CourseKey
from django_comment_common import signals from django_comment_common import signals
from lms.djangoapps.discussion.config.waffle import waffle, FORUM_RESPONSE_NOTIFICATIONS from lms.djangoapps.discussion.config.waffle import waffle, FORUM_RESPONSE_NOTIFICATIONS, SEND_NOTIFICATIONS_FOR_COURSE
from lms.djangoapps.discussion import tasks from lms.djangoapps.discussion import tasks
from openedx.core.djangoapps.theming.helpers import get_current_site
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -16,11 +17,23 @@ log = logging.getLogger(__name__) ...@@ -16,11 +17,23 @@ log = logging.getLogger(__name__)
@receiver(signals.comment_created) @receiver(signals.comment_created)
def send_discussion_email_notification(sender, user, post, **kwargs): def send_discussion_email_notification(sender, user, post, **kwargs):
if waffle().is_enabled(FORUM_RESPONSE_NOTIFICATIONS): if not waffle().is_enabled(FORUM_RESPONSE_NOTIFICATIONS):
send_message(post) log.debug('Discussion: Response notifications waffle switch not enabled')
return
if not SEND_NOTIFICATIONS_FOR_COURSE.is_enabled(CourseKey.from_string(post.thread.course_id)):
log.debug('Discussion: Response notifications not enabled for this course')
return
def send_message(comment): current_site = get_current_site()
if current_site is None:
log.debug('Discussion: No current site, not sending notification')
return
send_message(post, current_site)
def send_message(comment, site):
thread = comment.thread thread = comment.thread
context = { context = {
'course_id': unicode(thread.course_id), 'course_id': unicode(thread.course_id),
...@@ -28,13 +41,13 @@ def send_message(comment): ...@@ -28,13 +41,13 @@ def send_message(comment):
'comment_body': comment.body, 'comment_body': comment.body,
'comment_author_id': comment.user_id, 'comment_author_id': comment.user_id,
'comment_username': comment.username, 'comment_username': comment.username,
'comment_created_at': comment.created_at, 'comment_created_at': comment.created_at, # comment_client models dates are already serialized
'site_id': Site.objects.get_current().id,
'thread_id': thread.id, 'thread_id': thread.id,
'thread_title': thread.title, 'thread_title': thread.title,
'thread_username': thread.username, 'thread_username': thread.username,
'thread_author_id': thread.user_id, 'thread_author_id': thread.user_id,
'thread_created_at': thread.created_at, 'thread_created_at': thread.created_at, # comment_client models dates are already serialized
'thread_commentable_id': thread.commentable_id, 'thread_commentable_id': thread.commentable_id,
'site_id': site.id
} }
tasks.send_ace_message.apply_async(args=[context]) tasks.send_ace_message.apply_async(args=[context])
...@@ -13,13 +13,13 @@ from django.contrib.sites.models import Site ...@@ -13,13 +13,13 @@ from django.contrib.sites.models import Site
from celery_utils.logged_task import LoggedTask from celery_utils.logged_task import LoggedTask
from edx_ace import ace from edx_ace import ace
from edx_ace.utils import date
from edx_ace.message import MessageType from edx_ace.message import MessageType
from edx_ace.recipient import Recipient from edx_ace.recipient import Recipient
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.site_configuration.helpers import get_value from openedx.core.djangoapps.site_configuration.helpers import get_value
from lms.djangoapps.django_comment_client.utils import permalink from lms.djangoapps.django_comment_client.utils import permalink
import lms.lib.comment_client as cc import lms.lib.comment_client as cc
from lms.lib.comment_client.utils import merge_dict
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.schedules.template_context import get_base_template_context from openedx.core.djangoapps.schedules.template_context import get_base_template_context
...@@ -56,7 +56,22 @@ def send_ace_message(context): ...@@ -56,7 +56,22 @@ def send_ace_message(context):
def _should_send_message(context): def _should_send_message(context):
cc_thread_author = cc.User(id=context['thread_author_id'], course_id=context['course_id']) cc_thread_author = cc.User(id=context['thread_author_id'], course_id=context['course_id'])
return _is_user_subscribed_to_thread(cc_thread_author, context['thread_id']) return (
_is_user_subscribed_to_thread(cc_thread_author, context['thread_id']) and
_is_not_subcomment(context['comment_id']) and
_is_first_comment(context['comment_id'], context['thread_id'])
)
def _is_not_subcomment(comment_id):
comment = cc.Comment.find(id=comment_id).retrieve()
return not getattr(comment, 'parent_id', None)
def _is_first_comment(comment_id, thread_id):
thread = cc.Thread.find(id=thread_id).retrieve(with_responses=True)
first_comment = thread.children[0]
return first_comment.get('id') == comment_id
def _is_user_subscribed_to_thread(cc_user, thread_id): def _is_user_subscribed_to_thread(cc_user, thread_id):
...@@ -78,13 +93,19 @@ def _get_course_language(course_id): ...@@ -78,13 +93,19 @@ def _get_course_language(course_id):
def _build_message_context(context): def _build_message_context(context):
message_context = get_base_template_context(context['site']) message_context = get_base_template_context(Site.objects.get(id=context['site_id']))
message_context.update(context) message_context.update(_deserialize_context_dates(context))
message_context['post_link'] = _get_thread_url(context) message_context['post_link'] = _get_thread_url(context)
message_context['ga_tracking_pixel_url'] = _generate_ga_pixel_url(context) message_context['ga_tracking_pixel_url'] = _generate_ga_pixel_url(context)
return message_context return message_context
def _deserialize_context_dates(context):
context['comment_created_at'] = date.deserialize(context['comment_created_at'])
context['thread_created_at'] = date.deserialize(context['thread_created_at'])
return context
def _get_thread_url(context): def _get_thread_url(context):
thread_content = { thread_content = {
'type': 'thread', 'type': 'thread',
......
from django.test import TestCase from django.test import TestCase
import mock import mock
from opaque_keys.edx.keys import CourseKey
from django_comment_common import signals from django_comment_common import signals
from lms.djangoapps.discussion.config.waffle import waffle, FORUM_RESPONSE_NOTIFICATIONS from lms.djangoapps.discussion.config.waffle import waffle, FORUM_RESPONSE_NOTIFICATIONS, SEND_NOTIFICATIONS_FOR_COURSE
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
class SendMessageHandlerTestCase(TestCase): class SendMessageHandlerTestCase(TestCase):
def setUp(self):
self.sender = mock.Mock()
self.user = mock.Mock()
self.post = mock.Mock()
self.post.thread.course_id = 'course-v1:edX+DemoX+Demo_Course'
@mock.patch('lms.djangoapps.discussion.signals.handlers.get_current_site')
@mock.patch('lms.djangoapps.discussion.signals.handlers.send_message') @mock.patch('lms.djangoapps.discussion.signals.handlers.send_message')
def test_comment_created_signal_sends_message(self, mock_send_message): @override_waffle_flag(SEND_NOTIFICATIONS_FOR_COURSE, True)
def test_comment_created_signal_sends_message(self, mock_send_message, mock_get_current_site):
with waffle().override(FORUM_RESPONSE_NOTIFICATIONS): with waffle().override(FORUM_RESPONSE_NOTIFICATIONS):
sender = mock.Mock() signals.comment_created.send(sender=self.sender, user=self.user, post=self.post)
user = mock.Mock()
post = mock.Mock()
signals.comment_created.send(sender=sender, user=user, post=post)
mock_send_message.assert_called_once_with(post) mock_send_message.assert_called_once_with(self.post, mock_get_current_site.return_value)
@mock.patch('lms.djangoapps.discussion.signals.handlers.send_message') @mock.patch('lms.djangoapps.discussion.signals.handlers.send_message')
@override_waffle_flag(SEND_NOTIFICATIONS_FOR_COURSE, True)
def test_comment_created_signal_message_not_sent_without_waffle_switch(self, mock_send_message): def test_comment_created_signal_message_not_sent_without_waffle_switch(self, mock_send_message):
with waffle().override(FORUM_RESPONSE_NOTIFICATIONS, active=False): with waffle().override(FORUM_RESPONSE_NOTIFICATIONS, active=False):
sender = mock.Mock() signals.comment_created.send(sender=self.sender, user=self.user, post=self.post)
user = mock.Mock()
post = mock.Mock() self.assertFalse(mock_send_message.called)
signals.comment_created.send(sender=sender, user=user, post=post) @mock.patch('lms.djangoapps.discussion.signals.handlers.send_message')
def test_comment_created_signal_message_not_sent_without_course_waffle_flag(self, mock_send_message):
with waffle().override(FORUM_RESPONSE_NOTIFICATIONS, active=True):
signals.comment_created.send(sender=self.sender, user=self.user, post=self.post)
self.assertFalse(mock_send_message.called)
@mock.patch('lms.djangoapps.discussion.signals.handlers.get_current_site', return_value=None)
@mock.patch('lms.djangoapps.discussion.signals.handlers.send_message')
@override_waffle_flag(SEND_NOTIFICATIONS_FOR_COURSE, True)
def test_comment_created_signal_message_not_sent_without_site(self, mock_send_message, mock_get_current_site):
with waffle().override(FORUM_RESPONSE_NOTIFICATIONS, active=True):
signals.comment_created.send(sender=self.sender, user=self.user, post=self.post)
self.assertFalse(mock_send_message.called) self.assertFalse(mock_send_message.called)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment