Source code for superdjango.contrib.notifications.models

# Imports

from django.conf import settings
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
import logging
from superdjango.db.lookups.models import StringLookupModel
from .constants import DEFAULT_ICONS, LEVEL, STAGE

log = logging.getLogger(__name__)

# Exports

__all__ = (
    "Category",
    "Notification",
    "NotificationUser",
    "UserPreferences",
)

# Constants

AUTH_USER_MODEL = settings.AUTH_USER_MODEL

NOTIFICATION_ICONS = getattr(settings, "SUPERDJANGO_NOTIFICATION_ICONS", DEFAULT_ICONS)
"""Load notification icons from settings. Useful, for example, to include pro icons."""

# Models


[docs]class Category(StringLookupModel): """Notification categories.""" class Meta: ordering = ["label"] verbose_name = _("Category") verbose_name_plural = _("Categories")
[docs]class Notification(models.Model): """A notification sent to a user.""" action_text = models.CharField( _("action text"), blank=True, help_text=_('The text to display for the action. Defaults to "More" in templates.'), max_length=128, null=True ) action_url = models.CharField( _("action URL"), blank=True, help_text=_("The URL to which users should be directed to take action or get more information."), max_length=1024, null=True ) added_dt = models.DateTimeField( _("added date/time"), auto_now_add=True, help_text=_("Date and time the notification was added."), ) category = models.ForeignKey( Category, blank=True, help_text=_("The category of the message."), limit_choices_to={'is_enabled': True}, null=True, on_delete=models.SET_NULL, related_name="notifications", verbose_name=_("category") ) body = models.TextField( _("body"), help_text=_("The body of the message."), ) body_sms = models.TextField( _("SMS body"), blank=True, help_text=_("The SMS/text body of the message."), null=True ) email_enabled = models.BooleanField( _("email enabled"), default=False, help_text=_("Indicates the message should be sent via email.") ) has_been_sent = models.BooleanField( _("sent"), default=False, help_text=_("Indicates the notification has been sent.") ) html_enabled = models.BooleanField( _("HTML enabled"), default=False, help_text=_("Indicates HTML email output should be supported.") ) icon_override = models.CharField( _("icon override"), blank=True, help_text=_("Override the default icon."), max_length=128, null=True ) is_mandatory = models.BooleanField( _("mandatory"), default=False, help_text=_("Indicates the notification should be sent regardless of user preferences.") ) # https://www.codexworld.com/create-top-notification-bar-html-css-jquery/ is_promoted = models.BooleanField( _("promoted"), default=False, help_text=_("Indicates the notification should be promoted to prominence.") ) LEVEL_CHOICES = ( (LEVEL.DEBUG, _("Debug")), (LEVEL.INFO, _("Info")), (LEVEL.SUCCESS, _("Success")), (LEVEL.WARNING, _("Warning")), (LEVEL.ERROR, _("Error")), ) level = models.PositiveSmallIntegerField( _("level"), choices=LEVEL_CHOICES, default=LEVEL.INFO, help_text=_("The level of the notification.") ) sent_dt = models.DateTimeField( _("sent date/time"), blank=True, help_text=_("Date and time the notification was sent."), null=True ) sms_enabled = models.BooleanField( _("SMS enabled"), default=False, help_text=_("Indicates the message should be sent via SMS/text.") ) STAGE_CHOICES = ( (STAGE.DRAFT, _("Draft")), (STAGE.QUEUED, _("Queued")), (STAGE.SENT, _("Sent")), (STAGE.ARCHIVED, _("Archived")), ) stage = models.PositiveSmallIntegerField( _("stage"), choices=STAGE_CHOICES, default=STAGE.QUEUED, help_text=_("The current stage of the notification.") ) subject = models.CharField( _("subject"), help_text=_("The subject line of the message."), max_length=128 ) users = models.ManyToManyField( AUTH_USER_MODEL, blank=True, help_text=_("The users to which the notification is sent."), related_name="sd_notifications", verbose_name=_("users") ) class Meta: get_latest_by = "added_dt" ordering = ["-added_dt"] verbose_name = _("Notification") verbose_name_plural = _("Notifications") def __str__(self): return self.subject
[docs] @classmethod def create_for(cls, body, subject, users, action_text=None, action_url=None, body_sms=None, category=None, email_enabled=False, html_enabled=False, icon_override=None, is_mandatory=False, is_promoted=False, level=LEVEL.INFO, sms_enabled=False): """Create a notification for the given users. :param body: Required. The body of the message. :type body: str :param subject: Required. The subject line of the message. :type subject: str :param users: An iterable of users to which the notification should be sent. :param action_text: The action text to display for the URL. Defaults to "More" in templates. :type action_text: str :param action_url: Optional. The action URL. :type action_url: str :param body_sms: Optional. The SMS/text version of the message. :type body_sms: str :param category: The message category. :type category: str | Category :param email_enabled: Indicates email should be sent. Requires additional setup. :type email_enabled: bool :param html_enabled: Indicates the body of the message is HTML. :type html_enabled: bool :param icon_override: Overrides the default icon. :type icon_override: str :param is_mandatory: Indicates the message should be sent regardless of user preferences. :type is_mandatory: bool :param is_promoted: Indicates the message should be displayed with prominence. :type is_promoted: bool :param level: The level of the message. :type level: int :param sms_enabled: Indicates email should be sent. Requires additional setup. :type sms_enabled: bool :rtype: Notification .. warning:: This creates a notification that is immediately queued for delivery. """ _category = None if category is not None: if isinstance(category, Category): _category = category else: try: _category = Category.objects.get(value=category) except Category.DoesNotExist: log.debug("Notification category does not exist: %s" % category) notification = cls( action_text=action_text, action_url=action_url, body=body, body_sms=body_sms, category=_category, email_enabled=email_enabled, html_enabled=html_enabled, icon_override=icon_override, is_mandatory=is_mandatory, is_promoted=is_promoted, level=level, sms_enabled=sms_enabled, subject=subject ) notification.save() for user in users: notification.users.add(user) # for user in users: # link = NotificationUser( # notification=notification, # user=user # ) # link.save() return notification
[docs] def get_icon(self): """Get the Font Awesome icon associated with the level. :rtype: str """ if self.icon_override: return self.icon_override return NOTIFICATION_ICONS[self.level]
@property def icon(self): """Alias for ``get_icon()``.""" return self.get_icon()
[docs] def mark_sent(self, commit=True): """Mark the notification as sent. :param commit: Save after updating the fields. :type commit: bool """ self.has_been_sent = True self.sent_dt = now() self.stage = STAGE.SENT if commit: self.save(update_fields=["has_been_sent", "sent_dt", "stage"])
[docs]class NotificationUser(models.Model): """Track the users that have seen a notification.""" has_been_viewed = models.BooleanField( _("viewed"), default=False, help_text=_("Indicates the notification has been viewed.") ) notification = models.ForeignKey( Notification, help_text=_("The notification being tracked."), on_delete=models.CASCADE, related_name="user_views", verbose_name=_("notification") ) sent_email = models.NullBooleanField( _("email sent"), help_text=_("Indicates whether an email was sent for this notification.") ) sent_sms = models.NullBooleanField( _("SMS sent"), help_text=_("Indicates whether a SMS/text was sent for this notification.") ) user = models.ForeignKey( AUTH_USER_MODEL, help_text=_("The user to which the notification is sent."), on_delete=models.CASCADE, related_name="notifications", verbose_name=_("user") ) viewed_dt = models.DateTimeField( _("viewed date/time"), blank=True, help_text=_("Date and time the notification was viewed."), null=True ) class Meta: get_latest_by = "notification__added_dt" ordering = ["-viewed_dt"] unique_together = ["notification", "user"] verbose_name = _("Notification View") verbose_name_plural = _("Notification Views") def __str__(self): return "%s %s" % (self.notification.subject, self.user) @property def action_url(self): """Alias on ``notification``.""" return self.notification.action_url @property def added_dt(self): """Alias on ``notification``.""" return self.notification.added_dt @property def body(self): """Alias on ``notification``.""" return self.notification.body @property def body_sms(self): """Alias on ``notification``.""" return self.notification.body_sms @property def email_enabled(self): """Alias on ``notification``.""" return self.notification.email_enabled @property def has_been_sent(self): """Alias on ``notification``.""" return self.notification.has_been_sent @property def icon(self): """Alias on ``notification``.""" return self.notification.get_icon() @property def is_mandatory(self): """Alias on ``notification``.""" return self.notification.is_mandatory @property def is_promoted(self): """Alias on ``notification``.""" return self.notification.is_promoted @property def level(self): """Alias on ``notification``.""" return self.notification.level
[docs] def get_level_display(self): """Alias on ``notification``.""" return self.notification.get_level_display()
[docs] def mark_viewed(self, commit=True): """Mark the record as viewed. :param commit: Indicates the record should be saved after updating the field. :type commit: bool """ self.has_been_viewed = True self.viewed_dt = now() if commit: self.save(update_fields=["has_been_viewed", "viewed_dt"])
[docs] def mark_unviewed(self, commit=True): """Mark the record as "un-viewed", removing the last viewed data. :param commit: Indicates the record should be saved after updating the field. :type commit: bool """ self.has_been_viewed = False self.viewed_dt = None if commit: self.save(update_fields=["has_been_viewed", "viewed_dt"])
@property def sent_dt(self): """Alias on ``notification``.""" return self.notification.sent_dt @property def sms_enabled(self): """Alias on ``notification``.""" return self.notification.sms_enabled @property def stage(self): """Alias on ``notification``.""" return self.notification.stage @property def subject(self): """Alias on ``notification``.""" return self.notification.subject
[docs]class UserPreferences(models.Model): """User notification preferences.""" categories = models.ManyToManyField( Category, blank=True, help_text=_("The category or categories of notifications to be received."), limit_choices_to={'is_enabled': True}, related_name="user_preferences", verbose_name=_("categories") ) email_enabled = models.BooleanField( _("email enabled"), default=True, help_text=_("Indicates email notifications are enabled. Some mandatory messages may be sent even " "when disabled.") ) LEVEL_CHOICES = ( (LEVEL.DEBUG, _("Debug")), (LEVEL.INFO, _("Info")), (LEVEL.SUCCESS, _("Success")), (LEVEL.WARNING, _("Warning")), (LEVEL.ERROR, _("Error")), ) level = models.PositiveSmallIntegerField( _("level"), choices=LEVEL_CHOICES, default=LEVEL.ERROR, help_text=_("The minimum level of the notifications to receive.") ) mobile_number = models.CharField( _("mobile number"), blank=True, help_text=_("The mobile number at which SMS/text notifications may be received. Note: Mobile charges " "may apply."), max_length=128, null=True ) sms_enabled = models.BooleanField( _("SMS enabled"), default=False, help_text=_("Indicates messages may be sent via SMS/text.") ) user = models.OneToOneField( AUTH_USER_MODEL, help_text=_("The user to which the preferences belong."), on_delete=models.CASCADE, related_name="notification_preferences", verbose_name=_("user") ) class Meta: verbose_name = _("User Notification Preferences") verbose_name_plural = _("User Notification Preferences")