-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added notification grouping (#35368)
Co-authored-by: Ahtisham Shahid <ahtisham300@gmail.com>
- Loading branch information
1 parent
6c3d8fa
commit 38cddab
Showing
10 changed files
with
427 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
openedx/core/djangoapps/notifications/grouping_notifications.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
""" | ||
Notification grouping utilities for notifications | ||
""" | ||
import datetime | ||
from typing import Dict, Type, Union | ||
|
||
from pytz import utc | ||
|
||
from abc import ABC, abstractmethod | ||
|
||
from openedx.core.djangoapps.notifications.models import Notification | ||
|
||
|
||
class BaseNotificationGrouper(ABC): | ||
""" | ||
Base class for notification groupers. | ||
""" | ||
|
||
@abstractmethod | ||
def group(self, new_notification, old_notification): | ||
pass | ||
|
||
|
||
class NotificationRegistry: | ||
""" | ||
Registry for notification groupers. | ||
""" | ||
_groupers: Dict[str, Type[BaseNotificationGrouper]] = {} | ||
|
||
@classmethod | ||
def register(cls, notification_type: str): | ||
""" | ||
Registers a notification grouper for the given notification type. | ||
Args | ||
notification_type: The type of notification for which to register the grouper. | ||
Returns: | ||
A decorator that registers the grouper class for the given notification type. | ||
""" | ||
|
||
def decorator(grouper_class: Type[BaseNotificationGrouper]) -> Type[BaseNotificationGrouper]: | ||
""" | ||
Registers the grouper class for the given notification type. | ||
""" | ||
cls._groupers[notification_type] = grouper_class | ||
return grouper_class | ||
|
||
return decorator | ||
|
||
@classmethod | ||
def get_grouper(cls, notification_type: str) -> Union[BaseNotificationGrouper, None]: | ||
"""Retrieves the appropriate notification grouper based on the given notification type. | ||
Args: | ||
notification_type: The type of notification for which to retrieve the grouper. | ||
Returns: | ||
The corresponding BaseNotificationGrouper instance or None if no grouper is found. | ||
""" | ||
grouper_class = cls._groupers.get(notification_type) | ||
if not grouper_class: | ||
return None | ||
return grouper_class() | ||
|
||
|
||
@NotificationRegistry.register('new_comment') | ||
class NewCommentGrouper(BaseNotificationGrouper): | ||
""" | ||
Groups new comment notifications based on the replier name. | ||
""" | ||
|
||
def group(self, new_notification, old_notification): | ||
""" | ||
Groups new comment notifications based on the replier name. | ||
""" | ||
context = old_notification.content_context.copy() | ||
if not context.get('grouped'): | ||
context['replier_name_list'] = [context['replier_name']] | ||
context['grouped_count'] = 1 | ||
context['grouped'] = True | ||
context['replier_name_list'].append(new_notification.content_context['replier_name']) | ||
context['grouped_count'] += 1 | ||
return context | ||
|
||
|
||
def group_user_notifications(new_notification: Notification, old_notification: Notification): | ||
""" | ||
Groups user notification based on notification type and group_id | ||
""" | ||
notification_type = new_notification.notification_type | ||
grouper_class = NotificationRegistry.get_grouper(notification_type) | ||
|
||
if grouper_class: | ||
old_notification.content_context = grouper_class.group(new_notification, old_notification) | ||
old_notification.content_context['grouped'] = True | ||
old_notification.web = old_notification.web or new_notification.web | ||
old_notification.email = old_notification.email or new_notification.email | ||
old_notification.last_read = None | ||
old_notification.last_seen = None | ||
old_notification.created = utc.localize(datetime.datetime.now()) | ||
old_notification.save() | ||
|
||
|
||
def get_user_existing_notifications(user_ids, notification_type, group_by_id, course_id): | ||
""" | ||
Returns user last group able notification | ||
""" | ||
notifications = Notification.objects.filter( | ||
user__in=user_ids, | ||
notification_type=notification_type, | ||
group_by_id=group_by_id, | ||
course_id=course_id | ||
) | ||
notifications_mapping = {user_id: [] for user_id in user_ids} | ||
for notification in notifications: | ||
notifications_mapping[notification.user_id].append(notification) | ||
|
||
for user_id, notifications in notifications_mapping.items(): | ||
notifications.sort(key=lambda elem: elem.created) | ||
notifications_mapping[user_id] = notifications[0] if notifications else None | ||
return notifications_mapping |
36 changes: 24 additions & 12 deletions
36
openedx/core/djangoapps/notifications/notification_content.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,47 @@ | ||
""" | ||
Helper functions for overriding notification content for given notification type. | ||
""" | ||
from typing import Dict | ||
|
||
|
||
def get_notification_type_content_function(notification_type): | ||
def get_notification_type_context_function(notification_type) -> callable: | ||
""" | ||
Returns the content function for the given notification if it exists. | ||
Returns: | ||
callable : The function that returns the context for the given notification type. | ||
""" | ||
try: | ||
return globals()[f"get_{notification_type}_notification_content"] | ||
return globals()[f"get_{notification_type}_notification_context"] | ||
except KeyError: | ||
return None | ||
return lambda context: context | ||
|
||
|
||
def get_notification_content_with_author_pronoun(notification_type, context): | ||
def get_notification_context_with_author_pronoun(context: Dict) -> Dict: | ||
""" | ||
Helper function to get notification content with author's pronoun. | ||
Returns the context for the given notification type with the author pronoun. | ||
""" | ||
html_tags_context = { | ||
'strong': 'strong', | ||
'p': 'p', | ||
} | ||
notification_type_content_template = notification_type.get('content_template', None) | ||
context.update(html_tags_context) | ||
if 'author_pronoun' in context: | ||
context['author_name'] = context['author_pronoun'] | ||
if notification_type_content_template: | ||
return notification_type_content_template.format(**context, **html_tags_context) | ||
return '' | ||
return context | ||
|
||
|
||
# Returns notification content for the new_comment notification. | ||
get_new_comment_notification_content = get_notification_content_with_author_pronoun | ||
def get_new_comment_notification_context(context): | ||
""" | ||
Returns the context for the new_comment notification | ||
""" | ||
if not context.get('grouped'): | ||
return get_notification_context_with_author_pronoun(context) | ||
num_repliers = context['grouped_count'] | ||
repliers_string = f"{num_repliers - 1} other{'s' if num_repliers > 2 else ''}" | ||
context['replier_name'] = f"{context['replier_name_list'][0]} and {repliers_string}" | ||
return context | ||
|
||
|
||
# Returns notification content for the comment_on_followed_post notification. | ||
get_comment_on_followed_post_notification_content = get_notification_content_with_author_pronoun | ||
get_comment_on_followed_post_notification_context = get_notification_context_with_author_pronoun |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.