diff --git a/openwisp_notifications/base/models.py b/openwisp_notifications/base/models.py index 002611fa..9c7a4a5f 100644 --- a/openwisp_notifications/base/models.py +++ b/openwisp_notifications/base/models.py @@ -101,6 +101,17 @@ class AbstractNotification(UUIDModel, BaseNotification): _action_object = BaseNotification.action_object _target = BaseNotification.target + @property + def resolved_verb(self): + config = {} + try: + config = get_notification_configuration(self.type) + except NotificationRenderException as e: + logger.error( + "Couldn't get notification config for type %s : %s", self.type, e + ) + return config.get("verb") or self.verb + class Meta(BaseNotification.Meta): abstract = True diff --git a/openwisp_notifications/base/notifications.py b/openwisp_notifications/base/notifications.py index 3674b276..2ba4a297 100644 --- a/openwisp_notifications/base/notifications.py +++ b/openwisp_notifications/base/notifications.py @@ -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( diff --git a/openwisp_notifications/handlers.py b/openwisp_notifications/handlers.py index b2aa04cf..21feb451 100644 --- a/openwisp_notifications/handlers.py +++ b/openwisp_notifications/handlers.py @@ -67,7 +67,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) @@ -146,7 +145,6 @@ def notify_handler(**kwargs): notification = Notification( recipient=recipient, actor=actor, - verb=str(verb), public=public, description=description, timestamp=timestamp, diff --git a/openwisp_notifications/migrations/0012_alter_notification_verb.py b/openwisp_notifications/migrations/0012_alter_notification_verb.py new file mode 100644 index 00000000..3b049015 --- /dev/null +++ b/openwisp_notifications/migrations/0012_alter_notification_verb.py @@ -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" + ), + ), + ] diff --git a/openwisp_notifications/templates/openwisp_notifications/default_message.md b/openwisp_notifications/templates/openwisp_notifications/default_message.md index e673186a..d38e35cc 100644 --- a/openwisp_notifications/templates/openwisp_notifications/default_message.md +++ b/openwisp_notifications/templates/openwisp_notifications/default_message.md @@ -1,7 +1,7 @@ -{% block head %} {{ notification.level }} : {{notification.target}} {{ notification.verb }} {% endblock head %} +{% block head %} {{ notification.level }} : {{notification.target}} {{ notification.resolved_verb }} {% endblock head %} {% block body %} {% if notification.actor_link %}[{{notification.actor}}]({{notification.actor_link}}){% else %}{{notification.actor}}{% endif %} reports {% if notification.target_link %}[{{notification.target}}]({{notification.target_link}}){% else %}{{notification.target}}{% endif %} -{{ notification.verb }}. +{{ notification.resolved_verb }}. {% endblock body %} diff --git a/openwisp_notifications/tests/test_notifications.py b/openwisp_notifications/tests/test_notifications.py index ddccc541..e2af7840 100644 --- a/openwisp_notifications/tests/test_notifications.py +++ b/openwisp_notifications/tests/test_notifications.py @@ -407,7 +407,7 @@ def test_default_notification_type(self): self._create_notification() n = notification_queryset.first() self.assertEqual(n.level, "info") - self.assertEqual(n.verb, "default verb") + self.assertEqual(n.resolved_verb, "default verb") self.assertIn( "Default notification with default verb and level info by", n.message ) @@ -470,7 +470,7 @@ def test_generic_notification_type(self): self._create_notification() n = notification_queryset.first() self.assertEqual(n.level, "info") - self.assertEqual(n.verb, "generic verb") + self.assertEqual(n.resolved_verb, "generic verb") expected_output = ( '

admin

' ).format( @@ -573,8 +573,8 @@ def test_register_unregister_notification_type(self): "verbose_name": "Test Notification Type", "level": "test", "verb": "testing", - "message": "{notification.verb} initiated by {notification.actor} since {notification}", - "email_subject": "[{site.name}] {notification.verb} reported by {notification.actor}", + "message": "{notification.resolved_verb} initiated by {notification.actor} since {notification}", + "email_subject": "[{site.name}] {notification.resolved_verb} reported by {notification.actor}", } with self.subTest("Registering new notification type"): @@ -583,7 +583,7 @@ def test_register_unregister_notification_type(self): self._create_notification() n = notification_queryset.first() self.assertEqual(n.level, "test") - self.assertEqual(n.verb, "testing") + self.assertEqual(n.resolved_verb, "testing") self.assertEqual( n.message, "

testing initiated by admin since 0\xa0minutes

", @@ -1506,6 +1506,38 @@ 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.resolved_verb}" + default_config["verb"] = "initial verb" + + self._create_notification() + notification = notification_queryset.first() + + with self.subTest("Test initial verb from config"): + self.assertEqual(notification.resolved_verb, "initial verb") + self.assertIn("initial verb", notification.message) + + with self.subTest("Test verb changes dynamically from config"): + default_config["verb"] = "updated verb" + del notification.message + self.assertEqual(notification.resolved_verb, "updated verb") + self.assertIn("updated verb", notification.message) + + with self.subTest("Test fallback to database verb"): + unregister_notification_type("default") + notification.__dict__["verb"] = "db verb" + self.assertEqual(notification.verb, "db verb") + + default_config["message"] = original_message + default_config["verb"] = original_verb + class TestTransactionNotifications(TestOrganizationMixin, TransactionTestCase): def setUp(self): diff --git a/openwisp_notifications/types.py b/openwisp_notifications/types.py index 04ae7ace..a368b02b 100644 --- a/openwisp_notifications/types.py +++ b/openwisp_notifications/types.py @@ -10,7 +10,7 @@ "verbose_name": "Default Type", "email_subject": "[{site.name}] Default Notification Subject", "message": ( - "Default notification with {notification.verb} and level {notification.level}" + "Default notification with {notification.resolved_verb} and level {notification.level}" " by [{notification.target}]({notification.target_link})" ), "message_template": "openwisp_notifications/default_message.md", @@ -23,7 +23,7 @@ "verbose_name": "Generic Type", "email_subject": "[{site.name}] Generic Notification Subject", "message": ( - "Generic notification with {notification.verb} and level {notification.level}" + "Generic notification with {notification.resolved_verb} and level {notification.level}" " by [{notification.actor}]({notification.actor_link})" ), "description": "{notification.description}", diff --git a/tests/openwisp2/sample_notifications/apps.py b/tests/openwisp2/sample_notifications/apps.py index 6c53a08d..bb4df376 100644 --- a/tests/openwisp2/sample_notifications/apps.py +++ b/tests/openwisp2/sample_notifications/apps.py @@ -24,7 +24,7 @@ def register_notification_types(self): "verbose_name": "Object created", "verb": "created", "level": "info", - "message": "{notification.target} object {notification.verb}.", + "message": "{notification.target} object {notification.resolved_verb}.", "email_subject": "[{site.name}] INFO: {notification.target} created", }, ) diff --git a/tests/openwisp2/sample_notifications/migrations/0004_alter_notification_verb.py b/tests/openwisp2/sample_notifications/migrations/0004_alter_notification_verb.py new file mode 100644 index 00000000..900e6ce5 --- /dev/null +++ b/tests/openwisp2/sample_notifications/migrations/0004_alter_notification_verb.py @@ -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" + ), + ), + ]