notify
: Notification framework¶
The lino.modlib.notify
plugin adds a notification framework to your Lino
application.
This is a tested document. The following instructions are used for initialization:
>>> import lino
>>> lino.startup('lino_book.projects.chatter.settings.demo')
>>> from lino.api.shell import *
>>> from pprint import pprint
Overview¶
A notification message is a message sent by the application to a user of the site.
Lino sends notifications as Desktop notifications and/or by email
depending on the user preferences of the recipient.
In addition, notification messages are sent via email to the user
according to his mail_mode
field.
Don't mix up notifications with "system notes" (implemented by
lino_xl.lib.notes
). These are fundamentally different beasts
which partly resemble each other. For example both types of messages
have a "subject" and a "body". Both describe some event that happens
to some database object. But while notifications are messages to be
quickly sent to their recipients, system notes are permanent
historic entries visible to every user (who has the required
permission).
The emitter of a notification message is currently not stored. That is, you cannot currently request to see a list of all messages emitted by a given user.
Notification messages are emitted by the application code
Either manually by calling the class method
Message.create_message()
to create a new message.Have your models inherit from
ChangeNotifier
.Add actions that inherit from
NotifyingAction
.
Desktop notifications¶
To enable desktop notifications, there are some requirements:
lino.core.site.Site.use_websockets
must be Truenotifications must be properly installed on the server
the user must have their browser open and have signed in to the Lino site
the user must give their browser permission to show desktop notifications from the Lino site
Marking notifications as seen¶
doctest docs/specs/notify.rst In addition to sending notifications via email and as desktop notification, Lino displays unseen notfication messages in the dashboard where it also provides an action for marking individual message as seen.
A common caveat is that Lino does not know whether you saw the desktop notification or the email. That's why all notifications remain on your dashboard until you tick them off explicitly.
It can be disturbing to read a message again in the dasboard if you have just read by email or as a desktop notification.
Some users tend to not care about marking their notifications as seen in the dashboard, which causes their "My notification messages" to become overfilled and useless.
Some users misunderstand the my notifications widget as a to-do list, which is not a good idea.
There is no perfect solution for these problems. One workaround is to
instruct Lino to also delete unseen notifications automatically, by
setting keep_unseen
to
False. Here is an example which also increases remove_after
to 240 hours (10 days):
SITE.plugins.notify.configure(remove_after=240, keep_unseen=False)
Users can hide the MyMessages widget in their preferences if
lino.modlib.dashboard
is installed as well. But that's not a
recommended solution. If you see that users of your application are
doing this, you should analyze why they do it and e.g. add filtering
options.
Possible optimizations of the system:
Marking notifications as seen in the dashboard can be a bit slow because Lino refreshes the whole dashboard after every click. We could avoid this using javascript which sets the item to hidden instead of calling refresh.
Add a ¬ify=123456" (the id of the message) to every link in the email so that when the user follows one of them, the message can get marked as seen.
Notification messages¶
-
class
lino.modlib.notify.
Message
¶ The Django model that represents a notification message.
-
subject
¶
-
body
¶
-
user
¶ The recipient.
-
owner
¶ The database object which controls this message.
This may be None, which means that the message has no controller.
When a notification is controlled, then the recipient will receive only the first message for that object. Any following message is ignored until the recipient has "confirmed" the first message. Typical use case are the messages emitted by
ChangeNotifier
: you don't want to get 10 mails just because a colleague makes 10 small modifications when authoring the text field of a ChangeNotifier object.
-
created
¶
-
sent
¶
-
seen
¶
-
emit_notification
(cls, ar, owner, message_type, msg_func, recipients)¶ Class method which creates one database object per recipient.
recipients is an iterable of (user, mail_mode) tuples. Duplicate items, items with user being None and items having
mail_mode
set tosilent
are removed.msg_func is a callable expected to return a tuple (subject, body). It is called for each recipient in the recipient's language.
The emitting user does not get notified, except when working as another user or when notify_myself is set.
-
create_message
(cls, user, owner=None, **kwargs)¶ Create a message unless that user has already been notified about that object.
-
send_summary_emails
(cls, mm)¶ Send summary emails for all pending notifications with the given mail_mode mm.
-
send_browser_message_for_all_users
(self, user)¶ Send_message to all connected users
-
send_browser_message
(self, user)¶ Send_message to the user's browser
-
-
class
lino.modlib.notify.
Messages
¶ Base for all tables of messages.
-
class
lino.modlib.notify.
AllMessages
(Messages)¶ The gobal list of all messages.
-
class
lino.modlib.notify.
MyMessages
(Messages)¶ Shows messages emitted to me.
As an application developer you should understand the different meanings of "subject" and "body":
The body is expected to be a self-sufficient and complete description of the event. If a message has a body, then the subject is not being displayed in the MyMessages summary.
The subject might contain limited rich text (text formatting, links) but be aware that this formatting gets lost when the message is sent as an email.
Change notifiers¶
-
class
lino.modlib.notify.
ChangeNotifier
¶
-
class
lino.modlib.notify.
ChangeNotifier
¶ Mixin for models which can emit notifications to a list of "observers" when an instance is modified.
TODO: rename ChangeNotifier to ChangeNotifier
-
get_change_subject
(self, ar, cw)¶ Returns the subject text of the notification message to emit.
The default implementation returns a message of style "{user} modified|created {object}" .
Returning None or an empty string means to suppress notification.
-
add_change_watcher
(self, user)¶ Parameters:
- User
The user that will be linked to this object as a change watcher.
-
get_change_body
(self, ar, cw)¶ Returns the body text of the notification message to emit.
The default implementation returns a message of style "{object} has been modified by {user}" followed by a summary of the changes.
-
get_change_info
(self, ar, cw)¶ -
Return a list of HTML elements to be inserted into the body.
This is called by
get_change_body()
. Subclasses can override this. Usage examplelino_xl.lib.notes.models.Note
-
get_change_owner
(self)¶ Return the owner of the notification to emit.
The "owner" is "the database object we are talking about" and decides who is observing this object.
-
Notifying actions¶
-
class
lino.modlib.notify.
NotifyingAction
¶ An action which pops up a dialog window of three fields "Summary", "Description" and a checkbox "Don't notify others" to optionally suppress notification.
Screenshot of a notifying action:
Dialog fields:
-
notify_subject
¶
-
notify_body
¶
-
notify_silent
¶
-
get_notify_subject
(self, ar, obj)¶ Return the default value of the notify_subject field.
-
get_notify_body
(self, ar, obj)¶ Return the default value of the notify_body field.
-
get_notify_owner
(self, ar, obj)¶ Expected to return the
owner lino.modlib.notify.Message.owner>
of the message.The default returns None.
ar is the action request, obj the object on which the action is running,
-
get_notify_recipients
(self, ar, obj)¶ Yield a list of users to be notified.
ar is the action request, obj the object on which the action is running,
-
A NotifyingAction
is a dialog action which potentially sends
a notification. It has three dialog fields ("subject", "body" and a
checkbox "silent"). You can have non-dialog actions (or actions with
some other dialog than a simple subject and body) which build a custom
subject and body and emit a notification. If the emitting object also
has a method emit_system_note()
, then this is being called as
well.
Local configuration¶
-
CHANNEL_LAYERS
¶
This plugin inspects some process parameters in order to automagically set a
default value for the CHANNEL_LAYERS
setting.
>>> from django.conf import settings
>>> from lino.core.utils import is_devserver
>>> is_devserver()
True
>>> settings.SITE.use_websockets
True
>>> settings.CHANNEL_LAYERS['default']['BACKEND'] in ['asgiref.inmemory.ChannelLayer','channels_redis.core.RedisChannelLayer']
True
>>> settings.CHANNEL_LAYERS['default'].get('ROUTING','') in ['lino.modlib.notify.routing.channel_routing','']
True
How to configure locally on a production site:
SITE = Site(...)
CHANNEL_LAYERS['default']['BACKEND'] = 'asgi_redis.RedisChannelLayer'
CHANNEL_LAYERS['default']['CONFIG'] = {
'hosts': [('localhost', 6379)],
}
Utility functions¶
-
lino.modlib.notify.
send_pending_emails_often
()¶
-
lino.modlib.notify.
send_pending_emails_daily
()¶
-
lino.modlib.notify.
clear_seen_messages
()¶ Daily task which deletes messages older than
remove_after
hours.
Choicelists¶
Actions¶
-
class
lino.modlib.notify.
MarkSeen
¶ Mark this message as seen.
-
class
lino.modlib.notify.
MarkAllSeen
¶ Mark all messages as seen.
-
class
lino.modlib.notify.
ClearSeen
¶ Mark this message as not yet seen.
Templates used by this plugin¶
-
notify/body.eml
¶ A Jinja template used for generating the body of the email when sending a message per email to its recipient.
Available context variables:
obj
-- TheMessage
instance being sent.E
-- The html namespaceetgen.html
rt
-- The runtime APIlino.api.rt
ar
-- The action request which caused the message. aBaseRequest
instance.