Implementing Notifications in Open Event Server
In FOSSASIA’s Open Event Server project, along with emails, almost all actions have necessary user notifications as well. So, when a new session is created or a session is accepted by the event organisers, along with the email, a user notification is also sent. Though showing the user notification is mainly implemented in the frontend site but the content to be shown and on which action to show is strictly decided by the server project.
A notification essentially helps an user to get the necessary information while staying in the platform itself and not needing to go to check his/her email for every action he performs. So unlike email which acts as a backup for the informations, notification is more of an instant thing.
The API
The Notifications API is mostly like all other JSON API endpoints in the open event project. However in Notifications API we do not allow any to send a POST request. The admin of the server is able to send a GET a request to view all the notifications that are there in the system while a user can only view his/her notification. As of PATCH we allow only the user to edit his/her notification to mark it as read or not read. Following is the schema for the API:
class NotificationSchema(Schema): """ API Schema for Notification Model """ class Meta: """ Meta class for Notification API schema """ type_ = 'notification' self_view = 'v1.notification_detail' self_view_kwargs = {'id': '<id>'} self_view_many = 'v1.microlocation_list_post' inflect = dasherize id = fields.Str(dump_only=True) title = fields.Str(allow_none=True, dump_only=True) message = fields.Str(allow_none=True, dump_only=True) received_at = fields.DateTime(dump_only=True) accept = fields.Str(allow_none=True, dump_only=True) is_read = fields.Boolean() user = Relationship(attribute='user', self_view='v1.notification_user', self_view_kwargs={'id': '<id>'}, related_view='v1.user_detail', related_view_kwargs={'notification_id': '<id>'}, schema='UserSchema', type_='user' )
The main things that are shown in the notification from the frontend are the title and message. The title is the text that is shown without expanding the entire notification that gives an overview about the message in case you don’t want to read the entire message. The message however provides the entire detail that is associated with the action performed. The user relationship stores which user the particular notification is related with. It is a one-to-one relationship where one notification can only belong to one user. However one user can have multiple notifications. Another important attribute is the is_read attribute. This is the only attribute that is allowed to be changed. By default, when we make an entry in the database, is_read is set to FALSE. Once an user has read the notification, a request is sent from the frontend to change is_read to TRUE.
The different actions for which we send notification are stored in the models/notification.py file as global variables.
USER_CHANGE_EMAIL = "User email"' NEW_SESSION = 'New Session Proposal' PASSWORD_CHANGE = 'Change Password' EVENT_ROLE = 'Event Role Invitation' TICKET_PURCHASED = 'Ticket(s) Purchased' TICKET_PURCHASED_ATTENDEE = 'Ticket(s) purchased to Attendee ' EVENT_EXPORTED = 'Event Exported' EVENT_EXPORT_FAIL = 'Event Export Failed' EVENT_IMPORTED = 'Event Imported'
HTML Templates
The notification title and message that is stored in the database and later served via the Notification API is created using some string formatting HTML templates. We firstly import all the global variables that represent the various actions from the notification model. Then we declare a global dict type variable named NOTIFS which stores all title and messages to be stored in the notification table.
NEW_SESSION: { 'title': u'New session proposal for {event_name}', 'message': u"""The event <strong>{event_name}</strong> has received a new session proposal.<br><br> <a href='{link}' class='btn btn-info btn-sm'>View Session</a>""", 'recipient': 'Organizer', },
This is an example of the contents stored inside the dict. For every action, there is a dict with attributes title, message and recipient. Title contains the brief overview of the entire notification whereas message contains a more vivid description with proper links. Recipient contains the one who receives the notification. So for example in the above code snippet, it is a notification for a new session created. The notification goes to the organizer. The title and message contains named placeholders which are later replaced by particular values using python’s .format() function.
Notification Helper
Notification helper module contains two main parts :-
- A parent function which saves the notification to the table related to the user to whom the notification belongs.
- Individual notification helper functions that are used by the APIs to save notification after various actions are performed.
Parent Function
def send_notification(user, action, title, message): if not current_app.config['TESTING']: notification = Notification(user_id=user.id, title=title, message=message, action=action ) save_to_db(notification, msg="Notification saved") record_activity('notification_event', user=user, action=action, title=title)
send_notification() is the parent function which takes as parameters user, action, title and message and stores them in the notification table in the database. The user is the one to whom the notification belongs to, action represents the particular action in an API which triggered the notification. Title and message are the contents that are shown in the frontend in the form of a notification. The frontend can implement it as a dropdown notification like facebook or a desktop notification like gitter or whatsapp. After the notification is saved we also update the activity table with the action that a notification has been saved for a user with the following action and title. Later, the Notification API mentioned in the very beginning of the blog uses this data that is being stored now and serves it as a JSON response.
Individual Functions
Apart from this, we have individual functions that uses the parent function to store notifications particular to a particular actions. For example, we have a send_notif_new_session_organizer() function which is used to save notification for all the organizers of an event that a new session has been added to their particular event. This function is called when a POST request is made in the Sessions API and the data is saved successfully. The function is executed for all the organizers of the event for which the session has been created.
def send_notif_new_session_organizer(user, event_name, link): message_settings = MessageSettings.query.filter_by(action=NEW_SESSION).first() if not message_settings or message_settings.notification_status == 1: notif = NOTIFS[NEW_SESSION] action = NEW_SESSION title = notif['title'].format(event_name=event_name) message = notif['message'].format(event_name=event_name, link=link) send_notification(user, action, title, message)
In the above function, we take in 3 parameters, user, event_name and link. The value of the user parameter is used to link the notification to that particular user. Event_name and link are used in the title and message of the notification that is saved in the database. Firstly in the function we check if there is certain message setting which tells that the user doesn’t want to receive notifications related to new sessions being created. If not, we proceed. We get the title and message strings from the NOTIFS dict from the system_notification.py file.
After that, using string formatting we get the actual message. For example,
u'New session proposal for {event_name}'.format(‘FOSSASIA’)
would give us a resulting string of the form:
u'New session proposal for FOSSASIA'
After this, we use this variables and send them as parameters to the send_notification() parent function to save the notification properly.
Reference:
- Read about the notification API documentation: http://open-event-api.herokuapp.com/#notifications
- Read about the notification helper functions: https://github.com/fossasia/open-event-orga-server/blob/nextgen/app/api/helpers/notification.py
- Read more about various Python String formatting: https://pyformat.info/