mirror of
https://github.com/Hopiu/django-notifications.git
synced 2026-03-16 21:30:24 +00:00
Refactoring the AbstractNotification model and its tests
This commit is contained in:
parent
ec236d59bc
commit
78faf1abaa
4 changed files with 132 additions and 60 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
from .base import NotificationLevel
|
||||
from .base import AbstractNotification, NotificationLevel
|
||||
from .notification import Notification
|
||||
|
||||
__all__ = [
|
||||
__all__ = (
|
||||
"AbstractNotification",
|
||||
"Notification",
|
||||
"NotificationLevel",
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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``.
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
(
|
||||
|
|
|
|||
Loading…
Reference in a new issue