Skip to content

Commit 85a5890

Browse files
feat: added api to update all notification preferences for user (#35795)
1 parent 930989e commit 85a5890

File tree

7 files changed

+1001
-24
lines changed

7 files changed

+1001
-24
lines changed

openedx/core/djangoapps/notifications/email/utils.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from django.contrib.auth import get_user_model
1010
from django.shortcuts import get_object_or_404
1111
from pytz import utc
12-
from waffle import get_waffle_flag_model # pylint: disable=invalid-django-waffle-import
12+
from waffle import get_waffle_flag_model # pylint: disable=invalid-django-waffle-import
1313

1414
from common.djangoapps.student.models import CourseEnrollment
1515
from lms.djangoapps.branding.api import get_logo_url_for_email
@@ -29,7 +29,6 @@
2929

3030
from .notification_icons import NotificationTypeIcons
3131

32-
3332
User = get_user_model()
3433

3534

@@ -370,14 +369,6 @@ def is_name_match(name, param_name):
370369
"""
371370
return True if param_name is None else name == param_name
372371

373-
def is_editable(app_name, notification_type, channel):
374-
"""
375-
Returns if notification type channel is editable
376-
"""
377-
if notification_type == 'core':
378-
return channel not in COURSE_NOTIFICATION_APPS[app_name]['non_editable']
379-
return channel not in COURSE_NOTIFICATION_TYPES[notification_type]['non_editable']
380-
381372
def get_default_cadence_value(app_name, notification_type):
382373
"""
383374
Returns default email cadence value
@@ -417,9 +408,18 @@ def get_updated_preference(pref):
417408
for channel in ['web', 'email', 'push']:
418409
if not is_name_match(channel, channel_value):
419410
continue
420-
if is_editable(app_name, noti_type, channel):
411+
if is_notification_type_channel_editable(app_name, noti_type, channel):
421412
type_prefs[channel] = pref_value
422413
if channel == 'email' and pref_value and type_prefs.get('email_cadence') == EmailCadence.NEVER:
423414
type_prefs['email_cadence'] = get_default_cadence_value(app_name, noti_type)
424415
preference.save()
425416
notification_preference_unsubscribe_event(user)
417+
418+
419+
def is_notification_type_channel_editable(app_name, notification_type, channel):
420+
"""
421+
Returns if notification type channel is editable
422+
"""
423+
if notification_type == 'core':
424+
return channel not in COURSE_NOTIFICATION_APPS[app_name]['non_editable']
425+
return channel not in COURSE_NOTIFICATION_TYPES[notification_type]['non_editable']

openedx/core/djangoapps/notifications/serializers.py

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Serializers for the notifications API.
33
"""
4+
45
from django.core.exceptions import ValidationError
56
from rest_framework import serializers
67

@@ -9,9 +10,12 @@
910
from openedx.core.djangoapps.notifications.models import (
1011
CourseNotificationPreference,
1112
Notification,
12-
get_notification_channels, get_additional_notification_channel_settings
13+
get_additional_notification_channel_settings,
14+
get_notification_channels
1315
)
16+
1417
from .base_notification import COURSE_NOTIFICATION_APPS, COURSE_NOTIFICATION_TYPES, EmailCadence
18+
from .email.utils import is_notification_type_channel_editable
1519
from .utils import remove_preferences_with_no_access
1620

1721

@@ -202,3 +206,113 @@ class Meta:
202206
'last_seen',
203207
'created',
204208
)
209+
210+
211+
def validate_email_cadence(email_cadence: str) -> str:
212+
"""
213+
Validate email cadence value.
214+
"""
215+
if EmailCadence.get_email_cadence_value(email_cadence) is None:
216+
raise ValidationError(f'{email_cadence} is not a valid email cadence.')
217+
return email_cadence
218+
219+
220+
def validate_notification_app(notification_app: str) -> str:
221+
"""
222+
Validate notification app value.
223+
"""
224+
if not COURSE_NOTIFICATION_APPS.get(notification_app):
225+
raise ValidationError(f'{notification_app} is not a valid notification app.')
226+
return notification_app
227+
228+
229+
def validate_notification_app_enabled(notification_app: str) -> str:
230+
"""
231+
Validate notification app is enabled.
232+
"""
233+
234+
if COURSE_NOTIFICATION_APPS.get(notification_app) and COURSE_NOTIFICATION_APPS.get(notification_app)['enabled']:
235+
return notification_app
236+
raise ValidationError(f'{notification_app} is not a valid notification app.')
237+
238+
239+
def validate_notification_type(notification_type: str) -> str:
240+
"""
241+
Validate notification type value.
242+
"""
243+
if not COURSE_NOTIFICATION_TYPES.get(notification_type):
244+
raise ValidationError(f'{notification_type} is not a valid notification type.')
245+
return notification_type
246+
247+
248+
def validate_notification_channel(notification_channel: str) -> str:
249+
"""
250+
Validate notification channel value.
251+
"""
252+
valid_channels = set(get_notification_channels()) | set(get_additional_notification_channel_settings())
253+
if notification_channel not in valid_channels:
254+
raise ValidationError(f'{notification_channel} is not a valid notification channel setting.')
255+
return notification_channel
256+
257+
258+
class UserNotificationPreferenceUpdateAllSerializer(serializers.Serializer):
259+
"""
260+
Serializer for user notification preferences update with custom field validators.
261+
"""
262+
notification_app = serializers.CharField(
263+
required=True,
264+
validators=[validate_notification_app, validate_notification_app_enabled]
265+
)
266+
value = serializers.BooleanField(required=False)
267+
notification_type = serializers.CharField(
268+
required=True,
269+
)
270+
notification_channel = serializers.CharField(
271+
required=False,
272+
validators=[validate_notification_channel]
273+
)
274+
email_cadence = serializers.CharField(
275+
required=False,
276+
validators=[validate_email_cadence]
277+
)
278+
279+
def validate(self, attrs):
280+
"""
281+
Cross-field validation for notification preference update.
282+
"""
283+
notification_app = attrs.get('notification_app')
284+
notification_type = attrs.get('notification_type')
285+
notification_channel = attrs.get('notification_channel')
286+
email_cadence = attrs.get('email_cadence')
287+
288+
# Validate email_cadence requirements
289+
if email_cadence and not notification_type:
290+
raise ValidationError({
291+
'notification_type': 'notification_type is required for email_cadence.'
292+
})
293+
294+
# Validate notification_channel requirements
295+
if not email_cadence and notification_type and not notification_channel:
296+
raise ValidationError({
297+
'notification_channel': 'notification_channel is required for notification_type.'
298+
})
299+
300+
# Validate notification type
301+
if all([not COURSE_NOTIFICATION_TYPES.get(notification_type), notification_type != "core"]):
302+
raise ValidationError(f'{notification_type} is not a valid notification type.')
303+
304+
# Validate notification type and channel is editable
305+
if notification_channel and notification_type:
306+
if not is_notification_type_channel_editable(
307+
notification_app,
308+
notification_type,
309+
notification_channel
310+
):
311+
raise ValidationError({
312+
'notification_channel': (
313+
f'{notification_channel} is not editable for notification type '
314+
f'{notification_type}.'
315+
)
316+
})
317+
318+
return attrs

0 commit comments

Comments
 (0)