feat: added support to use account level preferences with email digest (#36976)
This commit is contained in:
committed by
GitHub
parent
f996ed7c16
commit
113f0ff7e4
@@ -10,10 +10,12 @@ from edx_ace import ace
|
||||
from edx_ace.recipient import Recipient
|
||||
from edx_django_utils.monitoring import set_code_owner_attribute
|
||||
|
||||
from openedx.core.djangoapps.notifications.config.waffle import ENABLE_ACCOUNT_LEVEL_PREFERENCES
|
||||
from openedx.core.djangoapps.notifications.email_notifications import EmailCadence
|
||||
from openedx.core.djangoapps.notifications.models import (
|
||||
CourseNotificationPreference,
|
||||
Notification,
|
||||
NotificationPreference,
|
||||
get_course_notification_preference_config_version
|
||||
)
|
||||
from .events import send_immediate_email_digest_sent_event, send_user_email_digest_sent_event
|
||||
@@ -23,6 +25,7 @@ from .utils import (
|
||||
create_app_notifications_dict,
|
||||
create_email_digest_context,
|
||||
create_email_template_context,
|
||||
filter_email_enabled_notifications,
|
||||
filter_notification_with_email_enabled_preferences,
|
||||
get_course_info,
|
||||
get_language_preference_for_users,
|
||||
@@ -99,9 +102,15 @@ def send_digest_email_to_user(user, cadence_type, start_date, end_date, user_lan
|
||||
return
|
||||
|
||||
with translation_override(user_language):
|
||||
course_ids = get_unique_course_ids(notifications)
|
||||
preferences = get_user_preferences_for_courses(course_ids, user)
|
||||
notifications = filter_notification_with_email_enabled_preferences(notifications, preferences, cadence_type)
|
||||
if ENABLE_ACCOUNT_LEVEL_PREFERENCES.is_enabled():
|
||||
preferences = NotificationPreference.objects.filter(user=user)
|
||||
notifications = filter_email_enabled_notifications(notifications, preferences, user,
|
||||
cadence_type=cadence_type)
|
||||
else:
|
||||
course_ids = get_unique_course_ids(notifications)
|
||||
preferences = get_user_preferences_for_courses(course_ids, user)
|
||||
notifications = filter_notification_with_email_enabled_preferences(notifications, preferences, cadence_type)
|
||||
|
||||
if not notifications:
|
||||
logger.info(f'<Email Cadence> No filtered notification for {user.username} ==Temp Log==')
|
||||
return
|
||||
|
||||
@@ -9,7 +9,9 @@ from unittest.mock import patch
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICATIONS, ENABLE_EMAIL_NOTIFICATIONS
|
||||
from openedx.core.djangoapps.notifications.config.waffle import (
|
||||
ENABLE_ACCOUNT_LEVEL_PREFERENCES, ENABLE_NOTIFICATIONS, ENABLE_EMAIL_NOTIFICATIONS
|
||||
)
|
||||
from openedx.core.djangoapps.notifications.tasks import send_notifications
|
||||
from openedx.core.djangoapps.notifications.email_notifications import EmailCadence
|
||||
from openedx.core.djangoapps.notifications.email.tasks import (
|
||||
@@ -18,7 +20,7 @@ from openedx.core.djangoapps.notifications.email.tasks import (
|
||||
send_digest_email_to_user
|
||||
)
|
||||
from openedx.core.djangoapps.notifications.email.utils import get_start_end_date
|
||||
from openedx.core.djangoapps.notifications.models import CourseNotificationPreference
|
||||
from openedx.core.djangoapps.notifications.models import CourseNotificationPreference, NotificationPreference
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
@@ -125,6 +127,107 @@ class TestEmailDigestForUser(ModuleStoreTestCase):
|
||||
assert mock_func.called is notification_created
|
||||
|
||||
|
||||
@override_waffle_flag(ENABLE_ACCOUNT_LEVEL_PREFERENCES, True)
|
||||
@ddt.ddt
|
||||
class TestEmailDigestForUserWithAccountPreferences(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests email notification for a specific user
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Setup
|
||||
"""
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
self.course = CourseFactory.create(display_name='test course', run="Testing_course")
|
||||
|
||||
@patch('edx_ace.ace.send')
|
||||
def test_email_is_not_sent_if_no_notifications(self, mock_func):
|
||||
"""
|
||||
Tests email is sent iff waffle flag is enabled
|
||||
"""
|
||||
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
|
||||
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
|
||||
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
|
||||
assert not mock_func.called
|
||||
|
||||
@ddt.data(True, False)
|
||||
@patch('edx_ace.ace.send')
|
||||
def test_email_is_sent_iff_flag_enabled(self, flag_value, mock_func):
|
||||
"""
|
||||
Tests email is sent iff waffle flag is enabled
|
||||
"""
|
||||
created_date = datetime.datetime.now() - datetime.timedelta(days=1)
|
||||
create_notification(self.user, self.course.id, created=created_date)
|
||||
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
|
||||
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, flag_value):
|
||||
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
|
||||
assert mock_func.called is flag_value
|
||||
|
||||
@patch('edx_ace.ace.send')
|
||||
def test_notification_not_send_if_created_on_next_day(self, mock_func):
|
||||
"""
|
||||
Tests email is not sent if notification is created on next day
|
||||
"""
|
||||
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
|
||||
create_notification(self.user, self.course.id, created=end_date + datetime.timedelta(minutes=2))
|
||||
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
|
||||
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
|
||||
assert not mock_func.called
|
||||
|
||||
@ddt.data(True, False)
|
||||
@patch('edx_ace.ace.send')
|
||||
def test_email_not_send_to_disable_user(self, value, mock_func):
|
||||
"""
|
||||
Tests email is not sent to disabled user
|
||||
"""
|
||||
created_date = datetime.datetime.now() - datetime.timedelta(days=1)
|
||||
create_notification(self.user, self.course.id, created=created_date)
|
||||
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
|
||||
if value:
|
||||
self.user.set_password("12345678")
|
||||
else:
|
||||
self.user.set_unusable_password()
|
||||
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
|
||||
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
|
||||
assert mock_func.called is value
|
||||
|
||||
@patch('edx_ace.ace.send')
|
||||
def test_notification_not_send_if_created_day_before_yesterday(self, mock_func):
|
||||
"""
|
||||
Tests email is not sent if notification is created day before yesterday
|
||||
"""
|
||||
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
|
||||
created_date = datetime.datetime.now() - datetime.timedelta(days=1, minutes=18)
|
||||
create_notification(self.user, self.course.id, created=created_date)
|
||||
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
|
||||
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
|
||||
assert not mock_func.called
|
||||
|
||||
@ddt.data(
|
||||
(EmailCadence.DAILY, datetime.datetime.now() - datetime.timedelta(days=1, minutes=30), False),
|
||||
(EmailCadence.DAILY, datetime.datetime.now() - datetime.timedelta(minutes=10), True),
|
||||
(EmailCadence.DAILY, datetime.datetime.now() - datetime.timedelta(days=1), True),
|
||||
(EmailCadence.DAILY, datetime.datetime.now() + datetime.timedelta(minutes=20), False),
|
||||
(EmailCadence.WEEKLY, datetime.datetime.now() - datetime.timedelta(days=7, minutes=30), False),
|
||||
(EmailCadence.WEEKLY, datetime.datetime.now() - datetime.timedelta(days=7), True),
|
||||
(EmailCadence.WEEKLY, datetime.datetime.now() - datetime.timedelta(minutes=20), True),
|
||||
(EmailCadence.WEEKLY, datetime.datetime.now() + datetime.timedelta(minutes=20), False),
|
||||
)
|
||||
@ddt.unpack
|
||||
@patch('edx_ace.ace.send')
|
||||
def test_notification_content(self, cadence_type, created_time, notification_created, mock_func):
|
||||
"""
|
||||
Tests email only contains notification created within date
|
||||
"""
|
||||
start_date, end_date = get_start_end_date(cadence_type)
|
||||
create_notification(self.user, self.course.id, created=created_time)
|
||||
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
|
||||
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
|
||||
assert mock_func.called is notification_created
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestEmailDigestAudience(ModuleStoreTestCase):
|
||||
"""
|
||||
@@ -261,6 +364,65 @@ class TestPreferences(ModuleStoreTestCase):
|
||||
assert not mock_func.called
|
||||
|
||||
|
||||
@override_waffle_flag(ENABLE_ACCOUNT_LEVEL_PREFERENCES, True)
|
||||
@ddt.ddt
|
||||
class TestAccountPreferences(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests preferences
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Setup
|
||||
"""
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
self.course = CourseFactory.create(display_name='test course', run="Testing_course")
|
||||
self.preference, _ = NotificationPreference.objects.get_or_create(user=self.user, app="discussion",
|
||||
type="new_discussion_post")
|
||||
created_date = datetime.datetime.now() - datetime.timedelta(hours=23)
|
||||
create_notification(self.user, self.course.id, notification_type='new_discussion_post', created=created_date)
|
||||
|
||||
@patch('edx_ace.ace.send')
|
||||
def test_email_send_for_digest_preference(self, mock_func):
|
||||
"""
|
||||
Tests email is send for digest notification preference
|
||||
"""
|
||||
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
|
||||
self.preference.email = True
|
||||
self.preference.email_cadence = EmailCadence.DAILY
|
||||
self.preference.save()
|
||||
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
|
||||
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
|
||||
assert mock_func.called
|
||||
|
||||
@ddt.data(True, False)
|
||||
@patch('edx_ace.ace.send')
|
||||
def test_email_send_for_email_preference_value(self, pref_value, mock_func):
|
||||
"""
|
||||
Tests email is sent iff preference value is True
|
||||
"""
|
||||
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
|
||||
self.preference.email = pref_value
|
||||
self.preference.email_cadence = EmailCadence.DAILY
|
||||
self.preference.save()
|
||||
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
|
||||
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
|
||||
assert mock_func.called is pref_value
|
||||
|
||||
@patch('edx_ace.ace.send')
|
||||
def test_email_not_send_if_different_digest_preference(self, mock_func):
|
||||
"""
|
||||
Tests email is not send if digest notification preference doesnot match
|
||||
"""
|
||||
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
|
||||
self.preference.email = True
|
||||
self.preference.email_cadence = EmailCadence.WEEKLY
|
||||
self.preference.save()
|
||||
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
|
||||
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
|
||||
assert not mock_func.called
|
||||
|
||||
|
||||
class TestImmediateEmail(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests immediate email
|
||||
|
||||
@@ -23,6 +23,7 @@ from openedx.core.djangoapps.notifications.email_notifications import EmailCaden
|
||||
from openedx.core.djangoapps.notifications.events import notification_preference_unsubscribe_event
|
||||
from openedx.core.djangoapps.notifications.models import (
|
||||
CourseNotificationPreference,
|
||||
NotificationPreference,
|
||||
get_course_notification_preference_config_version
|
||||
)
|
||||
from openedx.core.djangoapps.user_api.models import UserPreference
|
||||
@@ -325,6 +326,63 @@ def filter_notification_with_email_enabled_preferences(notifications, preference
|
||||
return filtered_notifications
|
||||
|
||||
|
||||
def create_missing_account_level_preferences(notifications, preferences, user):
|
||||
"""
|
||||
Creates missing account level preferences for notifications
|
||||
"""
|
||||
preferences = list(preferences)
|
||||
notification_types = list(set(
|
||||
(notification.app_name, "core") if COURSE_NOTIFICATION_TYPES[notification.notification_type]["is_core"]
|
||||
else (notification.app_name, notification.notification_type)
|
||||
for notification in notifications
|
||||
))
|
||||
missing_prefs = []
|
||||
for notification_type in notification_types:
|
||||
if not any(
|
||||
preference.app == notification_type[0] and preference.type == notification_type[1]
|
||||
for preference in preferences
|
||||
):
|
||||
if notification_type[1] == "core":
|
||||
app_pref = COURSE_NOTIFICATION_APPS.get(notification_type[0], {})
|
||||
default_pref = {
|
||||
"notification_app": notification_type[0],
|
||||
"web": app_pref["core_web"],
|
||||
"push": app_pref["core_push"],
|
||||
"email": app_pref["core_email"],
|
||||
"email_cadence": app_pref["core_email_cadence"]
|
||||
}
|
||||
else:
|
||||
default_pref = COURSE_NOTIFICATION_TYPES.get(notification_type[1], {})
|
||||
missing_prefs.append(
|
||||
NotificationPreference(
|
||||
user=user, type=notification_type[1], app=notification_type[0], web=default_pref['web'],
|
||||
push=default_pref['push'], email=default_pref['email'], email_cadence=default_pref['email_cadence'],
|
||||
)
|
||||
)
|
||||
if missing_prefs:
|
||||
created_prefs = NotificationPreference.objects.bulk_create(missing_prefs, ignore_conflicts=True)
|
||||
preferences = preferences + list(created_prefs)
|
||||
return preferences
|
||||
|
||||
|
||||
def filter_email_enabled_notifications(notifications, preferences, user, cadence_type=EmailCadence.DAILY):
|
||||
"""
|
||||
Filter notifications with email enabled in account level preferences
|
||||
"""
|
||||
preferences = create_missing_account_level_preferences(notifications, preferences, user)
|
||||
enabled_course_prefs = [
|
||||
preference.type
|
||||
for preference in preferences
|
||||
if preference.email and preference.email_cadence == cadence_type
|
||||
]
|
||||
filtered_notifications = []
|
||||
for notification in notifications:
|
||||
if notification.notification_type in enabled_course_prefs:
|
||||
filtered_notifications.append(notification)
|
||||
filtered_notifications.sort(key=lambda elem: elem.created, reverse=True)
|
||||
return filtered_notifications
|
||||
|
||||
|
||||
def encrypt_string(string):
|
||||
"""
|
||||
Encrypts input string
|
||||
|
||||
Reference in New Issue
Block a user