feat: implementing basic functionality to incorporate new notification types
This commit is contained in:
@@ -4,15 +4,24 @@ Django Admin for Notifications
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Notification, NotificationPreference
|
||||
from .models import Notification, CourseNotificationPreference
|
||||
|
||||
|
||||
class NotificationAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class NotificationPreferenceAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
class CourseNotificationPreferenceAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin for Course Notification Preferences
|
||||
"""
|
||||
model = CourseNotificationPreference
|
||||
list_display = ['get_username', 'course_id', 'notification_preference_config']
|
||||
|
||||
@admin.display(description='Username', ordering='user__username')
|
||||
def get_username(self, obj):
|
||||
return obj.user.username
|
||||
|
||||
|
||||
admin.site.register(Notification, NotificationAdmin)
|
||||
admin.site.register(NotificationPreference, NotificationPreferenceAdmin)
|
||||
admin.site.register(CourseNotificationPreference, CourseNotificationPreferenceAdmin)
|
||||
|
||||
180
openedx/core/djangoapps/notifications/base_notification.py
Normal file
180
openedx/core/djangoapps/notifications/base_notification.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""
|
||||
Base setup for Notification Apps and Types.
|
||||
"""
|
||||
from typing import Dict
|
||||
|
||||
COURSE_NOTIFICATION_TYPES = {
|
||||
'new_comment_on_response': {
|
||||
'notification_app': 'discussion',
|
||||
'name': 'new_comment_on_response',
|
||||
'is_core': True,
|
||||
'info': 'Comment on response',
|
||||
'content_template': '<p><strong>{replier_name}</strong> replied on your response in '
|
||||
'<strong>{post_title}</strong></p>',
|
||||
'content_context': {
|
||||
'post_title': 'Post title',
|
||||
'replier_name': 'replier name',
|
||||
},
|
||||
'email_template': '',
|
||||
},
|
||||
'new_comment': {
|
||||
'notification_app': 'discussion',
|
||||
'name': 'new_comment',
|
||||
'is_core': False,
|
||||
'web': True,
|
||||
'email': True,
|
||||
'push': True,
|
||||
'info': 'Comment on post',
|
||||
'non-editable': ['web', 'email'],
|
||||
'content_template': '<p><strong>{replier_name}</strong> replied on <strong>{author_name}</strong> response '
|
||||
'to your post <strong>{post_title}</strong></p>',
|
||||
'content_context': {
|
||||
'post_title': 'Post title',
|
||||
'author_name': 'author name',
|
||||
'replier_name': 'replier name',
|
||||
},
|
||||
'email_template': '',
|
||||
},
|
||||
'new_response': {
|
||||
'notification_app': 'discussion',
|
||||
'name': 'new_response',
|
||||
'is_core': False,
|
||||
'web': True,
|
||||
'email': True,
|
||||
'push': True,
|
||||
'info': 'Response on post',
|
||||
'non-editable': [],
|
||||
'content_template': '<p><strong>{replier_name}</strong> responded to your '
|
||||
'post <strong>{post_title}</strong></p>',
|
||||
'content_context': {
|
||||
'post_title': 'Post title',
|
||||
'replier_name': 'replier name',
|
||||
},
|
||||
'email_template': '',
|
||||
},
|
||||
}
|
||||
|
||||
COURSE_NOTIFICATION_APPS = {
|
||||
'discussion': {
|
||||
'enabled': True,
|
||||
'core_info': '',
|
||||
'core_web': True,
|
||||
'core_email': True,
|
||||
'core_push': True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class NotificationTypeManager:
|
||||
"""
|
||||
Manager for notification types
|
||||
"""
|
||||
notification_types: Dict = {}
|
||||
|
||||
def __init__(self):
|
||||
self.notification_types = COURSE_NOTIFICATION_TYPES
|
||||
|
||||
def get_notification_types_by_app(self, notification_app):
|
||||
"""
|
||||
Returns notification types for the given notification app.
|
||||
"""
|
||||
return [
|
||||
notification_type for _, notification_type in self.notification_types.items()
|
||||
if notification_type.get('notification_app', None) == notification_app
|
||||
]
|
||||
|
||||
def get_core_and_non_core_notification_types(self, notification_app):
|
||||
"""
|
||||
Returns core notification types for the given app name.
|
||||
"""
|
||||
notification_types = self.get_notification_types_by_app(notification_app)
|
||||
core_notification_types = []
|
||||
non_core_notification_types = []
|
||||
for notification_type in notification_types:
|
||||
if notification_type.get('is_core', None):
|
||||
core_notification_types.append(notification_type)
|
||||
else:
|
||||
non_core_notification_types.append(notification_type)
|
||||
return core_notification_types, non_core_notification_types
|
||||
|
||||
@staticmethod
|
||||
def get_non_editable_notification_channels(notification_types):
|
||||
"""
|
||||
Returns non-editable notification channels for the given notification types.
|
||||
"""
|
||||
non_editable_notification_channels = {}
|
||||
for notification_type in notification_types:
|
||||
if notification_type.get('non-editable', None):
|
||||
non_editable_notification_channels[notification_type.get('name')] = \
|
||||
notification_type.get('non-editable')
|
||||
return non_editable_notification_channels
|
||||
|
||||
@staticmethod
|
||||
def get_non_core_notification_type_preferences(non_core_notification_types):
|
||||
"""
|
||||
Returns non-core notification type preferences for the given notification types.
|
||||
"""
|
||||
non_core_notification_type_preferences = {}
|
||||
for notification_type in non_core_notification_types:
|
||||
non_core_notification_type_preferences[notification_type.get('name')] = {
|
||||
'web': notification_type.get('web', False),
|
||||
'email': notification_type.get('email', False),
|
||||
'push': notification_type.get('push', False),
|
||||
'info': notification_type.get('info', ''),
|
||||
}
|
||||
return non_core_notification_type_preferences
|
||||
|
||||
def get_notification_app_preference(self, notification_app):
|
||||
"""
|
||||
Returns notification app preferences for the given notification app.
|
||||
"""
|
||||
core_notification_types, non_core_notification_types = self.get_core_and_non_core_notification_types(
|
||||
notification_app,
|
||||
)
|
||||
non_core_notification_types_preferences = self.get_non_core_notification_type_preferences(
|
||||
non_core_notification_types,
|
||||
)
|
||||
non_editable_notification_channels = self.get_non_editable_notification_channels(non_core_notification_types)
|
||||
core_notification_types_name = [notification_type.get('name') for notification_type in core_notification_types]
|
||||
return non_core_notification_types_preferences, core_notification_types_name, non_editable_notification_channels
|
||||
|
||||
|
||||
class NotificationAppManager:
|
||||
"""
|
||||
Notification app manager
|
||||
"""
|
||||
notification_apps: Dict = {}
|
||||
|
||||
def __init__(self):
|
||||
self.notification_apps = COURSE_NOTIFICATION_APPS
|
||||
|
||||
def add_core_notification_preference(self, notification_app_attrs, notification_types):
|
||||
"""
|
||||
Adds core notification preference for the given notification app.
|
||||
"""
|
||||
notification_types['core'] = {
|
||||
'web': notification_app_attrs.get('core_web', False),
|
||||
'email': notification_app_attrs.get('core_email', False),
|
||||
'push': notification_app_attrs.get('core_push', False),
|
||||
'info': notification_app_attrs.get('core_info', ''),
|
||||
}
|
||||
|
||||
def get_notification_app_preferences(self):
|
||||
"""
|
||||
Returns notification app preferences for the given name.
|
||||
"""
|
||||
course_notification_preference_config = {}
|
||||
for notification_app_key, notification_app_attrs in COURSE_NOTIFICATION_APPS.items():
|
||||
notification_app_preferences = {}
|
||||
notification_types, core_notifications, \
|
||||
non_editable_channels = NotificationTypeManager().get_notification_app_preference(notification_app_key)
|
||||
self.add_core_notification_preference(notification_app_attrs, notification_types)
|
||||
|
||||
notification_app_preferences['enabled'] = notification_app_attrs.get('enabled', False)
|
||||
notification_app_preferences['core_notification_types'] = core_notifications
|
||||
notification_app_preferences['notification_types'] = notification_types
|
||||
notification_app_preferences['non_editable'] = non_editable_channels
|
||||
course_notification_preference_config[notification_app_key] = notification_app_preferences
|
||||
|
||||
return course_notification_preference_config
|
||||
return None
|
||||
@@ -8,7 +8,7 @@ from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICATIONS
|
||||
from openedx.core.djangoapps.notifications.models import NotificationPreference
|
||||
from openedx.core.djangoapps.notifications.models import CourseNotificationPreference
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -17,10 +17,11 @@ log = logging.getLogger(__name__)
|
||||
def course_enrollment_post_save(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Watches for post_save signal for creates on the CourseEnrollment table.
|
||||
Generate a NotificationPreference if new Enrollment is created
|
||||
Generate a CourseNotificationPreference if new Enrollment is created
|
||||
"""
|
||||
if created and ENABLE_NOTIFICATIONS.is_enabled(instance.course_id):
|
||||
try:
|
||||
NotificationPreference.objects.create(user=instance.user, course_id=instance.course_id)
|
||||
CourseNotificationPreference.objects.create(user=instance.user, course_id=instance.course_id)
|
||||
except IntegrityError:
|
||||
log.info(f'NotificationPreference already exists for user {instance.user} and course {instance.course_id}')
|
||||
log.info(f'CourseNotificationPreference already exists for user {instance.user} '
|
||||
f'and course {instance.course_id}')
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 3.2.19 on 2023-05-26 12:13
|
||||
|
||||
from django.db import migrations, models
|
||||
import openedx.core.djangoapps.notifications.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('notifications', '0003_alter_notification_app_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='content_context',
|
||||
field=models.JSONField(default=dict),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notificationpreference',
|
||||
name='notification_preference_config',
|
||||
field=models.JSONField(default=openedx.core.djangoapps.notifications.models.get_notification_preference_config),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
# Generated by Django 3.2.19 on 2023-06-07 07:57
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
import opaque_keys.edx.django.models
|
||||
import openedx.core.djangoapps.notifications.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('notifications', '0003_alter_notification_app_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CourseNotificationPreference',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||
('course_id', opaque_keys.edx.django.models.CourseKeyField(max_length=255)),
|
||||
('notification_preference_config', models.JSONField(default=openedx.core.djangoapps.notifications.models.get_course_notification_preference_config)),
|
||||
('config_version', models.IntegerField(default=openedx.core.djangoapps.notifications.models.get_course_notification_preference_config_version)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_preferences', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('user', 'course_id')},
|
||||
},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='notification',
|
||||
name='content',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='notification',
|
||||
name='course_id',
|
||||
field=opaque_keys.edx.django.models.CourseKeyField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='app_name',
|
||||
field=models.CharField(db_index=True, max_length=64),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='content_context',
|
||||
field=models.JSONField(default=dict),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='notification_type',
|
||||
field=models.CharField(max_length=64),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='NotificationPreference',
|
||||
),
|
||||
]
|
||||
@@ -6,50 +6,67 @@ from django.db import models
|
||||
from model_utils.models import TimeStampedModel
|
||||
from opaque_keys.edx.django.models import CourseKeyField
|
||||
|
||||
from openedx.core.djangoapps.notifications.base_notification import NotificationAppManager
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
NOTIFICATION_CHANNELS = ['web', 'push', 'email']
|
||||
|
||||
# When notification preferences are updated, we need to update the CONFIG_VERSION.
|
||||
NOTIFICATION_PREFERENCE_CONFIG = {
|
||||
'discussion': {
|
||||
'enabled': False,
|
||||
'notification_types': {
|
||||
'new_post': {
|
||||
'info': '',
|
||||
'web': False,
|
||||
'push': False,
|
||||
'email': False,
|
||||
},
|
||||
'core': {
|
||||
'info': '',
|
||||
'web': False,
|
||||
'push': False,
|
||||
'email': False,
|
||||
},
|
||||
},
|
||||
# This is a list of notification channels for notification type that are not editable by the user.
|
||||
# e.g. 'new_post' web notification is not editable by user i.e. 'not_editable': {'new_post': ['web']}
|
||||
'not_editable': {},
|
||||
},
|
||||
}
|
||||
# Update this version when NOTIFICATION_PREFERENCE_CONFIG is updated.
|
||||
NOTIFICATION_CONFIG_VERSION = 1
|
||||
# Update this version when there is a change to any course specific notification type or app.
|
||||
COURSE_NOTIFICATION_CONFIG_VERSION = 1
|
||||
|
||||
|
||||
def get_notification_preference_config():
|
||||
def get_course_notification_preference_config():
|
||||
"""
|
||||
Returns the notification preference config.
|
||||
Returns the course specific notification preference config.
|
||||
|
||||
Sample Response:
|
||||
{
|
||||
'discussion': {
|
||||
'enabled': True,
|
||||
'not_editable': {
|
||||
'new_comment_on_post': ['push'],
|
||||
'new_response_on_post': ['web'],
|
||||
'new_response_on_comment': ['web', 'push']
|
||||
},
|
||||
'notification_types': {
|
||||
'new_comment_on_post': {
|
||||
'email': True,
|
||||
'push': True,
|
||||
'web': True,
|
||||
'info': 'Comment on post'
|
||||
},
|
||||
'new_response_on_comment': {
|
||||
'email': True,
|
||||
'push': True,
|
||||
'web': True,
|
||||
'info': 'Response on comment'
|
||||
},
|
||||
'new_response_on_post': {
|
||||
'email': True,
|
||||
'push': True,
|
||||
'web': True,
|
||||
'info': 'New Response on Post'
|
||||
},
|
||||
'core': {
|
||||
'email': True,
|
||||
'push': True,
|
||||
'web': True,
|
||||
'info': 'comment on post and response on comment'
|
||||
}
|
||||
},
|
||||
'core_notification_types': []
|
||||
}
|
||||
}
|
||||
"""
|
||||
return NOTIFICATION_PREFERENCE_CONFIG
|
||||
return NotificationAppManager().get_notification_app_preferences()
|
||||
|
||||
|
||||
def get_notification_preference_config_version():
|
||||
def get_course_notification_preference_config_version():
|
||||
"""
|
||||
Returns the notification preference config version.
|
||||
"""
|
||||
return NOTIFICATION_CONFIG_VERSION
|
||||
return COURSE_NOTIFICATION_CONFIG_VERSION
|
||||
|
||||
|
||||
def get_notification_channels():
|
||||
@@ -59,27 +76,6 @@ def get_notification_channels():
|
||||
return NOTIFICATION_CHANNELS
|
||||
|
||||
|
||||
class NotificationApplication(models.TextChoices):
|
||||
"""
|
||||
Application choices where notifications are generated from
|
||||
"""
|
||||
DISCUSSION = 'DISCUSSION'
|
||||
|
||||
|
||||
class NotificationType(models.TextChoices):
|
||||
"""
|
||||
Notification type choices
|
||||
"""
|
||||
NEW_CONTRIBUTION = 'NEW_CONTRIBUTION'
|
||||
|
||||
|
||||
class NotificationTypeContent:
|
||||
"""
|
||||
Notification type content
|
||||
"""
|
||||
NEW_CONTRIBUTION_NOTIFICATION_CONTENT = 'There is a new contribution. {new_contribution}'
|
||||
|
||||
|
||||
class Notification(TimeStampedModel):
|
||||
"""
|
||||
Model to store notifications for users
|
||||
@@ -87,30 +83,33 @@ class Notification(TimeStampedModel):
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(User, related_name="notifications", on_delete=models.CASCADE)
|
||||
app_name = models.CharField(max_length=64, choices=NotificationApplication.choices, db_index=True)
|
||||
notification_type = models.CharField(max_length=64, choices=NotificationType.choices)
|
||||
content = models.CharField(max_length=1024)
|
||||
course_id = CourseKeyField(max_length=255, null=True, blank=True)
|
||||
app_name = models.CharField(max_length=64, db_index=True)
|
||||
notification_type = models.CharField(max_length=64)
|
||||
content_context = models.JSONField(default=dict)
|
||||
content_url = models.URLField(null=True, blank=True)
|
||||
last_read = models.DateTimeField(null=True, blank=True)
|
||||
last_seen = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} - {self.app_name} - {self.notification_type} - {self.content}'
|
||||
return f'{self.user.username} - {self.course_id} - {self.app_name} - {self.notification_type}'
|
||||
|
||||
|
||||
class NotificationPreference(TimeStampedModel):
|
||||
class CourseNotificationPreference(TimeStampedModel):
|
||||
"""
|
||||
Model to store notification preferences for users
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
user = models.ForeignKey(User, related_name="notification_preferences", on_delete=models.CASCADE)
|
||||
course_id = CourseKeyField(max_length=255, blank=True, default=None)
|
||||
notification_preference_config = models.JSONField(default=get_notification_preference_config)
|
||||
course_id = CourseKeyField(max_length=255, null=False, blank=False)
|
||||
notification_preference_config = models.JSONField(default=get_course_notification_preference_config)
|
||||
# This version indicates the current version of this notification preference.
|
||||
config_version = models.IntegerField(blank=True, default=1)
|
||||
config_version = models.IntegerField(default=get_course_notification_preference_config_version)
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'course_id')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} - {self.course_id} - {self.notification_preference_config}'
|
||||
|
||||
@@ -9,7 +9,7 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi
|
||||
from openedx.core.djangoapps.notifications.models import (
|
||||
get_notification_channels,
|
||||
Notification,
|
||||
NotificationPreference,
|
||||
CourseNotificationPreference,
|
||||
)
|
||||
|
||||
|
||||
@@ -34,14 +34,14 @@ class NotificationCourseEnrollmentSerializer(serializers.ModelSerializer):
|
||||
fields = ('course',)
|
||||
|
||||
|
||||
class UserNotificationPreferenceSerializer(serializers.ModelSerializer):
|
||||
class UserCourseNotificationPreferenceSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for user notification preferences.
|
||||
"""
|
||||
course_name = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = NotificationPreference
|
||||
model = CourseNotificationPreference
|
||||
fields = ('id', 'course_name', 'course_id', 'notification_preference_config',)
|
||||
read_only_fields = ('id', 'course_name', 'course_id',)
|
||||
write_only_fields = ('notification_preference_config',)
|
||||
@@ -136,7 +136,6 @@ class NotificationSerializer(serializers.ModelSerializer):
|
||||
'id',
|
||||
'app_name',
|
||||
'notification_type',
|
||||
'content',
|
||||
'content_context',
|
||||
'content_url',
|
||||
'last_read',
|
||||
|
||||
@@ -14,8 +14,10 @@ from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICATIONS
|
||||
from openedx.core.djangoapps.notifications.models import Notification, NotificationPreference, \
|
||||
get_notification_preference_config
|
||||
from openedx.core.djangoapps.notifications.models import (
|
||||
Notification,
|
||||
CourseNotificationPreference,
|
||||
)
|
||||
from openedx.core.djangoapps.notifications.serializers import NotificationCourseEnrollmentSerializer
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
@@ -33,7 +35,6 @@ class CourseEnrollmentListViewTest(ModuleStoreTestCase):
|
||||
super().setUp()
|
||||
self.client = APIClient()
|
||||
self.user = UserFactory()
|
||||
# self.client.force_authenticate(user=self.user)
|
||||
course_1 = CourseFactory.create(
|
||||
org='testorg',
|
||||
number='testcourse',
|
||||
@@ -123,8 +124,8 @@ class CourseEnrollmentPostSaveTest(ModuleStoreTestCase):
|
||||
created=True
|
||||
)
|
||||
|
||||
# Assert that NotificationPreference object was created with correct attributes
|
||||
notification_preferences = NotificationPreference.objects.all()
|
||||
# Assert that CourseNotificationPreference object was created with correct attributes
|
||||
notification_preferences = CourseNotificationPreference.objects.all()
|
||||
|
||||
self.assertEqual(notification_preferences.count(), 1)
|
||||
self.assertEqual(notification_preferences[0].user, self.user)
|
||||
@@ -136,6 +137,7 @@ class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
|
||||
"""
|
||||
Test for user notification preference API.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
@@ -169,7 +171,35 @@ class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
|
||||
'id': 1,
|
||||
'course_name': 'course-v1:testorg+testcourse+testrun Course',
|
||||
'course_id': 'course-v1:testorg+testcourse+testrun',
|
||||
'notification_preference_config': get_notification_preference_config(),
|
||||
'notification_preference_config': {
|
||||
'discussion': {
|
||||
'enabled': True,
|
||||
'core_notification_types': ['new_comment_on_response'],
|
||||
'notification_types': {
|
||||
'new_comment': {
|
||||
'web': True,
|
||||
'email': True,
|
||||
'push': True,
|
||||
'info': 'Comment on post'
|
||||
},
|
||||
'new_response': {
|
||||
'web': True,
|
||||
'email': True,
|
||||
'push': True,
|
||||
'info': 'Response on post'
|
||||
},
|
||||
'core': {
|
||||
'web': True,
|
||||
'email': True,
|
||||
'push': True,
|
||||
'info': ''
|
||||
}
|
||||
},
|
||||
'non_editable': {
|
||||
'new_comment': ['web', 'email']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def test_get_user_notification_preference_without_login(self):
|
||||
@@ -193,14 +223,14 @@ class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
|
||||
('discussion', None, None, False, status.HTTP_200_OK, 'app_update'),
|
||||
('invalid_notification_app', None, None, True, status.HTTP_400_BAD_REQUEST, None),
|
||||
|
||||
('discussion', 'new_post', 'web', True, status.HTTP_200_OK, 'type_update'),
|
||||
('discussion', 'new_post', 'web', False, status.HTTP_200_OK, 'type_update'),
|
||||
('discussion', 'new_comment', 'web', True, status.HTTP_200_OK, 'type_update'),
|
||||
('discussion', 'new_response', 'web', False, status.HTTP_200_OK, 'type_update'),
|
||||
|
||||
('discussion', 'core', 'email', True, status.HTTP_200_OK, 'type_update'),
|
||||
('discussion', 'core', 'email', False, status.HTTP_200_OK, 'type_update'),
|
||||
|
||||
('discussion', 'invalid_notification_type', 'email', True, status.HTTP_400_BAD_REQUEST, None),
|
||||
('discussion', 'new_post', 'invalid_notification_channel', False, status.HTTP_400_BAD_REQUEST, None),
|
||||
('discussion', 'new_comment', 'invalid_notification_channel', False, status.HTTP_400_BAD_REQUEST, None),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_patch_user_notification_preference(
|
||||
@@ -252,7 +282,6 @@ class NotificationListAPIViewTest(APITestCase):
|
||||
user=self.user,
|
||||
app_name='app1',
|
||||
notification_type='info',
|
||||
content='This is a notification.',
|
||||
)
|
||||
self.client.login(username=self.user.username, password='test')
|
||||
|
||||
@@ -267,7 +296,6 @@ class NotificationListAPIViewTest(APITestCase):
|
||||
self.assertEqual(len(data), 1)
|
||||
self.assertEqual(data[0]['app_name'], 'app1')
|
||||
self.assertEqual(data[0]['notification_type'], 'info')
|
||||
self.assertEqual(data[0]['content'], 'This is a notification.')
|
||||
|
||||
def test_list_notifications_with_app_name_filter(self):
|
||||
"""
|
||||
@@ -278,13 +306,11 @@ class NotificationListAPIViewTest(APITestCase):
|
||||
user=self.user,
|
||||
app_name='app1',
|
||||
notification_type='info',
|
||||
content='This is a notification for app1.',
|
||||
)
|
||||
Notification.objects.create(
|
||||
user=self.user,
|
||||
app_name='app2',
|
||||
notification_type='info',
|
||||
content='This is a notification for app2.',
|
||||
)
|
||||
self.client.login(username=self.user.username, password='test')
|
||||
|
||||
@@ -299,7 +325,6 @@ class NotificationListAPIViewTest(APITestCase):
|
||||
self.assertEqual(len(data), 1)
|
||||
self.assertEqual(data[0]['app_name'], 'app1')
|
||||
self.assertEqual(data[0]['notification_type'], 'info')
|
||||
self.assertEqual(data[0]['content'], 'This is a notification for app1.')
|
||||
|
||||
def test_list_notifications_without_authentication(self):
|
||||
"""
|
||||
@@ -363,6 +388,7 @@ class MarkNotificationsUnseenAPIViewTestCase(APITestCase):
|
||||
"""
|
||||
Tests for the MarkNotificationsUnseenAPIView.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.user = UserFactory()
|
||||
|
||||
|
||||
@@ -12,15 +12,18 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from openedx.core.djangoapps.notifications.models import NotificationPreference, \
|
||||
get_notification_preference_config_version
|
||||
from openedx.core.djangoapps.notifications.models import (
|
||||
CourseNotificationPreference,
|
||||
get_course_notification_preference_config_version
|
||||
)
|
||||
|
||||
from .config.waffle import ENABLE_NOTIFICATIONS
|
||||
from .models import Notification
|
||||
from .serializers import (
|
||||
NotificationCourseEnrollmentSerializer,
|
||||
NotificationSerializer,
|
||||
UserNotificationPreferenceSerializer, UserNotificationPreferenceUpdateSerializer
|
||||
UserCourseNotificationPreferenceSerializer,
|
||||
UserNotificationPreferenceUpdateSerializer
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
@@ -142,12 +145,12 @@ class UserNotificationPreferenceView(APIView):
|
||||
}
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_key_string)
|
||||
user_notification_preference, _ = NotificationPreference.objects.get_or_create(
|
||||
user_notification_preference, _ = CourseNotificationPreference.objects.get_or_create(
|
||||
user=request.user,
|
||||
course_id=course_id,
|
||||
is_active=True,
|
||||
)
|
||||
serializer = UserNotificationPreferenceSerializer(user_notification_preference)
|
||||
serializer = UserCourseNotificationPreferenceSerializer(user_notification_preference)
|
||||
return Response(serializer.data)
|
||||
|
||||
def patch(self, request, course_key_string):
|
||||
@@ -165,23 +168,23 @@ class UserNotificationPreferenceView(APIView):
|
||||
400: Validation error
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_key_string)
|
||||
user_notification_preference = NotificationPreference.objects.get(
|
||||
user_course_notification_preference = CourseNotificationPreference.objects.get(
|
||||
user=request.user,
|
||||
course_id=course_id,
|
||||
is_active=True,
|
||||
)
|
||||
if user_notification_preference.config_version != get_notification_preference_config_version():
|
||||
if user_course_notification_preference.config_version != get_course_notification_preference_config_version():
|
||||
return Response(
|
||||
{'error': 'The notification preference config version is not up to date.'},
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
|
||||
preference_update_serializer = UserNotificationPreferenceUpdateSerializer(
|
||||
user_notification_preference, data=request.data, partial=True
|
||||
user_course_notification_preference, data=request.data, partial=True
|
||||
)
|
||||
preference_update_serializer.is_valid(raise_exception=True)
|
||||
updated_notification_preferences = preference_update_serializer.save()
|
||||
serializer = UserNotificationPreferenceSerializer(updated_notification_preferences)
|
||||
serializer = UserCourseNotificationPreferenceSerializer(updated_notification_preferences)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user