From bac7e12b5cd20b91b03bd09197e4387b77aade27 Mon Sep 17 00:00:00 2001 From: Ahtisham Shahid Date: Tue, 30 Dec 2025 13:10:08 +0500 Subject: [PATCH] feat: make email cadence overrideable from settings (#37822) --- .../djangoapps/notifications/docs/settings.md | 22 ++++++----- .../notifications/settings_override.py | 4 +- .../tests/test_settings_override.py | 38 +++++++++++++++++++ 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/openedx/core/djangoapps/notifications/docs/settings.md b/openedx/core/djangoapps/notifications/docs/settings.md index 4b6afdbf99..fff5a5aaf0 100644 --- a/openedx/core/djangoapps/notifications/docs/settings.md +++ b/openedx/core/djangoapps/notifications/docs/settings.md @@ -1,4 +1,3 @@ - # Notification Configuration Guide This guide explains how to override default notification settings for the platform without modifying the core code base. You can customize delivery channels (Web, Email) and behavior for specific notification types or entire notification apps using your Django settings. @@ -31,6 +30,7 @@ You can only modify the following fields for a notification type. Any other fiel | `email` | `bool` | Enable/Disable email delivery. | | `push` | `bool` | Enable/Disable push notifications. | | `non_editable` | `list` | Prevent users from changing preferences for these channels. | +| `email_cadence` | `str` or `EmailCadence` | How often emails are sent. Allowed values: `Daily`, `Weekly`, `Immediately`, `Never` (or use the `EmailCadence` enum constants). ### Example Configuration @@ -38,17 +38,19 @@ In your `settings.py` (or equivalent): ```python NOTIFICATION_TYPES_OVERRIDE = { - # CASE 1: Disable emails for new discussion posts by default + # Disable emails for new discussion posts by default and set daily cadence 'new_discussion_post': { 'email': False, - 'web': True + 'web': True, + 'email_cadence': 'Daily', }, - # CASE 2: Force "Course Updates" to be strictly email-only (users cannot disable it) + # Force "Course Updates" to be strictly email-only and deliver immediately 'course_updates': { 'email': True, 'web': False, - 'non_editable': ['email'] # User UI will lock the email toggle + 'non_editable': ['email'], + 'email_cadence': 'Immediately', } } @@ -79,23 +81,25 @@ These keys affect all "Core" notifications belonging to the app. | `core_email` | `bool` | Enable/Disable email delivery for core events. | | `core_push` | `bool` | Enable/Disable push delivery for core events. | | `non_editable` | `list` | Channels users cannot modify (e.g., `['email']`). | +| `core_email_cadence` | `str` or `EmailCadence` | Default email cadence for core notifications. Allowed values: `Daily`, `Weekly`, `Immediately`, `Never` (or use the `EmailCadence` enum constants). ### Example Configuration ```python NOTIFICATION_APPS_OVERRIDE = { - # CASE: Make all Discussion notifications (comments, responses, etc.) - # Web-only by default to reduce email spam. + # Make all Discussion core notifications Web-only and weekly cadence 'discussion': { 'core_email': False, 'core_web': True, + 'core_email_cadence': 'Weekly', }, - # CASE: Ensure Grading notifications are always delivered via email + # Ensure Grading core notifications are always delivered via email immediately # and users cannot disable them. 'grading': { 'core_email': True, - 'non_editable': ['email'] + 'non_editable': ['email'], + 'core_email_cadence': 'Immediately', } } diff --git a/openedx/core/djangoapps/notifications/settings_override.py b/openedx/core/djangoapps/notifications/settings_override.py index 309e5c9511..254fd44df5 100644 --- a/openedx/core/djangoapps/notifications/settings_override.py +++ b/openedx/core/djangoapps/notifications/settings_override.py @@ -45,7 +45,7 @@ def get_notification_types_config() -> Dict[str, Any]: return _apply_overrides( default_config=DEFAULT_TYPES, setting_name='NOTIFICATION_TYPES_OVERRIDE', - allowed_keys={'web', 'email', 'push', 'non_editable'} + allowed_keys={'web', 'email', 'push', 'non_editable', 'email_cadence'} ) @@ -58,5 +58,5 @@ def get_notification_apps_config() -> Dict[str, Any]: return _apply_overrides( default_config=DEFAULT_APPS, setting_name='NOTIFICATION_APPS_OVERRIDE', - allowed_keys={'core_web', 'core_email', 'core_push', 'non_editable'} + allowed_keys={'core_web', 'core_email', 'core_push', 'non_editable', 'core_email_cadence'} ) diff --git a/openedx/core/djangoapps/notifications/tests/test_settings_override.py b/openedx/core/djangoapps/notifications/tests/test_settings_override.py index 26420bed1d..e096b54d08 100644 --- a/openedx/core/djangoapps/notifications/tests/test_settings_override.py +++ b/openedx/core/djangoapps/notifications/tests/test_settings_override.py @@ -106,3 +106,41 @@ class SettingsOverrideIntegrationTest(TestCase): target['email'], "The 'email' field should be preserved from the default config." ) + + @override_settings(NOTIFICATION_TYPES_OVERRIDE={ + 'new_discussion_post': { + 'email_cadence': 'Weekly' + } + }) + def test_override_notification_types_email_cadence(self): + """ + Test overriding email_cadence for an existing notification type. + Ensures the override is applied and the module-level default isn't mutated. + """ + config = get_notification_types_config() + target = config['new_discussion_post'] + + self.assertEqual( + target.get('email_cadence'), + 'Weekly', + "The 'email_cadence' setting should be overridden to 'Weekly'." + ) + + @override_settings(NOTIFICATION_APPS_OVERRIDE={ + 'discussion': { + 'core_email_cadence': 'Immediately' + } + }) + def test_override_notification_apps_email_cadence(self): + """ + Test overriding core_email_cadence for an existing notification app. + Ensures the override is applied and the module-level default isn't mutated. + """ + config = get_notification_apps_config() + target_app = config['discussion'] + + self.assertEqual( + target_app.get('core_email_cadence'), + 'Immediately', + "The 'core_email_cadence' setting should be overridden to 'Immediately'." + )