Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added api to update all notification preferences for user #35795

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions openedx/core/djangoapps/notifications/email/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from pytz import utc
from waffle import get_waffle_flag_model # pylint: disable=invalid-django-waffle-import
from waffle import get_waffle_flag_model # pylint: disable=invalid-django-waffle-import

from common.djangoapps.student.models import CourseEnrollment
from lms.djangoapps.branding.api import get_logo_url_for_email
Expand All @@ -29,7 +29,6 @@

from .notification_icons import NotificationTypeIcons


User = get_user_model()


Expand Down Expand Up @@ -370,14 +369,6 @@ def is_name_match(name, param_name):
"""
return True if param_name is None else name == param_name

def is_editable(app_name, notification_type, channel):
"""
Returns if notification type channel is editable
"""
if notification_type == 'core':
return channel not in COURSE_NOTIFICATION_APPS[app_name]['non_editable']
return channel not in COURSE_NOTIFICATION_TYPES[notification_type]['non_editable']

def get_default_cadence_value(app_name, notification_type):
"""
Returns default email cadence value
Expand Down Expand Up @@ -417,9 +408,18 @@ def get_updated_preference(pref):
for channel in ['web', 'email', 'push']:
if not is_name_match(channel, channel_value):
continue
if is_editable(app_name, noti_type, channel):
if is_notification_type_channel_editable(app_name, noti_type, channel):
type_prefs[channel] = pref_value
if channel == 'email' and pref_value and type_prefs.get('email_cadence') == EmailCadence.NEVER:
type_prefs['email_cadence'] = get_default_cadence_value(app_name, noti_type)
preference.save()
notification_preference_unsubscribe_event(user)


def is_notification_type_channel_editable(app_name, notification_type, channel):
"""
Returns if notification type channel is editable
"""
if notification_type == 'core':
return channel not in COURSE_NOTIFICATION_APPS[app_name]['non_editable']
return channel not in COURSE_NOTIFICATION_TYPES[notification_type]['non_editable']
116 changes: 115 additions & 1 deletion openedx/core/djangoapps/notifications/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Serializers for the notifications API.
"""

from django.core.exceptions import ValidationError
from rest_framework import serializers

Expand All @@ -9,9 +10,12 @@
from openedx.core.djangoapps.notifications.models import (
CourseNotificationPreference,
Notification,
get_notification_channels, get_additional_notification_channel_settings
get_additional_notification_channel_settings,
get_notification_channels
)

from .base_notification import COURSE_NOTIFICATION_APPS, COURSE_NOTIFICATION_TYPES, EmailCadence
from .email.utils import is_notification_type_channel_editable
from .utils import remove_preferences_with_no_access


Expand Down Expand Up @@ -202,3 +206,113 @@ class Meta:
'last_seen',
'created',
)


def validate_email_cadence(email_cadence: str) -> str:
"""
Validate email cadence value.
"""
if EmailCadence.get_email_cadence_value(email_cadence) is None:
raise ValidationError(f'{email_cadence} is not a valid email cadence.')
return email_cadence


def validate_notification_app(notification_app: str) -> str:
"""
Validate notification app value.
"""
if not COURSE_NOTIFICATION_APPS.get(notification_app):
raise ValidationError(f'{notification_app} is not a valid notification app.')
return notification_app


def validate_notification_app_enabled(notification_app: str) -> str:
"""
Validate notification app is enabled.
"""

if COURSE_NOTIFICATION_APPS.get(notification_app) and COURSE_NOTIFICATION_APPS.get(notification_app)['enabled']:
return notification_app
raise ValidationError(f'{notification_app} is not a valid notification app.')


def validate_notification_type(notification_type: str) -> str:
"""
Validate notification type value.
"""
if not COURSE_NOTIFICATION_TYPES.get(notification_type):
raise ValidationError(f'{notification_type} is not a valid notification type.')
return notification_type


def validate_notification_channel(notification_channel: str) -> str:
"""
Validate notification channel value.
"""
valid_channels = set(get_notification_channels()) | set(get_additional_notification_channel_settings())
if notification_channel not in valid_channels:
raise ValidationError(f'{notification_channel} is not a valid notification channel setting.')
return notification_channel


class UserNotificationPreferenceUpdateAllSerializer(serializers.Serializer):
"""
Serializer for user notification preferences update with custom field validators.
"""
notification_app = serializers.CharField(
required=True,
validators=[validate_notification_app, validate_notification_app_enabled]
)
value = serializers.BooleanField(required=False)
notification_type = serializers.CharField(
required=True,
)
notification_channel = serializers.CharField(
required=False,
validators=[validate_notification_channel]
)
email_cadence = serializers.CharField(
required=False,
validators=[validate_email_cadence]
)

def validate(self, attrs):
"""
Cross-field validation for notification preference update.
"""
notification_app = attrs.get('notification_app')
notification_type = attrs.get('notification_type')
notification_channel = attrs.get('notification_channel')
email_cadence = attrs.get('email_cadence')

# Validate email_cadence requirements
if email_cadence and not notification_type:
raise ValidationError({
'notification_type': 'notification_type is required for email_cadence.'
})

# Validate notification_channel requirements
if not email_cadence and notification_type and not notification_channel:
raise ValidationError({
'notification_channel': 'notification_channel is required for notification_type.'
})

# Validate notification type
if all([not COURSE_NOTIFICATION_TYPES.get(notification_type), notification_type != "core"]):
raise ValidationError(f'{notification_type} is not a valid notification type.')

# Validate notification type and channel is editable
if notification_channel and notification_type:
if not is_notification_type_channel_editable(
notification_app,
notification_type,
notification_channel
):
raise ValidationError({
'notification_channel': (
f'{notification_channel} is not editable for notification type '
f'{notification_type}.'
)
})

return attrs
Loading
Loading