Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Imports
3from django.conf import settings
4from django.db import models
5from django.utils.timezone import now
6from django.utils.translation import gettext_lazy as _
7import logging
8from superdjango.db.lookups.models import StringLookupModel
9from .constants import DEFAULT_ICONS, LEVEL, STAGE
11log = logging.getLogger(__name__)
13# Exports
15__all__ = (
16 "Category",
17 "Notification",
18 "NotificationUser",
19 "UserPreferences",
20)
22# Constants
24AUTH_USER_MODEL = settings.AUTH_USER_MODEL
26NOTIFICATION_ICONS = getattr(settings, "SUPERDJANGO_NOTIFICATION_ICONS", DEFAULT_ICONS)
27"""Load notification icons from settings. Useful, for example, to include pro icons."""
29# Models
32class Category(StringLookupModel):
33 """Notification categories."""
35 class Meta:
36 ordering = ["label"]
37 verbose_name = _("Category")
38 verbose_name_plural = _("Categories")
41class Notification(models.Model):
42 """A notification sent to a user."""
44 action_text = models.CharField(
45 _("action text"),
46 blank=True,
47 help_text=_('The text to display for the action. Defaults to "More" in templates.'),
48 max_length=128,
49 null=True
50 )
52 action_url = models.CharField(
53 _("action URL"),
54 blank=True,
55 help_text=_("The URL to which users should be directed to take action or get more information."),
56 max_length=1024,
57 null=True
58 )
60 added_dt = models.DateTimeField(
61 _("added date/time"),
62 auto_now_add=True,
63 help_text=_("Date and time the notification was added."),
64 )
66 category = models.ForeignKey(
67 Category,
68 blank=True,
69 help_text=_("The category of the message."),
70 limit_choices_to={'is_enabled': True},
71 null=True,
72 on_delete=models.SET_NULL,
73 related_name="notifications",
74 verbose_name=_("category")
75 )
77 body = models.TextField(
78 _("body"),
79 help_text=_("The body of the message."),
80 )
82 body_sms = models.TextField(
83 _("SMS body"),
84 blank=True,
85 help_text=_("The SMS/text body of the message."),
86 null=True
87 )
89 email_enabled = models.BooleanField(
90 _("email enabled"),
91 default=False,
92 help_text=_("Indicates the message should be sent via email.")
93 )
95 has_been_sent = models.BooleanField(
96 _("sent"),
97 default=False,
98 help_text=_("Indicates the notification has been sent.")
99 )
101 html_enabled = models.BooleanField(
102 _("HTML enabled"),
103 default=False,
104 help_text=_("Indicates HTML email output should be supported.")
105 )
107 icon_override = models.CharField(
108 _("icon override"),
109 blank=True,
110 help_text=_("Override the default icon."),
111 max_length=128,
112 null=True
113 )
115 is_mandatory = models.BooleanField(
116 _("mandatory"),
117 default=False,
118 help_text=_("Indicates the notification should be sent regardless of user preferences.")
119 )
121 # https://www.codexworld.com/create-top-notification-bar-html-css-jquery/
122 is_promoted = models.BooleanField(
123 _("promoted"),
124 default=False,
125 help_text=_("Indicates the notification should be promoted to prominence.")
126 )
128 LEVEL_CHOICES = (
129 (LEVEL.DEBUG, _("Debug")),
130 (LEVEL.INFO, _("Info")),
131 (LEVEL.SUCCESS, _("Success")),
132 (LEVEL.WARNING, _("Warning")),
133 (LEVEL.ERROR, _("Error")),
134 )
135 level = models.PositiveSmallIntegerField(
136 _("level"),
137 choices=LEVEL_CHOICES,
138 default=LEVEL.INFO,
139 help_text=_("The level of the notification.")
140 )
142 sent_dt = models.DateTimeField(
143 _("sent date/time"),
144 blank=True,
145 help_text=_("Date and time the notification was sent."),
146 null=True
147 )
149 sms_enabled = models.BooleanField(
150 _("SMS enabled"),
151 default=False,
152 help_text=_("Indicates the message should be sent via SMS/text.")
153 )
155 STAGE_CHOICES = (
156 (STAGE.DRAFT, _("Draft")),
157 (STAGE.QUEUED, _("Queued")),
158 (STAGE.SENT, _("Sent")),
159 (STAGE.ARCHIVED, _("Archived")),
160 )
161 stage = models.PositiveSmallIntegerField(
162 _("stage"),
163 choices=STAGE_CHOICES,
164 default=STAGE.QUEUED,
165 help_text=_("The current stage of the notification.")
166 )
168 subject = models.CharField(
169 _("subject"),
170 help_text=_("The subject line of the message."),
171 max_length=128
172 )
174 users = models.ManyToManyField(
175 AUTH_USER_MODEL,
176 blank=True,
177 help_text=_("The users to which the notification is sent."),
178 related_name="sd_notifications",
179 verbose_name=_("users")
180 )
182 class Meta:
183 get_latest_by = "added_dt"
184 ordering = ["-added_dt"]
185 verbose_name = _("Notification")
186 verbose_name_plural = _("Notifications")
188 def __str__(self):
189 return self.subject
191 @classmethod
192 def create_for(cls, body, subject, users, action_text=None, action_url=None, body_sms=None, category=None,
193 email_enabled=False, html_enabled=False, icon_override=None, is_mandatory=False, is_promoted=False,
194 level=LEVEL.INFO, sms_enabled=False):
195 """Create a notification for the given users.
197 :param body: Required. The body of the message.
198 :type body: str
200 :param subject: Required. The subject line of the message.
201 :type subject: str
203 :param users: An iterable of users to which the notification should be sent.
205 :param action_text: The action text to display for the URL. Defaults to "More" in templates.
206 :type action_text: str
208 :param action_url: Optional. The action URL.
209 :type action_url: str
211 :param body_sms: Optional. The SMS/text version of the message.
212 :type body_sms: str
214 :param category: The message category.
215 :type category: str | Category
217 :param email_enabled: Indicates email should be sent. Requires additional setup.
218 :type email_enabled: bool
220 :param html_enabled: Indicates the body of the message is HTML.
221 :type html_enabled: bool
223 :param icon_override: Overrides the default icon.
224 :type icon_override: str
226 :param is_mandatory: Indicates the message should be sent regardless of user preferences.
227 :type is_mandatory: bool
229 :param is_promoted: Indicates the message should be displayed with prominence.
230 :type is_promoted: bool
232 :param level: The level of the message.
233 :type level: int
235 :param sms_enabled: Indicates email should be sent. Requires additional setup.
236 :type sms_enabled: bool
238 :rtype: Notification
240 .. warning::
241 This creates a notification that is immediately queued for delivery.
243 """
244 _category = None
245 if category is not None:
246 if isinstance(category, Category):
247 _category = category
248 else:
249 try:
250 _category = Category.objects.get(value=category)
251 except Category.DoesNotExist:
252 log.debug("Notification category does not exist: %s" % category)
254 notification = cls(
255 action_text=action_text,
256 action_url=action_url,
257 body=body,
258 body_sms=body_sms,
259 category=_category,
260 email_enabled=email_enabled,
261 html_enabled=html_enabled,
262 icon_override=icon_override,
263 is_mandatory=is_mandatory,
264 is_promoted=is_promoted,
265 level=level,
266 sms_enabled=sms_enabled,
267 subject=subject
268 )
269 notification.save()
271 for user in users:
272 notification.users.add(user)
274 # for user in users:
275 # link = NotificationUser(
276 # notification=notification,
277 # user=user
278 # )
279 # link.save()
281 return notification
283 def get_icon(self):
284 """Get the Font Awesome icon associated with the level.
286 :rtype: str
288 """
289 if self.icon_override:
290 return self.icon_override
292 return NOTIFICATION_ICONS[self.level]
294 @property
295 def icon(self):
296 """Alias for ``get_icon()``."""
297 return self.get_icon()
299 def mark_sent(self, commit=True):
300 """Mark the notification as sent.
302 :param commit: Save after updating the fields.
303 :type commit: bool
305 """
306 self.has_been_sent = True
307 self.sent_dt = now()
308 self.stage = STAGE.SENT
310 if commit:
311 self.save(update_fields=["has_been_sent", "sent_dt", "stage"])
314class NotificationUser(models.Model):
315 """Track the users that have seen a notification."""
317 has_been_viewed = models.BooleanField(
318 _("viewed"),
319 default=False,
320 help_text=_("Indicates the notification has been viewed.")
321 )
323 notification = models.ForeignKey(
324 Notification,
325 help_text=_("The notification being tracked."),
326 on_delete=models.CASCADE,
327 related_name="user_views",
328 verbose_name=_("notification")
329 )
331 sent_email = models.NullBooleanField(
332 _("email sent"),
333 help_text=_("Indicates whether an email was sent for this notification.")
334 )
336 sent_sms = models.NullBooleanField(
337 _("SMS sent"),
338 help_text=_("Indicates whether a SMS/text was sent for this notification.")
339 )
341 user = models.ForeignKey(
342 AUTH_USER_MODEL,
343 help_text=_("The user to which the notification is sent."),
344 on_delete=models.CASCADE,
345 related_name="notifications",
346 verbose_name=_("user")
347 )
349 viewed_dt = models.DateTimeField(
350 _("viewed date/time"),
351 blank=True,
352 help_text=_("Date and time the notification was viewed."),
353 null=True
354 )
356 class Meta:
357 get_latest_by = "notification__added_dt"
358 ordering = ["-viewed_dt"]
359 unique_together = ["notification", "user"]
360 verbose_name = _("Notification View")
361 verbose_name_plural = _("Notification Views")
363 def __str__(self):
364 return "%s %s" % (self.notification.subject, self.user)
366 @property
367 def action_url(self):
368 """Alias on ``notification``."""
369 return self.notification.action_url
371 @property
372 def added_dt(self):
373 """Alias on ``notification``."""
374 return self.notification.added_dt
376 @property
377 def body(self):
378 """Alias on ``notification``."""
379 return self.notification.body
381 @property
382 def body_sms(self):
383 """Alias on ``notification``."""
384 return self.notification.body_sms
386 @property
387 def email_enabled(self):
388 """Alias on ``notification``."""
389 return self.notification.email_enabled
391 @property
392 def has_been_sent(self):
393 """Alias on ``notification``."""
394 return self.notification.has_been_sent
396 @property
397 def icon(self):
398 """Alias on ``notification``."""
399 return self.notification.get_icon()
401 @property
402 def is_mandatory(self):
403 """Alias on ``notification``."""
404 return self.notification.is_mandatory
406 @property
407 def is_promoted(self):
408 """Alias on ``notification``."""
409 return self.notification.is_promoted
411 @property
412 def level(self):
413 """Alias on ``notification``."""
414 return self.notification.level
416 def get_level_display(self):
417 """Alias on ``notification``."""
418 return self.notification.get_level_display()
420 def mark_viewed(self, commit=True):
421 """Mark the record as viewed.
423 :param commit: Indicates the record should be saved after updating the field.
424 :type commit: bool
426 """
427 self.has_been_viewed = True
428 self.viewed_dt = now()
430 if commit:
431 self.save(update_fields=["has_been_viewed", "viewed_dt"])
433 def mark_unviewed(self, commit=True):
434 """Mark the record as "un-viewed", removing the last viewed data.
436 :param commit: Indicates the record should be saved after updating the field.
437 :type commit: bool
439 """
440 self.has_been_viewed = False
441 self.viewed_dt = None
443 if commit:
444 self.save(update_fields=["has_been_viewed", "viewed_dt"])
446 @property
447 def sent_dt(self):
448 """Alias on ``notification``."""
449 return self.notification.sent_dt
451 @property
452 def sms_enabled(self):
453 """Alias on ``notification``."""
454 return self.notification.sms_enabled
456 @property
457 def stage(self):
458 """Alias on ``notification``."""
459 return self.notification.stage
461 @property
462 def subject(self):
463 """Alias on ``notification``."""
464 return self.notification.subject
467class UserPreferences(models.Model):
468 """User notification preferences."""
470 categories = models.ManyToManyField(
471 Category,
472 blank=True,
473 help_text=_("The category or categories of notifications to be received."),
474 limit_choices_to={'is_enabled': True},
475 related_name="user_preferences",
476 verbose_name=_("categories")
477 )
479 email_enabled = models.BooleanField(
480 _("email enabled"),
481 default=True,
482 help_text=_("Indicates email notifications are enabled. Some mandatory messages may be sent even "
483 "when disabled.")
484 )
486 LEVEL_CHOICES = (
487 (LEVEL.DEBUG, _("Debug")),
488 (LEVEL.INFO, _("Info")),
489 (LEVEL.SUCCESS, _("Success")),
490 (LEVEL.WARNING, _("Warning")),
491 (LEVEL.ERROR, _("Error")),
492 )
493 level = models.PositiveSmallIntegerField(
494 _("level"),
495 choices=LEVEL_CHOICES,
496 default=LEVEL.ERROR,
497 help_text=_("The minimum level of the notifications to receive.")
498 )
500 mobile_number = models.CharField(
501 _("mobile number"),
502 blank=True,
503 help_text=_("The mobile number at which SMS/text notifications may be received. Note: Mobile charges "
504 "may apply."),
505 max_length=128,
506 null=True
507 )
509 sms_enabled = models.BooleanField(
510 _("SMS enabled"),
511 default=False,
512 help_text=_("Indicates messages may be sent via SMS/text.")
513 )
515 user = models.OneToOneField(
516 AUTH_USER_MODEL,
517 help_text=_("The user to which the preferences belong."),
518 on_delete=models.CASCADE,
519 related_name="notification_preferences",
520 verbose_name=_("user")
521 )
523 class Meta:
524 verbose_name = _("User Notification Preferences")
525 verbose_name_plural = _("User Notification Preferences")