Files
edx-platform/openedx/core/djangoapps/notifications/models.py
2026-01-02 17:48:01 +05:00

192 lines
6.7 KiB
Python

"""
Models for notifications
"""
import logging
from django.contrib.auth import get_user_model
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 (
get_notification_content,
COURSE_NOTIFICATION_APPS,
COURSE_NOTIFICATION_TYPES
)
from openedx.core.djangoapps.notifications.email_notifications import EmailCadence
User = get_user_model()
log = logging.getLogger(__name__)
NOTIFICATION_CHANNELS = ['web', 'push', 'email']
ADDITIONAL_NOTIFICATION_CHANNEL_SETTINGS = ['email_cadence']
def get_notification_channels():
"""
Returns the notification channels.
"""
return NOTIFICATION_CHANNELS
def get_additional_notification_channel_settings():
"""
Returns the additional notification channel settings.
"""
return ADDITIONAL_NOTIFICATION_CHANNEL_SETTINGS
def create_notification_preference(user_id: int, notification_type: str):
"""
Create a single notification preference with appropriate defaults.
Args:
user_id: ID of the user
notification_type: Type of notification
Returns:
NotificationPreference instance
"""
notification_config = COURSE_NOTIFICATION_TYPES.get(notification_type, {})
is_core = notification_config.get('is_core', False)
app = notification_config['notification_app']
kwargs = {
"web": notification_config.get('web', True),
"push": notification_config.get('push', False),
"email": notification_config.get('email', False),
"email_cadence": notification_config.get('email_cadence', EmailCadence.DAILY),
}
if is_core:
app_config = COURSE_NOTIFICATION_APPS[app]
kwargs = {
"web": app_config.get("core_web", True),
"push": app_config.get("core_push", False),
"email": app_config.get("core_email", False),
"email_cadence": app_config.get("core_email_cadence", EmailCadence.DAILY),
}
return NotificationPreference(
user_id=user_id,
type=notification_type,
app=app,
**kwargs,
)
class Notification(TimeStampedModel):
"""
Model to store notifications for users
.. no_pii:
"""
user = models.ForeignKey(User, related_name="notifications", on_delete=models.CASCADE)
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)
web = models.BooleanField(default=True, null=False, blank=False)
email = models.BooleanField(default=False, null=False, blank=False)
push = models.BooleanField(default=False, null=False, blank=False)
last_read = models.DateTimeField(null=True, blank=True)
last_seen = models.DateTimeField(null=True, blank=True)
group_by_id = models.CharField(max_length=255, db_index=True, null=False, default="")
email_sent_on = models.DateTimeField(null=True, blank=True)
email_scheduled = models.BooleanField(
default=False,
db_index=True,
help_text="True if this notification is waiting in buffer for digest"
)
class Meta:
indexes = [
models.Index(
fields=['user', 'course_id', 'email_sent_on', 'email_scheduled'],
name='notif_email_buffer_idx'
),
]
def __str__(self):
return f'{self.user.username} - {self.course_id} - {self.app_name} - {self.notification_type}'
@property
def content(self):
"""
Returns the content for the notification.
"""
return get_notification_content(self.notification_type, self.content_context)
class NotificationPreference(TimeStampedModel):
"""
Model to store notification preferences for users at account level
"""
class EmailCadenceChoices(models.TextChoices):
DAILY = 'Daily'
WEEKLY = 'Weekly'
IMMEDIATELY = 'Immediately'
class Meta:
# Ensures user do not have duplicate preferences.
unique_together = ('user', 'app', 'type',)
user = models.ForeignKey(User, related_name="notification_preference", on_delete=models.CASCADE)
type = models.CharField(max_length=128, db_index=True)
app = models.CharField(max_length=128, null=False, blank=False, db_index=True)
web = models.BooleanField(default=True, null=False, blank=False)
push = models.BooleanField(default=False, null=False, blank=False)
email = models.BooleanField(default=False, null=False, blank=False)
email_cadence = models.CharField(max_length=64, choices=EmailCadenceChoices.choices, null=False, blank=False)
is_active = models.BooleanField(default=True)
def __str__(self):
return f"{self.user_id} {self.type} (Web:{self.web}) (Push:{self.push})" \
f"(Email:{self.email}, {self.email_cadence})"
@classmethod
def create_default_preferences_for_user(cls, user_id) -> list:
"""
Creates all preferences for user
Note: It creates preferences using bulk create, so primary key will be missing for newly created
preference. Refetch if primary key is needed
"""
preferences = list(NotificationPreference.objects.filter(user_id=user_id))
user_preferences_map = {pref.type: pref for pref in preferences}
diff = set(COURSE_NOTIFICATION_TYPES.keys()) - set(user_preferences_map.keys())
if diff:
missing_types = [
create_notification_preference(user_id=user_id, notification_type=missing_type)
for missing_type in diff
]
new_preferences = NotificationPreference.objects.bulk_create(missing_types)
preferences = preferences + list(new_preferences)
return preferences
def is_enabled_for_any_channel(self, *args, **kwargs) -> bool:
"""
Returns True if the notification preference is enabled for any channel.
"""
return self.web or self.push or self.email
def get_channels_for_notification_type(self, *args, **kwargs) -> list:
"""
Returns the channels for the given app name and notification type.
Sample Response:
['web', 'push']
"""
channels = []
if self.web:
channels.append('web')
if self.push:
channels.append('push')
if self.email:
channels.append('email')
return channels
def get_email_cadence_for_notification_type(self, *args, **kwargs) -> str:
"""
Returns the email cadence for the notification type.
"""
return self.email_cadence