feat: add api for user notification preferences
This commit is contained in:
@@ -5,6 +5,7 @@ from rest_framework import serializers
|
||||
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.notifications.models import NotificationPreference
|
||||
|
||||
|
||||
class CourseOverviewSerializer(serializers.ModelSerializer):
|
||||
@@ -26,3 +27,28 @@ class NotificationCourseEnrollmentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = CourseEnrollment
|
||||
fields = ('course', 'is_active', 'mode')
|
||||
|
||||
|
||||
class UserNotificationPreferenceSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for user notification preferences.
|
||||
"""
|
||||
course_name = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = NotificationPreference
|
||||
fields = ('id', 'course_name', 'course_id', 'notification_preference_config',)
|
||||
read_only_fields = ('id', 'course_name', 'course_id',)
|
||||
write_only_fields = ('notification_preference_config',)
|
||||
|
||||
def get_course_name(self, obj):
|
||||
"""
|
||||
Returns course name from course id.
|
||||
"""
|
||||
return CourseOverview.get_from_id(obj.course_id).display_name
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
for key, val in validated_data.items():
|
||||
setattr(instance, key, val)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""
|
||||
Tests for the views in the notifications app.
|
||||
"""
|
||||
import json
|
||||
|
||||
from django.dispatch import Signal
|
||||
from django.urls import reverse
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
@@ -123,3 +125,95 @@ class CourseEnrollmentPostSaveTest(ModuleStoreTestCase):
|
||||
|
||||
self.assertEqual(notification_preferences.count(), 1)
|
||||
self.assertEqual(notification_preferences[0].user, self.user)
|
||||
|
||||
|
||||
@override_waffle_flag(ENABLE_NOTIFICATIONS, active=True)
|
||||
class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
|
||||
"""
|
||||
Test for user notification preference API.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
self.course = CourseFactory.create(
|
||||
org='testorg',
|
||||
number='testcourse',
|
||||
run='testrun'
|
||||
)
|
||||
|
||||
course_overview = CourseOverviewFactory.create(id=self.course.id, org='AwesomeOrg')
|
||||
self.course_enrollment = CourseEnrollment.objects.create(
|
||||
user=self.user,
|
||||
course=course_overview,
|
||||
is_active=True,
|
||||
mode='audit'
|
||||
)
|
||||
self.post_save_signal = Signal()
|
||||
self.client = APIClient()
|
||||
self.path = reverse('notification-preferences', kwargs={'course_key_string': self.course.id})
|
||||
self.post_save_signal.send(
|
||||
sender=self.course_enrollment.__class__,
|
||||
instance=self.course_enrollment,
|
||||
created=True
|
||||
)
|
||||
|
||||
def _expected_api_response(self, overrides=None):
|
||||
"""
|
||||
Helper method to return expected API response.
|
||||
"""
|
||||
expected_response = {
|
||||
'id': 1,
|
||||
'course_name': 'course-v1:testorg+testcourse+testrun Course',
|
||||
'course_id': 'course-v1:testorg+testcourse+testrun',
|
||||
'notification_preference_config': {
|
||||
'discussion': {
|
||||
'new_post': {
|
||||
'web': False,
|
||||
'push': False,
|
||||
'email': False
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if overrides:
|
||||
expected_response.update(overrides)
|
||||
return expected_response
|
||||
|
||||
def test_get_user_notification_preference_without_login(self):
|
||||
"""
|
||||
Test get user notification preference without login.
|
||||
"""
|
||||
response = self.client.get(self.path)
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_get_user_notification_preference(self):
|
||||
"""
|
||||
Test get user notification preference.
|
||||
"""
|
||||
self.client.login(username=self.user.username, password='test')
|
||||
response = self.client.get(self.path)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data, self._expected_api_response())
|
||||
|
||||
def test_patch_user_notification_preference(self):
|
||||
"""
|
||||
Test update of user notification preference.
|
||||
"""
|
||||
self.client.login(username=self.user.username, password='test')
|
||||
updated_notification_config_data = {
|
||||
"notification_preference_config": {
|
||||
"discussion": {
|
||||
"new_post": {
|
||||
"web": True,
|
||||
"push": False,
|
||||
"email": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
response = self.client.patch(
|
||||
self.path, json.dumps(updated_notification_config_data), content_type='application/json'
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
expected_data = self._expected_api_response(overrides=updated_notification_config_data)
|
||||
self.assertEqual(response.data, expected_data)
|
||||
|
||||
@@ -2,14 +2,21 @@
|
||||
URLs for the notifications API.
|
||||
"""
|
||||
from django.urls import path
|
||||
from django.urls import re_path
|
||||
from django.conf import settings
|
||||
from rest_framework import routers
|
||||
|
||||
from .views import CourseEnrollmentListView
|
||||
from .views import CourseEnrollmentListView, UserNotificationPreferenceView
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('enrollments/', CourseEnrollmentListView.as_view(), name='enrollment-list'),
|
||||
re_path(
|
||||
fr'^configurations/{settings.COURSE_KEY_PATTERN}$',
|
||||
UserNotificationPreferenceView.as_view(),
|
||||
name='notification-preferences'
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
"""
|
||||
Views for the notifications API.
|
||||
"""
|
||||
from rest_framework import generics, permissions
|
||||
from django.contrib.auth import get_user_model
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from rest_framework import generics, permissions, status
|
||||
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
|
||||
|
||||
from .serializers import NotificationCourseEnrollmentSerializer
|
||||
from .serializers import NotificationCourseEnrollmentSerializer, UserNotificationPreferenceSerializer
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class CourseEnrollmentListView(generics.ListAPIView):
|
||||
@@ -36,3 +43,90 @@ class CourseEnrollmentListView(generics.ListAPIView):
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
return CourseEnrollment.objects.filter(user=user, is_active=True)
|
||||
|
||||
|
||||
class UserNotificationPreferenceView(APIView):
|
||||
"""
|
||||
Supports retrieving and patching the UserNotificationPreference
|
||||
model.
|
||||
|
||||
**Example Requests**
|
||||
GET /api/notifications/configurations/{course_id}
|
||||
PATCH /api/notifications/configurations/{course_id}
|
||||
|
||||
**Example Response**:
|
||||
{
|
||||
'id': 1,
|
||||
'course_name': 'testcourse',
|
||||
'course_id': 'course-v1:testorg+testcourse+testrun',
|
||||
'notification_preference_config': {
|
||||
'discussion': {
|
||||
'new_post': {
|
||||
'web': False,
|
||||
'push': False,
|
||||
'email': False,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
def get(self, request, course_key_string):
|
||||
"""
|
||||
Returns notification preference for user for a course.
|
||||
|
||||
Parameters:
|
||||
request (Request): The request object.
|
||||
course_key_string (int): The ID of the course to retrieve notification preference.
|
||||
|
||||
Returns:
|
||||
{
|
||||
'id': 1,
|
||||
'course_name': 'testcourse',
|
||||
'course_id': 'course-v1:testorg+testcourse+testrun',
|
||||
'notification_preference_config': {
|
||||
'discussion': {
|
||||
'new_post': {
|
||||
'web': False,
|
||||
'push': False,
|
||||
'email': False,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_key_string)
|
||||
user_notification_preference, _ = NotificationPreference.objects.get_or_create(
|
||||
user=request.user,
|
||||
course_id=course_id,
|
||||
is_active=True,
|
||||
)
|
||||
serializer = UserNotificationPreferenceSerializer(user_notification_preference)
|
||||
return Response(serializer.data)
|
||||
|
||||
def patch(self, request, course_key_string):
|
||||
"""
|
||||
Update an existing user notification preference with the data in the request body.
|
||||
|
||||
Parameters:
|
||||
request (Request): The request object
|
||||
course_key_string (int): The ID of the course of the notification preference to be updated.
|
||||
|
||||
Returns:
|
||||
200: The updated preference, serialized using the UserNotificationPreferenceSerializer
|
||||
404: If the preference does not exist
|
||||
403: If the user does not have permission to update the preference
|
||||
400: Validation error
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_key_string)
|
||||
user_notification_preference = NotificationPreference.objects.get(
|
||||
user=request.user,
|
||||
course_id=course_id,
|
||||
is_active=True,
|
||||
)
|
||||
serializer = UserNotificationPreferenceSerializer(user_notification_preference, data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
Reference in New Issue
Block a user