Coverage for baibu/notifications/models.py: 100%

39 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-10-06 13:07 +0000

1import uuid 

2 

3from django.conf import settings 

4from django.db import models 

5from django.utils.translation import gettext_lazy as _ 

6 

7 

8class Notification(models.Model): 

9 """A message for one user, shown in their in-app inbox.""" 

10 

11 id = models.UUIDField(primary_key=True, default=uuid.uuid7, editable=False) 

12 user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="notifications") 

13 # What it is about, e.g. "submission_accepted". Registered kinds (see 

14 # kinds.py) are translated when shown, using ``params``; other kinds show 

15 # the stored title and body. 

16 kind = models.CharField(max_length=64) 

17 params = models.JSONField(default=dict, blank=True) 

18 title = models.CharField(max_length=200, blank=True) 

19 body = models.TextField(blank=True) 

20 # A path inside the platform to open, e.g. /contribute/. 

21 link = models.CharField(max_length=500, blank=True) 

22 created_at = models.DateTimeField(auto_now_add=True) 

23 read_at = models.DateTimeField(null=True, blank=True) 

24 

25 class Meta: 

26 ordering = ["-created_at"] 

27 indexes = [models.Index(fields=["user", "read_at"], name="notification_user_unread")] 

28 verbose_name = _("Notification") 

29 verbose_name_plural = _("Notifications") 

30 

31 def __str__(self) -> str: 

32 return str(self.display_title) 

33 

34 def _render(self, text) -> str: 

35 try: 

36 return str(text) % self.params if self.params else str(text) 

37 except (KeyError, TypeError, ValueError): 

38 return str(text) 

39 

40 @property 

41 def display_title(self) -> str: 

42 from .kinds import KINDS 

43 

44 kind = KINDS.get(self.kind) 

45 return self._render(kind.title) if kind else self.title 

46 

47 @property 

48 def display_body(self) -> str: 

49 from .kinds import KINDS 

50 

51 kind = KINDS.get(self.kind) 

52 return self._render(kind.body) if kind else self.body 

53 

54 @property 

55 def is_read(self) -> bool: 

56 return self.read_at is not None