Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Added generic message notification type (shown in dialg box) #275

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c207c9d
[feature] Description of notification shown in a dialog admin site
Dhanus3133 May 14, 2024
1025bfb
[qa] Fix
Dhanus3133 May 14, 2024
2605a09
[chore] Add trans
Dhanus3133 May 14, 2024
bf3ba25
Merge branch 'master' into feat/display-description-notification-dialog
Dhanus3133 May 18, 2024
a15a284
[chore] Increase page_size
Dhanus3133 May 20, 2024
3a0377d
[chore] Fix relative URL error
Dhanus3133 May 22, 2024
2be7037
[chore] Improvements
Dhanus3133 May 23, 2024
581cc90
[chore] Show description with md support
Dhanus3133 May 23, 2024
a7c792d
[chore] Rename general to generic message types
Dhanus3133 May 23, 2024
9e7c1fc
[chore] Fix menu bar
Dhanus3133 May 25, 2024
2608c11
[chore] Rename rendered_description
Dhanus3133 May 25, 2024
2a922a6
[chore] Fix test
Dhanus3133 May 25, 2024
0686c2d
[chore] Just use description
Dhanus3133 May 26, 2024
8106f3e
[fix] Rendered description shows error for None description
Dhanus3133 May 26, 2024
cc528df
[fix] Level icon repeat
Dhanus3133 May 26, 2024
aca39bc
[chore] JQuery way
Dhanus3133 May 26, 2024
1e6b840
[fix] Fixed tests
pandafy May 27, 2024
00ec345
[chore] Bump changes
Dhanus3133 May 27, 2024
e69600f
[chore] Add tests for notification level
Dhanus3133 May 27, 2024
bbff510
[chore] Remove comment
Dhanus3133 May 27, 2024
8dfcfce
Merge branch 'master' into feat/display-description-notification-dialog
Dhanus3133 May 28, 2024
df10915
[chore] Focus fix
Dhanus3133 May 28, 2024
6e5db98
[chores] UI/CSS improvements
nemesifier May 29, 2024
6de368f
[fix] Toast notifications dialog
Dhanus3133 May 30, 2024
b0725b1
[fix] Use relative path also for notification toast
nemesifier May 30, 2024
9ed5982
[chore] Add generic_message test
Dhanus3133 May 30, 2024
4a9956b
[docs] Added generic_message
nemesifier May 30, 2024
4593ef3
[feature] Support markdown in message and description of generic noti…
pandafy May 30, 2024
9f11de6
[fix] Open button display only when target_url is available
Dhanus3133 May 31, 2024
b09cd6f
[changes] Use context manager to handle temporary attribtues
pandafy May 31, 2024
8229268
[chore] Add Tests for open button visibility
Dhanus3133 Jun 1, 2024
08ab21b
[fix] Keyup event handler
Dhanus3133 Jun 7, 2024
69bfb39
Merge branch 'master' into feat/display-description-notification-dialog
nemesifier Jun 7, 2024
0020c21
[chore] Refactor message conversion to improve readability
Dhanus3133 Jun 7, 2024
859734e
[chore] Comment changes
Dhanus3133 Jun 8, 2024
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
67 changes: 62 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -527,11 +527,61 @@ value you want but it needs to be unique. For more details read `preventing dupl
Notification Types
------------------

**OpenWISP Notifications** simplifies configuring individual notification by
using notification types. You can think of a notification type as a template
**OpenWISP Notifications** allows defining notification types for
recurring events. Think of a notification type as a template
for notifications.

These properties can be configured for each notification type:
``generic_message``
~~~~~~~~~~~~~~~~~~~

.. figure:: https://raw.githubusercontent.com/openwisp/openwisp-notifications/docs/docs/images/1.1/generic_message.png
:target: https://raw.githubusercontent.com/openwisp/openwisp-notifications/docs/docs/images/1.1/generic_message.png
:align: center

This module includes a notification type called ``generic_message``.

This notification type is designed to deliver custom messages in the
user interface for infrequent events or errors that occur during
background operations and cannot be communicated easily to the user
in other ways.

These messages may require longer explanations and are therefore
displayed in a dialog overlay, as shown in the screenshot above.
This notification type does not send emails.

The following code example demonstrates how to send a notification
of this type:

.. code-block:: python

from openwisp_notifications.signals import notify
notify.send(
type='generic_message',
level='error',
message='An unexpected error happened!',
sender=User.objects.first(),
target=User.objects.last(),
description="""Lorem Ipsum is simply dummy text
of the printing and typesetting industry.

### Heading 3

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,
when an unknown printer took a galley of type and scrambled it to make a
type specimen book.

It has survived not only **five centuries**, but also the leap into
electronic typesetting, remaining essentially unchanged.

It was popularised in the 1960s with the release of Letraset sheets
containing Lorem Ipsum passages, and more recently with desktop publishing
software like Aldus PageMaker including versions of *Lorem Ipsum*."""
)

Properties of Notification Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The following properties can be configured for each notification type:

+------------------------+----------------------------------------------------------------+
| **Property** | **Description** |
Expand Down Expand Up @@ -569,8 +619,15 @@ These properties can be configured for each notification type:
+------------------------+----------------------------------------------------------------+


**Note**: A notification type configuration should contain atleast one of ``message`` or ``message_template``
settings. If both of them are present, ``message`` is given preference over ``message_template``.
**Note**: It is recommended that a notification type configuration
for recurring events contains either the ``message`` or
``message_template`` properties. If both are present,
``message`` is given preference over ``message_template``.

If you don't plan on using ``message`` or ``message_template``,
it may be better to use the existing ``generic_message`` type.
However, it's advised to do so only if the event being notified
is infrequent.

**Note**: The callable for ``actor_link``, ``action_object_link`` and ``target_link`` should
have the following signature:
Expand Down
3 changes: 3 additions & 0 deletions openwisp_notifications/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,13 @@ def data(self):


class NotificationListSerializer(NotificationSerializer):
description = serializers.CharField(source='rendered_description')

class Meta(NotificationSerializer.Meta):
fields = [
'id',
'message',
'description',
'unread',
'target_url',
'email_subject',
Expand Down
92 changes: 63 additions & 29 deletions openwisp_notifications/base/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from contextlib import contextmanager

from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
Expand Down Expand Up @@ -29,6 +30,35 @@
logger = logging.getLogger(__name__)


@contextmanager
def notification_render_attributes(obj, **attrs):
"""
This context manager sets temporary attributes on
the notification object to allowing rendering of
notification.

It can only be used to set aliases of the existing attributes.
By default, it will set the following aliases:
- actor_link -> actor_url
- action_link -> action_url
- target_link -> target_url
"""
defaults = {
'actor_link': 'actor_url',
'action_link': 'action_url',
'target_link': 'target_url',
}
defaults.update(attrs)

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

yield obj

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


class AbstractNotification(UUIDModel, BaseNotification):
CACHE_KEY_PREFIX = 'ow-notifications-'
type = models.CharField(max_length=30, null=True, choices=NOTIFICATION_CHOICES)
Expand Down Expand Up @@ -103,40 +133,44 @@ def target_url(self):

@cached_property
def message(self):
return self.get_message()
with notification_render_attributes(self):
return self.get_message()

@cached_property
def rendered_description(self):
Dhanus3133 marked this conversation as resolved.
Show resolved Hide resolved
if not self.description:
return
with notification_render_attributes(self):
data = self.data or {}
desc = self.description.format(notification=self, **data)
return mark_safe(markdown(desc))

@property
def email_message(self):
return self.get_message(email_message=True)
with notification_render_attributes(self, target_link='redirect_view_url'):
return self.get_message()

def get_message(self, email_message=False):
if self.type:
# setting links in notification object for message rendering
self.actor_link = self.actor_url
self.action_link = self.action_url
self.target_link = (
self.target_url if not email_message else self.redirect_view_url
)
try:
config = get_notification_configuration(self.type)
data = self.data or {}
if 'message' in config:
md_text = config['message'].format(notification=self, **data)
else:
md_text = render_to_string(
config['message_template'], context=dict(notification=self)
).strip()
except (AttributeError, KeyError, NotificationRenderException) as exception:
self._invalid_notification(
self.pk,
exception,
'Error encountered in rendering notification message',
)
# clean up
self.actor_link = self.action_link = self.target_link = None
return mark_safe(markdown(md_text))
else:
def get_message(self):
if not self.type:
return self.description
try:
config = get_notification_configuration(self.type)
data = self.data or {}
if 'message' in data:
md_text = data['message'].format(notification=self, **data)
elif 'message' in config:
md_text = config['message'].format(notification=self, **data)
else:
md_text = render_to_string(
config['message_template'], context=dict(notification=self, **data)
).strip()
except (AttributeError, KeyError, NotificationRenderException) as exception:
self._invalid_notification(
self.pk,
exception,
'Error encountered in rendering notification message',
)
return mark_safe(markdown(md_text))

@cached_property
def email_subject(self):
Expand Down
14 changes: 7 additions & 7 deletions openwisp_notifications/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ def notify_handler(**kwargs):
except NotificationRenderException as error:
logger.error(f'Error encountered while creating notification: {error}')
return
level = notification_template.get(
'level', kwargs.pop('level', Notification.LEVELS.info)
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
Expand Down Expand Up @@ -195,22 +195,22 @@ def send_email_notification(sender, instance, created, **kwargs):
# Do not send email if notification is malformed.
return
url = instance.data.get('url', '') if instance.data else None
description = instance.message
body_text = instance.email_message
if url:
target_url = url
elif instance.target:
target_url = instance.redirect_view_url
else:
target_url = None
if target_url:
description += _('\n\nFor more information see %(target_url)s.') % {
body_text += _('\n\nFor more information see %(target_url)s.') % {
'target_url': target_url
}

send_email(
subject,
description,
instance.message,
subject=subject,
body_text=body_text,
body_html=instance.email_message,
recipients=[instance.recipient.email],
extra_context={
'call_to_action_url': target_url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@
.ow-notification-level-text {
padding: 0px 6px;
text-transform: uppercase;
font-weight: bold;
}
.ow-notification-elem .icon,
.ow-notification-toast .icon {
Expand Down Expand Up @@ -300,6 +301,66 @@
}
.ow-notification-elem:last-child .ow-notification-inner { border-bottom: none }

/* Generic notification dialog */
.ow-overlay-notification {
background-color: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
transition: opacity 0.3s;
}
.ow-dialog-notification {
position: relative;
background-color: white;
padding: 20px;
padding-top: 20px;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
width: 90%;
max-width: 500px;
text-align: left;
}
.ow-dialog-notification .ow-notification-date {
padding-right: 15px;
}
.ow-dialog-notification .button {
margin-right: 10px;
}
.ow-dialog-notification-level-wrapper {
display: flex;
justify-content: space-between;
}
.ow-dialog-notification .icon {
min-height: 15;
min-width: 15px;
background-repeat: no-repeat;
}
.ow-dialog-close-x {
cursor: pointer;
font-size: 1.75em;
position: absolute;
display: block;
font-weight: bold;
top: 3px;
right: 10px;
}
.ow-dialog-close-x:hover {
color: #df5d43;
}
.ow-message-title {
color: #333;
margin-bottom: 10px;
}
.ow-message-title a {
color: #df5d43;
}
.ow-message-title a:hover {
text-decoration: underline;
}
.ow-dialog-buttons {
line-height: 3em;
}

@media screen and (max-width: 600px) {
.ow-notification-dropdown {
width: 98%;
Expand Down
Loading
Loading