Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions openwisp_notifications/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ def notification_render_attributes(obj, **attrs):
}
defaults.update(attrs)

db_verb = obj.verb

config = {}
if obj.type:
try:
config = get_notification_configuration(obj.type)
except NotificationRenderException as e:
logger.error(
"Couldn't get notification config for type %s : %s", obj.type, e
)
obj.verb = db_verb if db_verb is not None else config.get("verb")

for target_attr, source_attr in defaults.items():
setattr(obj, target_attr, getattr(obj, source_attr))

Expand All @@ -75,9 +87,11 @@ def notification_render_attributes(obj, **attrs):
setattr(obj, "target", obj._related_object("target"))

yield obj
obj.verb = db_verb

for attr in defaults.keys():
delattr(obj, attr)
if hasattr(obj, attr):
delattr(obj, attr)


class AbstractNotification(UUIDModel, BaseNotification):
Expand Down Expand Up @@ -283,23 +297,28 @@ def get_message(self):

@cached_property
def email_subject(self):
if self.type:
try:
config = get_notification_configuration(self.type)
data = self.data or {}
return config["email_subject"].format(
site=Site.objects.get_current(), notification=self, **data
)
except (AttributeError, KeyError, NotificationRenderException) as exception:
self._invalid_notification(
self.pk,
exception,
"Error encountered in generating notification email",
)
elif self.data.get("email_subject", None):
return self.data.get("email_subject")
else:
return self.message
with notification_render_attributes(self):
if self.type:
try:
config = get_notification_configuration(self.type)
data = self.data or {}
return config["email_subject"].format(
site=Site.objects.get_current(), notification=self, **data
)
except (
AttributeError,
KeyError,
NotificationRenderException,
) as exception:
self._invalid_notification(
self.pk,
exception,
"Error encountered in generating notification email",
)
elif self.data.get("email_subject", None):
return self.data.get("email_subject")
else:
return self.message

def _related_object(self, field):
obj_id = getattr(self, f"{field}_object_id")
Expand Down
2 changes: 1 addition & 1 deletion openwisp_notifications/base/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class AbstractNotification(models.Model):
actor = GenericForeignKey("actor_content_type", "actor_object_id")
actor.short_description = _("actor")

verb = models.CharField(_("verb"), max_length=255)
verb = models.CharField(_("verb"), max_length=255, null=True, blank=True)
description = models.TextField(_("description"), blank=True, null=True)

target_content_type = models.ForeignKey(
Expand Down
2 changes: 0 additions & 2 deletions openwisp_notifications/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def notify_handler(**kwargs):
level = kwargs.pop(
"level", notification_template.get("level", Notification.LEVELS.info)
)
verb = notification_template.get("verb", kwargs.pop("verb", None))
user_app_name = User._meta.app_label

where = Q(is_superuser=True)
Expand Down Expand Up @@ -144,7 +143,6 @@ def notify_handler(**kwargs):
notification = Notification(
recipient=recipient,
actor=actor,
verb=str(verb),
public=public,
description=description,
timestamp=timestamp,
Expand Down
20 changes: 20 additions & 0 deletions openwisp_notifications/migrations/0012_alter_notification_verb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 5.2.6 on 2025-10-14 18:40

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("openwisp_notifications", "0011_populate_organizationnotificationsettings"),
]

operations = [
migrations.AlterField(
model_name="notification",
name="verb",
field=models.CharField(
blank=True, max_length=255, null=True, verbose_name="verb"
),
),
]
13 changes: 13 additions & 0 deletions openwisp_notifications/migrations/0013_merge_20260131_1927.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Generated by Django 5.2.6 on 2026-01-31 18:27

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("openwisp_notifications", "0012_alter_notification_verb"),
("openwisp_notifications", "0012_replace_jsonfield_with_django_builtin"),
]

operations = []
53 changes: 50 additions & 3 deletions openwisp_notifications/tests/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from openwisp_notifications import settings as app_settings
from openwisp_notifications import tasks, utils
from openwisp_notifications.base.models import notification_render_attributes
from openwisp_notifications.exceptions import NotificationRenderException
from openwisp_notifications.handlers import (
notify_handler,
Expand Down Expand Up @@ -431,7 +432,8 @@ def test_default_notification_type(self):
self._create_notification()
n = notification_queryset.first()
self.assertEqual(n.level, "info")
self.assertEqual(n.verb, "default verb")
with notification_render_attributes(n) as rendered:
self.assertEqual(rendered.verb, "default verb")
self.assertIn(
"Default notification with default verb and level info by", n.message
)
Expand Down Expand Up @@ -494,7 +496,8 @@ def test_generic_notification_type(self):
self._create_notification()
n = notification_queryset.first()
self.assertEqual(n.level, "info")
self.assertEqual(n.verb, "generic verb")
with notification_render_attributes(n) as rendered:
self.assertEqual(rendered.verb, "generic verb")
expected_output = (
'<p><a href="https://example.com{user_path}">admin</a></p>'
).format(
Expand Down Expand Up @@ -607,7 +610,8 @@ def test_register_unregister_notification_type(self):
self._create_notification()
n = notification_queryset.first()
self.assertEqual(n.level, "test")
self.assertEqual(n.verb, "testing")
with notification_render_attributes(n) as rendered:
self.assertEqual(rendered.verb, "testing")
self.assertEqual(
n.message,
"<p>testing initiated by admin since 0\xa0minutes</p>",
Expand Down Expand Up @@ -1530,6 +1534,49 @@ def test_notification_preference_page(self):
response = self.client.get(reverse(preference_page, args=(uuid4(),)))
self.assertEqual(response.status_code, 404)

@mock_notification_types
def test_dynamic_verb_changed(self):
self.notification_options.update(
{"type": "default", "target": self._get_org_user()}
)
default_config = get_notification_configuration("default")
original_message = default_config["message"]
original_verb = default_config.get("verb", "default verb")
default_config["message"] = "Notification with {notification.verb}"
default_config["verb"] = "initial verb"

self._create_notification()
notification = notification_queryset.first()

with self.subTest("DB does not store default verb"):
self.assertIsNone(notification.verb)

with self.subTest("Initial config verb is rendered"):
with notification_render_attributes(notification) as n:
self.assertEqual(n.verb, "initial verb")
self.assertIn("initial verb", n.message)

default_config["verb"] = "updated verb"
del notification.message

with self.subTest("Config change affects existing notification"):
with notification_render_attributes(notification) as n:
self.assertEqual(n.verb, "updated verb")
self.assertIn("updated verb", n.message)

notification.verb = "db verb"
notification.save()
notification.refresh_from_db()
del notification.message

with self.subTest("DB verb overrides config"):
with notification_render_attributes(notification) as n:
self.assertEqual(n.verb, "db verb")
self.assertIn("db verb", n.message)

default_config["message"] = original_message
default_config["verb"] = original_verb
Comment thread
coderabbitai[bot] marked this conversation as resolved.


class TestTransactionNotifications(TestOrganizationMixin, TransactionTestCase):
def setUp(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 5.2.6 on 2025-10-14 23:01

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("sample_notifications", "0003_default_groups_permissions"),
]

operations = [
migrations.AlterField(
model_name="notification",
name="verb",
field=models.CharField(
blank=True, max_length=255, null=True, verbose_name="verb"
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Generated by Django 5.2.6 on 2026-01-31 18:29

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("sample_notifications", "0004_alter_notification_verb"),
("sample_notifications", "0004_replace_jsonfield_with_django_builtin"),
]

operations = []
Loading