Skip to content

Commit

Permalink
feat: aded api to update all notification preferences for user
Browse files Browse the repository at this point in the history
fix: resolved linter errors

fix: resolved linter errors

fix: resolved linter errors

feat: added new api to get aggregated user preferences

fix: resolved linter errors

fix: resolved linter errors

fix: updated notification type validator

fix: updated validation logic

fix: updated validation and fixed unit tests

fix: updated validation and fixed unit tests

fix: added new cadence type

fix: removed extra tests

fix: resolved logical issue in email cadence

fix: resolved linter errors

fix: resolved none value for cadence issue
  • Loading branch information
AhtishamShahid committed Dec 11, 2024
1 parent ee30f1b commit bd70ce7
Show file tree
Hide file tree
Showing 7 changed files with 1,000 additions and 24 deletions.
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

0 comments on commit bd70ce7

Please sign in to comment.