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:
Awais Ansari
2023-06-19 18:47:56 +05:00
committed by GitHub
parent 7a217bf912
commit 8990035116
3 changed files with 161 additions and 3 deletions

View File

@@ -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.'})

View File

@@ -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'),
]

View File

@@ -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)