notify: Notification framework

The lino.modlib.notify plugin adds a notification framework to your Lino application.

We assume that you have read the end-user documentation in notify: Notifications.

You can play with this plugin in the demo projects chatter, noi1e and noi1r.

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.doctest import *

Concepts

This page will introduce the following new concepts:

push notification

A technology used to implement desktop notifications.

https://rossta.net/blog/using-the-web-push-api-with-vapid.html

push subscription

The fact that a site user has given permission to some of their browsers to show desktop notifications for this site.

Usage

Add lino.modlib.notify to your lino.core.site.Site.get_installed_apps().

To emit a notification message from your application code, you can

You can use this plugin without enabling desktop notifications. In that case the site users will receive only email notifications and/or dashboard notifications.

How to activate desktop notifications

To enable desktop notifications, there are some requirements:

Set up a public URL for your development server

The Push API requires your web server to be publicly reachable via https. One method to do this for a development server is to use ngrok.

Install ngrok: https://ngrok.com/download

Run ngrok:

$ ngrok http 8000

ngrok by @inconshreveable                                                                                                                                     (Ctrl+C to quit)

Session Status                online
Account                       joe@example.com (Plan: Free)
Version                       2.3.40
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://b3735559b89b.ngrok.io -> http://localhost:8000
Forwarding                    https://b3735559b89b.ngrok.io -> http://localhost:8000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              268     0       0.00    0.00    0.50    1.39

In most terminals you can then Ctrl-click on the https://b3735559b89b.ngrok.io URL to open your browser on it.

More about the Push API

Push API

A technology for delivering desktop notifications.

Currently a Working Draft published by the W3C Web Applications Working Group, and intended to become a W3C Recommendation.

Unlike alternative technologies like WebSockets or server-sent events, the Push API uses a third-party push server.

Subject and body of a notification message

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 may get lost when the message is sent as an email or as a desktop notification.

Notification messages

You can use Explorer ‣ System ‣ Notification messages to see all notification messages.

>>> # run_menu_command("Explorer --> System --> Notification messages")
>>> rt.show('notify.AllMessages')  
============================ ==================================================== ============ ====== ======
 Created                      Subject                                              Recipient    seen   sent
---------------------------- ---------------------------------------------------- ------------ ------ ------
 2014-10-23 05:48:00          Broadcast message
 ...                          Andy commented on Hitchhiker's Guide to the Galaxy   Bert
 ...                          Bert commented on Hitchhiker's Guide to the Galaxy   Andy
 ...                          Robin Rood commented on Star Trek                    Chloe
 ...                          Andy commented on Star Trek                          Robin Rood
 ...                          Chloe commented on Harry Potter                      Andy
 ...                          Chloe commented on Harry Potter                      Bert
============================ ==================================================== ============ ====== ======
class lino.modlib.notify.Message

The Django model that represents a notification message.

subject

The subject of this message. See Subject and body of a notification message.

body

The body of this message. See Subject and body of a notification message.

user

The recipient of this message. The site user to whom this message is to be delivered.

If this is empty, then it is a broadcast notification.

owner

The owner of this message. Expresses what this message is about.

See The owner of a message.

This is a generic foreign key. If this is empty, the message is said to have no owner.

created

Timestamp of when this message has been emitted.

sent

Timestamp of when this message has been sent via email to its recipient.

seen

Timestamp of when the recipient of this message has marked it as seen.

emit_notification(cls, ar, owner, message_type, msg_func, recipients)

Emit a notification message to each the given recipients, respecting their individual user preferences.

This is a class method that creates zero, one or several database objects.

recipients is an iterable of (user, mail_mode) tuples. Duplicate items, items with user being None and items having mail_mode set to silent are removed.

msg_func is a callable expected to return either None or a tuple (subject, body). It is called for each recipient after having activated the recipient's language, and any translatable chunk of text will be translated to the user'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.

Push subscriptions

class lino.modlib.notify.Subscription

The Django model that represents a push subscription.

Loosely inspired by django-webpush.

user
lang
userAgent
endpoint
p256dh
auth

Change notifiers

class lino.modlib.notify.ChangeNotifier

Mixin for models that can emit notifications to a list of "observers" when an instance is modified.

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 example lino_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

A ntifying actions is an action that pops up a dialog window with at least three fields "Summary", "Description" and a checkbox "Don't notify others" to optionally suppress notification.

Screenshot of a notifying action:

../_images/reception.CheckinVisitor.png
class lino.modlib.notify.NotifyingAction

Mixin for notifying actions.

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 that 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.

Site configuration

lino.modlib.notify.remove_after

Automatically remove notification messages after x hours.

Default value is 24 hours. Set this to None or 0 to deactivate cleanup and keep messages forever.

lino.modlib.notify.keep_unseen

Whether to keep unseen messages when removing old messages according to remove_after.

In normal operation this should be True, but e.g. after a flood of messages during experimental phases we might want to get rid of them automatically.

lino.modlib.notify.mark_seen_when_sent

When this is True, Lino marks notification messages as seen when they have been sent via email.

lino.modlib.notify.use_push_api

Whether to enable desktop notifications using webpush.

In a production server it is mandatory to set your own vapid credentials:

lino.modlib.notify.vapid_private_key

The private VAPID key of this site.

lino.modlib.notify.vapid_public_key

The public VAPID key of this site.

lino.modlib.notify.vapid_admin_email

The VAPID contact address of this site.

lino.modlib.notify.use_websockets

Set this to True in order to activate use of websockets and channels.

If you use lino.modlib.notify and change this setting to True, then you need to install django-channels:

pip install channels
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
>>> dd.plugins.notify.use_push_api
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

class lino.modlib.notify.MessageTypes

The list of possible choices for the message_type field of a Message.

class lino.modlib.notify.MailModes

How the system should send email notifications to a user.

silent

Disable notifications for this user.

never

Notify in Lino but never send email.

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: