Skip to content
Merged
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
18 changes: 18 additions & 0 deletions docs/developer/notification-types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ type:
software like Aldus PageMaker including versions of *Lorem Ipsum*.""",
)

The URL of a ``generic_message`` notification points to the target object
by default. To append a querystring or fragment to this URL, pass
``target_url_suffix`` to ``notify.send``:

.. code-block:: python

notify.send(
type="generic_message",
level="error",
message="An unexpected error happened!",
sender=User.objects.first(),
target=User.objects.last(),
target_url_suffix="?status=pending",
)

The value of ``target_url_suffix`` must be a string starting with ``?``,
``&`` or ``#``.

Properties of Notification Types
--------------------------------

Expand Down
111 changes: 59 additions & 52 deletions docs/developer/sending-notifications.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,58 +64,65 @@ The complete syntax for ``notify`` is:

The ``notify`` signal supports the following parameters:

================= ========================================================
**Parameter** **Description**
``actor`` An object of any type that represents the actor
performing the action that triggered the notification.

**Note:** Use ``sender`` instead of ``actor`` if you
intend to use keyword arguments.
``recipient`` The recipient of the notification. This can be a
``Group``, a list or queryset of ``User`` objects, or a
single ``User`` object.

Defaults to ``None``, meaning you need to provide this
argument.
``action_object`` An object related to the action that triggered the
notification (optional).

Defaults to ``None``.
``target`` The target object of the notification (optional).

Defaults to ``None``.
``type`` Set values of other parameters based on registered
:doc:`notification types <./notification-types>`

Defaults to ``None`` meaning you need to provide other
arguments.
``email_subject`` Sets subject of email notification to be sent.

Defaults to the notification message.
``url`` Adds a URL in the email text, e.g.:

``For more information see <url>.``

Defaults to ``None``, meaning the above message would
not be added to the email text.
``verb`` A string describing the action that triggered the
notification.

Defaults to ``None``, meaning you need to provide this
argument.
``level`` The level of the notification, one of 'success', 'info',
'warning' or 'error'.

Defaults to 'info'.
``description`` Additional information to be included in the
notification (optional).

Defaults to ``''``.
``timestamp`` A timestamp (``datetime`` object) for the notification
(optional).

Defaults to the current time.
================= ========================================================
===================== ====================================================
**Parameter** **Description**
``actor`` An object of any type that represents the actor
performing the action that triggered the
notification.

**Note:** Use ``sender`` instead of ``actor`` if you
intend to use keyword arguments.
``recipient`` The recipient of the notification. This can be a
``Group``, a list or queryset of ``User`` objects,
or a single ``User`` object.

Defaults to ``None``, meaning you need to provide
this argument.
``action_object`` An object related to the action that triggered the
notification (optional).

Defaults to ``None``.
``target`` The target object of the notification (optional).

Defaults to ``None``.
``target_url_suffix`` Appends a querystring or fragment to the generated
target URL. The value must be a string starting with
``?``, ``&`` or ``#``. See the :ref:`generic_message
example <notifications_generic_message_type>`.

Defaults to ``None``.
``type`` Set values of other parameters based on registered
:doc:`notification types <./notification-types>`

Defaults to ``None`` meaning you need to provide
other arguments.
``email_subject`` Sets subject of email notification to be sent.

Defaults to the notification message.
``url`` Adds a URL in the email text, e.g.:

``For more information see <url>.``

Defaults to ``None``, meaning the above message
would not be added to the email text.
``verb`` A string describing the action that triggered the
notification.

Defaults to ``None``, meaning you need to provide
this argument.
``level`` The level of the notification, one of 'success',
'info', 'warning' or 'error'.

Defaults to 'info'.
``description`` Additional information to be included in the
notification (optional).

Defaults to ``''``.
``timestamp`` A timestamp (``datetime`` object) for the
notification (optional).

Defaults to the current time.
===================== ====================================================

Passing Extra Data to Notifications
-----------------------------------
Expand Down
4 changes: 3 additions & 1 deletion openwisp_notifications/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@ def action_url(self):

@property
def target_url(self):
return self._get_related_object_url(field="target")
url = self._get_related_object_url(field="target")
suffix = (self.data or {}).get("target_url_suffix")
return f"{url}{suffix}" if url and url != "#" and suffix else url

@cached_property
def message(self):
Expand Down
8 changes: 8 additions & 0 deletions openwisp_notifications/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ def notify_handler(**kwargs):
optional_objs = [
(kwargs.pop(opt, None), opt) for opt in ("target", "action_object")
]
target_url_suffix = kwargs.get("target_url_suffix")
if target_url_suffix is not None and (
not isinstance(target_url_suffix, str)
or not target_url_suffix.startswith(("?", "&", "#"))
):
raise ValueError(
_("target_url_suffix must be a string starting with '?', '&' or '#'.")
)

notification_list = []
for recipient in recipients:
Expand Down
55 changes: 55 additions & 0 deletions openwisp_notifications/tests/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,61 @@ def test_create_with_extra_data(self):
self.assertIn(f"Error: {error}", n.message)
self.assertEqual(n.email_subject, f"Error subject: {error}")

def test_generic_message_target_url_suffix(self):
target = self._create_operator()
target_url = _get_absolute_url(
reverse(f"admin:{self.users_app_label}_user_change", args=[target.pk])
)
with self.subTest("Append querystring suffix"):
notify.send(
type="generic_message",
target=target,
sender=self.admin,
target_url_suffix="?status=pending",
)
notification = notification_queryset.first()
self.assertEqual(
notification.target_url,
f"{target_url}?status=pending",
)

with self.subTest("Append fragment suffix"):
notification.delete()
notify.send(
type="generic_message",
target=target,
sender=self.admin,
target_url_suffix="#pending",
)
notification = notification_queryset.first()
self.assertEqual(
notification.target_url,
f"{target_url}#pending",
)

with self.subTest("Do not append suffix to fallback target URL"):
notification.delete()
notify.send(
type="generic_message",
sender=self.admin,
target_url_suffix="?status=pending",
)
notification = notification_queryset.first()
self.assertEqual(notification.target_url, "#")

def test_generic_message_invalid_target_url_suffix(self):
target = self._create_operator()
invalid_suffixes = ["status=pending", "https://example.com"]
for suffix in invalid_suffixes:
with self.subTest(suffix=suffix):
with self.assertRaises(ValueError):
notify.send(
type="generic_message",
target=target,
sender=self.admin,
target_url_suffix=suffix,
)

def test_batch_email_helpers(self):
with self.subTest("get_user_batched_notifications_cache_key()"):
cache_key = Notification.get_user_batched_notifications_cache_key(
Expand Down
Loading