Refactoring the AbstractNotification model and its tests

This commit is contained in:
Alvaro Leonel 2024-04-28 17:11:17 -03:00
parent ec236d59bc
commit 78faf1abaa
4 changed files with 132 additions and 60 deletions

View file

@ -1,3 +1,4 @@
from django.core.exceptions import ImproperlyConfigured
from django.forms import model_to_dict
from notifications.settings import notification_settings
@ -34,3 +35,9 @@ def get_notification_list(request, method_name="all"):
if request.GET.get("mark_as_read"):
notification.mark_as_read()
return notification_list
def assert_soft_delete() -> None:
if not notification_settings.SOFT_DELETE:
msg = "To use this feature you need activate SOFT_DELETE in settings.py"
raise ImproperlyConfigured(msg)

View file

@ -1,7 +1,8 @@
from .base import NotificationLevel
from .base import AbstractNotification, NotificationLevel
from .notification import Notification
__all__ = [
__all__ = (
"AbstractNotification",
"Notification",
"NotificationLevel",
]
)

View file

@ -12,7 +12,9 @@ from django.utils import timesince, timezone
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from notifications.helpers import assert_soft_delete
from notifications.querysets import NotificationQuerySet
from notifications.settings import notification_settings
class NotificationLevel(models.IntegerChoices):
@ -52,8 +54,6 @@ class AbstractNotification(models.Model):
"""
level = models.IntegerField(_("level"), choices=NotificationLevel.choices, default=NotificationLevel.INFO)
recipient = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
@ -62,7 +62,6 @@ class AbstractNotification(models.Model):
verbose_name=_("recipient"),
blank=False,
)
unread = models.BooleanField(_("unread"), default=True, blank=False, db_index=True)
actor_content_type = models.ForeignKey(
ContentType,
@ -102,10 +101,12 @@ class AbstractNotification(models.Model):
action_object.short_description = _("action object")
timestamp = models.DateTimeField(_("timestamp"), default=timezone.now, db_index=True)
level = models.IntegerField(_("level"), choices=NotificationLevel.choices, default=NotificationLevel.INFO)
public = models.BooleanField(_("public"), default=True, db_index=True)
deleted = models.BooleanField(_("deleted"), default=False, db_index=True)
emailed = models.BooleanField(_("emailed"), default=False, db_index=True)
public = models.BooleanField(_("public"), default=True, db_index=True)
unread = models.BooleanField(_("unread"), default=True, blank=False, db_index=True)
data = models.JSONField(_("data"), blank=True, null=True)
@ -135,27 +136,42 @@ class AbstractNotification(models.Model):
return _("%(actor)s %(verb)s %(action_object)s %(timesince)s ago") % ctx
return _("%(actor)s %(verb)s %(timesince)s ago") % ctx
def timesince(self, now: Union[None, datetime.datetime] = None) -> str:
"""
Shortcut for the ``django.utils.timesince.timesince`` function of the
current timestamp.
"""
return timesince.timesince(self.timestamp, now)
@property
def slug(self):
return self.id
def mark_as_read(self) -> None:
if self.unread:
self.unread = False
def _mark_as(self, field: str, status: bool) -> None:
if getattr(self, field, None) != status:
setattr(self, field, status)
self.save()
def mark_as_active(self) -> None:
assert_soft_delete()
self._mark_as("deleted", False)
def mark_as_deleted(self) -> None:
if notification_settings.SOFT_DELETE:
self._mark_as("deleted", True)
else:
self.delete()
def mark_as_sent(self) -> None:
self._mark_as("emailed", True)
def mark_as_unsent(self) -> None:
self._mark_as("emailed", False)
def mark_as_public(self) -> None:
self._mark_as("public", True)
def mark_as_private(self) -> None:
self._mark_as("public", False)
def mark_as_read(self) -> None:
self._mark_as("unread", False)
def mark_as_unread(self) -> None:
if not self.unread:
self.unread = True
self.save()
self._mark_as("unread", True)
def _build_url(self, field_name: str) -> str:
app_label = getattr(getattr(self, f"{field_name}_content_type"), "app_label")
@ -179,6 +195,14 @@ class AbstractNotification(models.Model):
def target_object_url(self) -> Union[str, None]:
return self._build_url("target")
def timesince(self, now: Union[None, datetime.datetime] = None) -> str:
"""
Shortcut for the ``django.utils.timesince.timesince`` function of the
current timestamp.
"""
return timesince.timesince(self.timestamp, now)
def naturalday(self) -> Union[str, None]:
"""
Shortcut for the ``humanize``.

View file

@ -2,6 +2,8 @@ from datetime import datetime, timedelta
from unittest.mock import patch
import pytest
from django.core.exceptions import ImproperlyConfigured
from django.test import override_settings
from django.urls import NoReverseMatch
from freezegun import freeze_time
from swapper import load_model
@ -48,6 +50,83 @@ def test__str__():
assert str(notification.action_object) in notification_str
@pytest.mark.django_db
def test_slug():
notification = NotificationShortFactory()
assert notification.id == notification.slug
@pytest.mark.parametrize(
"field,initial_status,method_name,expected",
(
("emailed", True, "mark_as_sent", True),
("emailed", False, "mark_as_sent", True),
("emailed", True, "mark_as_unsent", False),
("emailed", False, "mark_as_unsent", False),
("public", True, "mark_as_public", True),
("public", False, "mark_as_public", True),
("public", True, "mark_as_private", False),
("public", False, "mark_as_private", False),
("unread", True, "mark_as_read", False),
("unread", False, "mark_as_read", False),
("unread", True, "mark_as_unread", True),
("unread", False, "mark_as_unread", True),
),
)
@pytest.mark.django_db
def test_mark_as_methods(field, initial_status, method_name, expected):
notification = NotificationShortFactory(**{field: initial_status})
func = getattr(notification, method_name)
func()
assert getattr(notification, field, None) is expected
@pytest.mark.django_db
def test_mark_as_active():
notification = NotificationShortFactory(deleted=True)
with pytest.raises(ImproperlyConfigured):
notification.mark_as_active()
with override_settings(DJANGO_NOTIFICATIONS_CONFIG={"SOFT_DELETE": True}):
notification.mark_as_active()
assert notification.deleted is False
@pytest.mark.django_db
def test_mark_as_deleted():
notification = NotificationShortFactory(deleted=False)
with override_settings(DJANGO_NOTIFICATIONS_CONFIG={"SOFT_DELETE": True}):
notification.mark_as_deleted()
assert notification.deleted is True
notification.mark_as_deleted()
assert Notification.objects.count() == 0
@pytest.mark.parametrize(
"method,field",
(
("actor_object_url", "actor"),
("action_object_url", "action_object"),
("target_object_url", "target"),
),
)
@pytest.mark.django_db
def test_build_url(method, field):
notification = NotificationFullFactory()
url = getattr(notification, method)()
assert "<a href=" in url
assert str(getattr(notification, field).id) in url
with patch("notifications.models.base.reverse") as mock:
mock.side_effect = NoReverseMatch
url = getattr(notification, method)()
assert getattr(notification, field).id == url
@pytest.mark.parametrize(
"increase,expected_result",
(
@ -64,45 +143,6 @@ def test_timesince(increase, expected_result):
assert notification.timesince() == expected_result
@pytest.mark.django_db
def test_slug():
notification = NotificationShortFactory()
assert notification.id == notification.slug
@pytest.mark.parametrize(
"before,method",
(
(True, "mark_as_read"),
(False, "mark_as_unread"),
),
)
@pytest.mark.django_db
def test_mark_as_read_unread(before, method):
notification = NotificationShortFactory(unread=before)
assert Notification.objects.filter(unread=before).count() == 1
func = getattr(notification, method)
func()
assert Notification.objects.filter(unread=before).count() == 0
assert Notification.objects.filter(unread=not before).count() == 1
@pytest.mark.django_db
def test_build_url():
notification = NotificationShortFactory()
url = notification.actor_object_url()
assert "<a href=" in url
assert str(notification.actor.id) in url
with patch("notifications.models.base.reverse") as mock:
mock.side_effect = NoReverseMatch
url = notification.actor_object_url()
assert notification.actor.id == url
@pytest.mark.parametrize(
"increase,expected_result",
(