feat: add notifications mark as read API (#32475)
* feat: add notifications mark as read API * chore: update description for mark as read notification URL * refactor: resolve pylint issue * refactor: notifications mark as read API and test cases * feat: add translated messages in notificationsAPI response
This commit is contained in:
@@ -25,6 +25,8 @@ from openedx.core.djangoapps.notifications.serializers import NotificationCourse
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from ..base_notification import COURSE_NOTIFICATION_APPS
|
||||
|
||||
|
||||
class CourseEnrollmentListViewTest(ModuleStoreTestCase):
|
||||
"""
|
||||
@@ -501,3 +503,103 @@ class MarkNotificationsUnseenAPIViewTestCase(APITestCase):
|
||||
# Assert the notifications for 'App Name 1' are marked as unseen for the user
|
||||
notifications = Notification.objects.filter(user=self.user, app_name=app_name, last_seen__isnull=False)
|
||||
self.assertEqual(notifications.count(), 2)
|
||||
|
||||
|
||||
class NotificationReadAPIViewTestCase(APITestCase):
|
||||
"""
|
||||
Tests for the NotificationReadAPIView.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.user = UserFactory()
|
||||
self.url = reverse('notifications-read')
|
||||
self.client.login(username=self.user.username, password='test')
|
||||
|
||||
# Create some sample notifications for the user with already existing apps and with invalid app name
|
||||
Notification.objects.create(user=self.user, app_name='app_name_2', notification_type='Type A')
|
||||
for app_name in COURSE_NOTIFICATION_APPS:
|
||||
Notification.objects.create(user=self.user, app_name=app_name, notification_type='Type A')
|
||||
Notification.objects.create(user=self.user, app_name=app_name, notification_type='Type B')
|
||||
|
||||
def test_mark_all_notifications_read_with_app_name(self):
|
||||
# Create a PATCH request to mark all notifications as read for already existing app e.g 'discussion'
|
||||
app_name = next(iter(COURSE_NOTIFICATION_APPS))
|
||||
data = {'app_name': app_name}
|
||||
|
||||
response = self.client.patch(self.url, data)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data, {'message': 'Notifications marked read.'})
|
||||
notifications = Notification.objects.filter(user=self.user, app_name=app_name, last_read__isnull=False)
|
||||
self.assertEqual(notifications.count(), 2)
|
||||
|
||||
def test_mark_all_notifications_read_with_invalid_app_name(self):
|
||||
# Create a PATCH request to mark all notifications as read for 'app_name_1'
|
||||
app_name = 'app_name_1'
|
||||
data = {'app_name': app_name}
|
||||
|
||||
response = self.client.patch(self.url, data)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(response.data, {'error': 'Invalid app_name or notification_id.'})
|
||||
|
||||
def test_mark_notification_read_with_notification_id(self):
|
||||
# Create a PATCH request to mark notification as read for notification_id: 2
|
||||
notification_id = 2
|
||||
data = {'notification_id': notification_id}
|
||||
|
||||
response = self.client.patch(self.url, data)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data, {'message': 'Notification marked read.'})
|
||||
notifications = Notification.objects.filter(user=self.user, id=notification_id, last_read__isnull=False)
|
||||
self.assertEqual(notifications.count(), 1)
|
||||
|
||||
def test_mark_notification_read_with_other_user_notification_id(self):
|
||||
# Create a PATCH request to mark notification as read for notification_id: 2 through a different user
|
||||
self.client.logout()
|
||||
self.user = UserFactory()
|
||||
self.client.login(username=self.user.username, password='test')
|
||||
|
||||
notification_id = 2
|
||||
data = {'notification_id': notification_id}
|
||||
response = self.client.patch(self.url, data)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
notifications = Notification.objects.filter(user=self.user, id=notification_id, last_read__isnull=False)
|
||||
self.assertEqual(notifications.count(), 0)
|
||||
|
||||
def test_mark_notification_read_with_invalid_notification_id(self):
|
||||
# Create a PATCH request to mark notification as read for notification_id: 23345
|
||||
notification_id = 23345
|
||||
data = {'notification_id': notification_id}
|
||||
|
||||
response = self.client.patch(self.url, data)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
self.assertEqual(response.data["detail"], 'Not found.')
|
||||
|
||||
def test_mark_notification_read_with_app_name_and_notification_id(self):
|
||||
# Create a PATCH request to mark notification as read for existing app e.g 'discussion' and notification_id: 2
|
||||
# notification_id has higher priority than app_name in this case app_name is ignored
|
||||
app_name = next(iter(COURSE_NOTIFICATION_APPS))
|
||||
notification_id = 2
|
||||
data = {'app_name': app_name, 'notification_id': notification_id}
|
||||
|
||||
response = self.client.patch(self.url, data)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data, {'message': 'Notification marked read.'})
|
||||
notifications = Notification.objects.filter(
|
||||
user=self.user,
|
||||
id=notification_id,
|
||||
last_read__isnull=False
|
||||
)
|
||||
self.assertEqual(notifications.count(), 1)
|
||||
|
||||
def test_mark_notification_read_without_app_name_and_notification_id(self):
|
||||
# Create a PATCH request to mark notification as read without app_name and notification_id
|
||||
response = self.client.patch(self.url, {})
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(response.data, {'error': 'Invalid app_name or notification_id.'})
|
||||
|
||||
@@ -10,6 +10,7 @@ from .views import (
|
||||
MarkNotificationsUnseenAPIView,
|
||||
NotificationCountView,
|
||||
NotificationListAPIView,
|
||||
NotificationReadAPIView,
|
||||
UserNotificationPreferenceView
|
||||
)
|
||||
|
||||
@@ -30,6 +31,7 @@ urlpatterns = [
|
||||
MarkNotificationsUnseenAPIView.as_view(),
|
||||
name='mark-notifications-unseen'
|
||||
),
|
||||
path('read/', NotificationReadAPIView.as_view(), name='notifications-read'),
|
||||
|
||||
]
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ from datetime import datetime, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import gettext as _
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from pytz import UTC
|
||||
from rest_framework import generics, permissions, status
|
||||
@@ -18,6 +20,7 @@ from openedx.core.djangoapps.notifications.models import (
|
||||
get_course_notification_preference_config_version
|
||||
)
|
||||
|
||||
from .base_notification import COURSE_NOTIFICATION_APPS
|
||||
from .config.waffle import ENABLE_NOTIFICATIONS, SHOW_NOTIFICATIONS_TRAY
|
||||
from .models import Notification
|
||||
from .serializers import (
|
||||
@@ -174,7 +177,7 @@ class UserNotificationPreferenceView(APIView):
|
||||
)
|
||||
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.'},
|
||||
{'error': _('The notification preference config version is not up to date.')},
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
|
||||
@@ -323,7 +326,7 @@ class MarkNotificationsUnseenAPIView(UpdateAPIView):
|
||||
app_name = self.kwargs.get('app_name')
|
||||
|
||||
if not app_name:
|
||||
return Response({'message': 'Invalid app name.'}, status=400)
|
||||
return Response({'error': _('Invalid app name.')}, status=400)
|
||||
|
||||
notifications = Notification.objects.filter(
|
||||
user=request.user,
|
||||
@@ -333,4 +336,55 @@ class MarkNotificationsUnseenAPIView(UpdateAPIView):
|
||||
|
||||
notifications.update(last_seen=datetime.now())
|
||||
|
||||
return Response({'message': 'Notifications marked unseen.'}, status=200)
|
||||
return Response({'message': _('Notifications marked unseen.')}, status=200)
|
||||
|
||||
|
||||
class NotificationReadAPIView(APIView):
|
||||
"""
|
||||
API view for marking user notifications as read, either all notifications or a single notification
|
||||
"""
|
||||
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
"""
|
||||
Marks all notifications or single notification read for the given
|
||||
app name or notification id for the authenticated user.
|
||||
|
||||
Requests:
|
||||
PATCH /api/notifications/read/
|
||||
|
||||
Parameters:
|
||||
request (Request): The request object containing the app name or notification id.
|
||||
{
|
||||
"app_name": (str) app_name,
|
||||
"notification_id": (int) notification_id
|
||||
}
|
||||
|
||||
Returns:
|
||||
- 200: OK status code if the notification or notifications were successfully marked read.
|
||||
- 400: Bad Request status code if the app name is invalid.
|
||||
- 403: Forbidden status code if the user is not authenticated.
|
||||
- 404: Not Found status code if the notification was not found.
|
||||
"""
|
||||
notification_id = request.data.get('notification_id', None)
|
||||
read_at = datetime.now(UTC)
|
||||
|
||||
if notification_id:
|
||||
notification = get_object_or_404(Notification, pk=notification_id, user=request.user)
|
||||
notification.last_read = read_at
|
||||
notification.save()
|
||||
return Response({'message': _('Notification marked read.')}, status=status.HTTP_200_OK)
|
||||
|
||||
app_name = request.data.get('app_name', '')
|
||||
|
||||
if app_name and app_name in COURSE_NOTIFICATION_APPS:
|
||||
notifications = Notification.objects.filter(
|
||||
user=request.user,
|
||||
app_name=app_name,
|
||||
last_read__isnull=True,
|
||||
)
|
||||
notifications.update(last_read=read_at)
|
||||
return Response({'message': _('Notifications marked read.')}, status=status.HTTP_200_OK)
|
||||
|
||||
return Response({'error': _('Invalid app_name or notification_id.')}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
Reference in New Issue
Block a user