diff --git a/README.rst b/README.rst index 3492468c..9a522ced 100644 --- a/README.rst +++ b/README.rst @@ -51,7 +51,7 @@ Documentation - `Developer documentation `_ ------------- +---- Available features ------------------ @@ -60,10 +60,13 @@ Available features - `Web notifications <#web-notifications>`_ - `Email notifications <#email-notifications>`_ - `Notification types <#notification-types>`_ -- `Registering new notification types <#registering--unregistering-notification-types>`_ +- `Registering new notification types + <#registering--unregistering-notification-types>`_ - `User notification preferences <#notification-preferences>`_ -- `Silencing notifications for specific objects temporarily or permanently <#silencing-notifications-for-specific-objects-temporarily-or-permanently>`_ -- `Automatic cleanup of old notifications <#scheduled-deletion-of-notifications>`_ +- `Silencing notifications for specific objects temporarily or permanently + <#silencing-notifications-for-specific-objects-temporarily-or-permanently>`_ +- `Automatic cleanup of old notifications + <#scheduled-deletion-of-notifications>`_ - `Configurable host for API endpoints <#openwisp_notifications_host>`_ Installation instructions @@ -96,13 +99,14 @@ Alternatively, you can install via pip using git: Installing for development ~~~~~~~~~~~~~~~~~~~~~~~~~~ -We use Redis as celery broker (you can use a different broker if you want). -The recommended way for development is running it using Docker so you will need to -`install docker and docker-compose `_ beforehand. +We use Redis as celery broker (you can use a different broker if you +want). The recommended way for development is running it using Docker so +you will need to `install docker and docker-compose +`_ beforehand. -In case you prefer not to use Docker you can -`install Redis from your repositories `_, but keep in mind that -the version packaged by your distribution may be different. +In case you prefer not to use Docker you can `install Redis from your +repositories `_, but keep in mind that the +version packaged by your distribution may be different. Install SQLite: @@ -122,7 +126,8 @@ Navigate into the cloned repository: cd openwisp-notifications/ -Setup and activate a virtual-environment. (we'll be using `virtualenv `_) +Setup and activate a virtual-environment. (we'll be using `virtualenv +`_) .. code-block:: shell @@ -165,7 +170,7 @@ Launch the development server: You can access the admin interface at http://127.0.0.1:8000/admin/. -Run celery worker (separate terminal window is needed): +Run celery worker (separate terminal window is needed): .. code-block:: shell @@ -188,10 +193,11 @@ Run tests with: # If you running tests on PROD environment ./runtests.py --exclude skip_prod -When running the last line of the previous example, the environment variable ``SAMPLE_APP`` activates -the sample app in ``/tests/openwisp2/`` which is a simple django app that extends ``openwisp-notifications`` -with the sole purpose of testing its extensibility, for more information regarding this concept, -read the following section. +When running the last line of the previous example, the environment +variable ``SAMPLE_APP`` activates the sample app in ``/tests/openwisp2/`` +which is a simple django app that extends ``openwisp-notifications`` with +the sole purpose of testing its extensibility, for more information +regarding this concept, read the following section. Setup (integrate into an existing Django project) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -201,38 +207,38 @@ Setup (integrate into an existing Django project) .. code-block:: python INSTALLED_APPS = [ - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.sites', - 'django_extensions', - 'allauth', - 'allauth.account', - 'allauth.socialaccount', + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", + "django_extensions", + "allauth", + "allauth.account", + "allauth.socialaccount", # rest framework - 'rest_framework', - 'rest_framework.authtoken', - 'drf_yasg', - 'django_filters', - 'openwisp_users', + "rest_framework", + "rest_framework.authtoken", + "drf_yasg", + "django_filters", + "openwisp_users", # notifications module - 'openwisp_notifications', + "openwisp_notifications", # add openwisp theme # (must be loaded here) - 'openwisp_utils.admin_theme', + "openwisp_utils.admin_theme", # admin - 'admin_auto_filters', - 'django.contrib.admin', + "admin_auto_filters", + "django.contrib.admin", # channels - 'channels', + "channels", ] -**Note**: ``openwisp_utils.admin_theme`` and ``django.contrib.admin`` should always -follow ``openwisp_notifications`` in ``INSTALLED_APPS`` as shown in the example above. -It might result in undesired behavior otherwise, e.g. notification bell not being -shown on admin site. +**Note**: ``openwisp_utils.admin_theme`` and ``django.contrib.admin`` +should always follow ``openwisp_notifications`` in ``INSTALLED_APPS`` as +shown in the example above. It might result in undesired behavior +otherwise, e.g. notification bell not being shown on admin site. Add ``notification_api_settings`` context processor: @@ -241,11 +247,11 @@ Add ``notification_api_settings`` context processor: TEMPLATES = [ { # ... - 'OPTIONS': { + "OPTIONS": { # ... - 'context_processors': [ + "context_processors": [ # ... - 'openwisp_notifications.context_processors.notification_api_settings', + "openwisp_notifications.context_processors.notification_api_settings", # ... ], }, @@ -261,9 +267,15 @@ Add ``notification_api_settings`` context processor: from django.contrib.staticfiles.urls import staticfiles_urlpatterns urlpatterns = [ - path('admin/', admin.site.urls), - path('api/v1/', include(('openwisp_users.api.urls', 'users'), namespace='users')), - path('', include('openwisp_notifications.urls', namespace='notifications')), + path("admin/", admin.site.urls), + path( + "api/v1/", + include(("openwisp_users.api.urls", "users"), namespace="users"), + ), + path( + "", + include("openwisp_notifications.urls", namespace="notifications"), + ), ] urlpatterns += staticfiles_urlpatterns() @@ -279,7 +291,7 @@ Add routes for websockets: from openwisp_notifications.websockets.routing import get_routes application = ProtocolTypeRouter( - {'websocket': AuthMiddlewareStack(URLRouter(get_routes()))} + {"websocket": AuthMiddlewareStack(URLRouter(get_routes()))} ) Configure caching (you may use a different cache storage if you want): @@ -287,17 +299,17 @@ Configure caching (you may use a different cache storage if you want): .. code-block:: python CACHES = { - 'default': { - 'BACKEND': 'django_redis.cache.RedisCache', - 'LOCATION': 'redis://localhost/0', - 'OPTIONS': { - 'CLIENT_CLASS': 'django_redis.client.DefaultClient', - } + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://localhost/0", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, } } - SESSION_ENGINE = 'django.contrib.sessions.backends.cache' - SESSION_CACHE_ALIAS = 'default' + SESSION_ENGINE = "django.contrib.sessions.backends.cache" + SESSION_CACHE_ALIAS = "default" Configure celery: @@ -305,27 +317,27 @@ Configure celery: # Here we are showing how to configure celery with Redis but you can # use other brokers if you want, consult the celery docs - CELERY_BROKER_URL = 'redis://localhost/1' + CELERY_BROKER_URL = "redis://localhost/1" Configure celery beat: .. code-block:: python CELERY_BEAT_SCHEDULE = { - 'delete_old_notifications': { - 'task': 'openwisp_notifications.tasks.delete_old_notifications', - 'schedule': timedelta(days=1), - 'args': (90,), + "delete_old_notifications": { + "task": "openwisp_notifications.tasks.delete_old_notifications", + "schedule": timedelta(days=1), + "args": (90,), }, } -**Note**: You will only need to add ``CELERY_BEAT_SCHEDULE`` setting if you want -automatic deletion of old notifications. Please read -`Scheduled deletion of notifications <#scheduled-deletion-of-notifications>`_ -section to learn more about this feature. +**Note**: You will only need to add ``CELERY_BEAT_SCHEDULE`` setting if +you want automatic deletion of old notifications. Please read `Scheduled +deletion of notifications <#scheduled-deletion-of-notifications>`_ section +to learn more about this feature. -If you decide to use redis (as shown in these examples), make sure the python -dependencies are installed in your system: +If you decide to use redis (as shown in these examples), make sure the +python dependencies are installed in your system: .. code-block:: shell @@ -335,17 +347,18 @@ Configure ``ASGI_APPLICATION``: .. code-block:: python - ASGI_APPLICATION = 'yourproject.asgi.application' + ASGI_APPLICATION = "yourproject.asgi.application" -Configure channel layers (you may use a `different channel layer `_): +Configure channel layers (you may use a `different channel layer +`_): .. code-block:: python CHANNEL_LAYERS = { - 'default': { - 'BACKEND': 'channels_redis.core.RedisChannelLayer', - 'CONFIG': { - 'hosts': ['redis://localhost/7'], + "default": { + "BACKEND": "channels_redis.core.RedisChannelLayer", + "CONFIG": { + "hosts": ["redis://localhost/7"], }, }, } @@ -354,7 +367,7 @@ While development, you can configure it to localhost as shown below: .. code-block:: python - INTERNAL_IPS = ['127.0.0.1'] + INTERNAL_IPS = ["127.0.0.1"] Run migrations @@ -362,8 +375,9 @@ Run migrations ./manage.py migrate -**Note**: Running migrations is also required for creating `notification settings <#notification-preferences>`_ -apart from creating database schema. +**Note**: Running migrations is also required for creating `notification +settings <#notification-preferences>`_ apart from creating database +schema. Sending notifications --------------------- @@ -378,26 +392,28 @@ Notifications can be created using the ``notify`` signal. Eg: from openwisp_notifications.signals import notify User = get_user_model() - Group = load_model('openwisp_users', 'Group') - admin = User.objects.get(email='admin@admin.com') - operators = Group.objects.get(name='Operator') + Group = load_model("openwisp_users", "Group") + admin = User.objects.get(email="admin@admin.com") + operators = Group.objects.get(name="Operator") notify.send( sender=admin, recipient=operators, description="Test Notification", verb="Test Notification", - email_subject='Test Email Subject', - url='https://localhost:8000/admin', + email_subject="Test Email Subject", + url="https://localhost:8000/admin", ) -The above code snippet creates and sends a notification to all users belonging to the ``Operators`` -group if they have opted-in to receive notifications. Non-superusers receive notifications -only for organizations which they are a member of. +The above code snippet creates and sends a notification to all users +belonging to the ``Operators`` group if they have opted-in to receive +notifications. Non-superusers receive notifications only for organizations +which they are a member of. -**Note**: If recipient is not provided, it defaults to all superusers. If the target is provided, users -of same organization of the target object are added to the list of recipients given that they have staff -status and opted-in to receive notifications. +**Note**: If recipient is not provided, it defaults to all superusers. If +the target is provided, users of same organization of the target object +are added to the list of recipients given that they have staff status and +opted-in to receive notifications. The complete syntax for ``notify`` is: @@ -411,36 +427,35 @@ The complete syntax for ``notify`` is: target, level, description, - **kwargs + **kwargs, ) -**Note**: Since ``openwisp-notifications`` uses ``django-notifications`` under the hood, usage of the -``notify signal`` has been kept unaffected to maintain consistency with ``django-notifications``. -You can learn more about accepted parameters from `django-notifications documentation +**Note**: Since ``openwisp-notifications`` uses ``django-notifications`` +under the hood, usage of the ``notify signal`` has been kept unaffected to +maintain consistency with ``django-notifications``. You can learn more +about accepted parameters from `django-notifications documentation `_. Additional ``notify`` keyword arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+-------------------+-------------------------------------------------------------------+ -| **Parameter** | **Description** | -+-------------------+-------------------------------------------------------------------+ -| ``email_subject`` | Sets subject of email notification to be sent. | -| | | -| | Defaults to the notification message. | -+-------------------+-------------------------------------------------------------------+ -| ``url`` | Adds a URL in the email text, eg: | -| | | -| | ``For more information see .`` | -| | | -| | Defaults to ``None``, meaning the above message would | -| | not be added to the email text. | -+-------------------+-------------------------------------------------------------------+ -| ``type`` | Set values of other parameters based on registered | -| | `notification types <#notification-types>`_ | -| | | -| | Defaults to ``None`` meaning you need to provide other arguments. | -+-------------------+-------------------------------------------------------------------+ +================= ====================================================== +**Parameter** **Description** +``email_subject`` Sets subject of email notification to be sent. + + Defaults to the notification message. +``url`` Adds a URL in the email text, eg: + + ``For more information see .`` + + Defaults to ``None``, meaning the above message would + not be added to the email text. +``type`` Set values of other parameters based on registered + `notification types <#notification-types>`_ + + Defaults to ``None`` meaning you need to provide other + arguments. +================= ====================================================== Web Notifications ----------------- @@ -453,14 +468,14 @@ Notification Widget ~~~~~~~~~~~~~~~~~~~ .. figure:: https://github.com/openwisp/openwisp-notifications/raw/docs/docs/images/notification-widget.gif - :align: center + :align: center -A javascript widget has been added to make consuming notifications easy for users. -The notification widget provides following features: +A javascript widget has been added to make consuming notifications easy +for users. The notification widget provides following features: - A minimalistic UI to help getting things done quickly. -- Dynamically loading notifications with infinite scrolling to prevent unnecessary - network requests. +- Dynamically loading notifications with infinite scrolling to prevent + unnecessary network requests. - Option to filter unread notifications. - Option to mark all notifications as read on a single click. @@ -468,7 +483,7 @@ Notification Toasts ~~~~~~~~~~~~~~~~~~~ .. figure:: https://github.com/openwisp/openwisp-notifications/raw/docs/docs/images/notification-toast.gif - :align: center + :align: center A notification toast delivers notifications at real-time. This allows users to read notifications without even opening the notification widget. @@ -480,84 +495,98 @@ Email Notifications .. figure:: https://github.com/openwisp/openwisp-notifications/raw/docs/docs/images/email-template.png -Along with web notifications *OpenWISP Notifications* also sends email notifications -leveraging the `openwisp-utils send_email feature +Along with web notifications *OpenWISP Notifications* also sends email +notifications leveraging the `openwisp-utils send_email feature `_. - Notification Cache ------------------ -In a typical OpenWISP installation, ``actor``, ``action_object`` and ``target`` objects are same -for a number of notifications. To optimize database queries, these objects are cached using -`Django's cache framework `_. -The cached values are updated automatically to reflect actual data from database. You can control -the duration of caching these objects using -`OPENWISP_NOTIFICATIONS_CACHE_TIMEOUT setting <#OPENWISP_NOTIFICATIONS_CACHE_TIMEOUT>`_. +In a typical OpenWISP installation, ``actor``, ``action_object`` and +``target`` objects are same for a number of notifications. To optimize +database queries, these objects are cached using `Django's cache framework +`_. The cached values +are updated automatically to reflect actual data from database. You can +control the duration of caching these objects using +`OPENWISP_NOTIFICATIONS_CACHE_TIMEOUT setting +<#OPENWISP_NOTIFICATIONS_CACHE_TIMEOUT>`_. Cache invalidation ~~~~~~~~~~~~~~~~~~ -The function ``register_notification_cache_update`` can be used to register a signal of a model which is being used as an -``actor``, ``action_object`` and ``target`` objects. As these values are cached for the optimization purpose so their cached -values are need to be changed when they are changed. You can register any signal you want which will delete the cached value. -To register a signal you need to include following code in your ``apps.py``. +The function ``register_notification_cache_update`` can be used to +register a signal of a model which is being used as an ``actor``, +``action_object`` and ``target`` objects. As these values are cached for +the optimization purpose so their cached values are need to be changed +when they are changed. You can register any signal you want which will +delete the cached value. To register a signal you need to include +following code in your ``apps.py``. .. code-block:: python from django.db.models.signals import post_save from swapper import load_model + def ready(self): super().ready() # Include lines after this inside # ready function of you app config class - from openwisp_notifications.handlers import register_notification_cache_update + from openwisp_notifications.handlers import ( + register_notification_cache_update, + ) - model = load_model('app_name', 'model_name') - register_notification_cache_update(model, post_save, dispatch_uid="myapp_mymodel_notification_cache_invalidation") + model = load_model("app_name", "model_name") + register_notification_cache_update( + model, + post_save, + dispatch_uid="myapp_mymodel_notification_cache_invalidation", + ) -**Note**: You need to import ``register_notification_cache_update`` inside the ``ready`` function or -you can define another funtion to register signals which will be called in ``ready`` and then it will be -imported in this function. Also ``dispatch_uid`` is unique identifier of a signal. You can pass any -value you want but it needs to be unique. For more details read `preventing duplicate signals section of Django documentation `_ +**Note**: You need to import ``register_notification_cache_update`` inside +the ``ready`` function or you can define another funtion to register +signals which will be called in ``ready`` and then it will be imported in +this function. Also ``dispatch_uid`` is unique identifier of a signal. You +can pass any value you want but it needs to be unique. For more details +read `preventing duplicate signals section of Django documentation +`_ Notification Types ------------------ **OpenWISP Notifications** allows defining notification types for -recurring events. Think of a notification type as a template -for notifications. +recurring events. Think of a notification type as a template for +notifications. ``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 + :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. +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. +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: +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!', + 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 @@ -574,7 +603,7 @@ of this type: 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*.""" + software like Aldus PageMaker including versions of *Lorem Ipsum*.""", ) Properties of Notification Types @@ -582,54 +611,47 @@ Properties of Notification Types The following properties can be configured for each notification type: -+------------------------+----------------------------------------------------------------+ -| **Property** | **Description** | -+------------------------+----------------------------------------------------------------+ -| ``level`` | Sets ``level`` attribute of the notification. | -+------------------------+----------------------------------------------------------------+ -| ``verb`` | Sets ``verb`` attribute of the notification. | -+------------------------+----------------------------------------------------------------+ -| ``verbose_name`` | Sets display name of notification type. | -+------------------------+----------------------------------------------------------------+ -| ``message`` | Sets ``message`` attribute of the notification. | -+------------------------+----------------------------------------------------------------+ -| ``email_subject`` | Sets subject of the email notification. | -+------------------------+----------------------------------------------------------------+ -| ``message_template`` | Path to file having template for message of the notification. | -+------------------------+----------------------------------------------------------------+ -| ``email_notification`` | Sets preference for email notifications. Defaults to ``True``. | -+------------------------+----------------------------------------------------------------+ -| ``web_notification`` | Sets preference for web notifications. Defaults to ``True``. | -+------------------------+----------------------------------------------------------------+ -| ``actor_link`` | Overrides the default URL used for the ``actor`` object. | -| | | -| | You can pass a static URL or a dotted path to a callable | -| | which returns the object URL. | -+------------------------+----------------------------------------------------------------+ -| ``action_object_link`` | Overrides the default URL used for the ``action`` object. | -| | | -| | You can pass a static URL or a dotted path to a callable | -| | which returns the object URL. | -+------------------------+----------------------------------------------------------------+ -| ``target_link`` | Overrides the default URL used for the ``target`` object. | -| | | -| | You can pass a static URL or a dotted path to a callable | -| | which returns the object URL. | -+------------------------+----------------------------------------------------------------+ - - -**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: +====================== ================================================== +**Property** **Description** +``level`` Sets ``level`` attribute of the notification. +``verb`` Sets ``verb`` attribute of the notification. +``verbose_name`` Sets display name of notification type. +``message`` Sets ``message`` attribute of the notification. +``email_subject`` Sets subject of the email notification. +``message_template`` Path to file having template for message of the + notification. +``email_notification`` Sets preference for email notifications. Defaults + to ``True``. +``web_notification`` Sets preference for web notifications. Defaults to + ``True``. +``actor_link`` Overrides the default URL used for the ``actor`` + object. + + You can pass a static URL or a dotted path to a + callable which returns the object URL. +``action_object_link`` Overrides the default URL used for the ``action`` + object. + + You can pass a static URL or a dotted path to a + callable which returns the object URL. +``target_link`` Overrides the default URL used for the ``target`` + object. + + You can pass a static URL or a dotted path to a + callable which returns the object URL. +====================== ================================================== + +**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: .. code-block:: python @@ -640,13 +662,14 @@ have the following signature: "target" field of the notification object absolute_url: boolean to flag if absolute URL should be returned """ - return 'https://custom.domain.com/custom/url/' + return "https://custom.domain.com/custom/url/" Defining ``message_template`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can either extend default message template or write your own markdown formatted message template -from scratch. An example to extend default message template is shown below. +You can either extend default message template or write your own markdown +formatted message template from scratch. An example to extend default +message template is shown below. .. code-block:: django @@ -656,19 +679,25 @@ from scratch. An example to extend default message template is shown below. [{{ notification.target }}]({{ notification.target_link }}) has malfunctioned. {% endblock body %} -**Note**: You can access all attributes of the notification using ``notification`` variables in your message -template as shown above. Additional attributes ``actor_link``, ``action_link`` and ``target_link`` are -also available for providing hyperlinks to respective object. +**Note**: You can access all attributes of the notification using +``notification`` variables in your message template as shown above. +Additional attributes ``actor_link``, ``action_link`` and ``target_link`` +are also available for providing hyperlinks to respective object. -**Note**: After writing code for registering or unregistering notification types, it is recommended to run -database migrations to create `notification settlings <#notification-preferences>`_ for these notification types. +**Note**: After writing code for registering or unregistering notification +types, it is recommended to run database migrations to create +`notification settlings <#notification-preferences>`_ for these +notification types. Registering / Unregistering Notification Types ---------------------------------------------- -**OpenWISP Notifications** provides registering and unregistering notifications through utility functions -``openwisp_notifications.types.register_notification_type`` and ``openwisp_notifications.types.unregister_notification_type``. -Using these functions you can register or unregister notification types from your code. +**OpenWISP Notifications** provides registering and unregistering +notifications through utility functions +``openwisp_notifications.types.register_notification_type`` and +``openwisp_notifications.types.unregister_notification_type``. Using these +functions you can register or unregister notification types from your +code. register_notification_type ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -681,16 +710,13 @@ Syntax: register_notification_type(type_name, type_config, models) -+---------------+-------------------------------------------------------------+ -| **Parameter** | **Description** | -+---------------+-------------------------------------------------------------+ -| type_name | A ``str`` defining name of the notification type. | -+---------------+-------------------------------------------------------------+ -| type_config | A ``dict`` defining configuration of the notification type. | -+---------------+-------------------------------------------------------------+ -| models | An optional ``list`` of models that can be associated with | -| | the notification type. | -+---------------+-------------------------------------------------------------+ +============= =========================================================== +**Parameter** **Description** +type_name A ``str`` defining name of the notification type. +type_config A ``dict`` defining configuration of the notification type. +models An optional ``list`` of models that can be associated with + the notification type. +============= =========================================================== An example usage has been shown below. @@ -703,35 +729,39 @@ An example usage has been shown below. # Define configuration of your notification type custom_type = { - 'level': 'info', - 'verb': 'added', - 'verbose_name': 'device added', - 'message': '[{notification.target}]({notification.target_link}) was {notification.verb} at {notification.timestamp}', - 'email_subject' : '[{site.name}] A device has been added', - 'web_notification': True, - 'email_notification': True, + "level": "info", + "verb": "added", + "verbose_name": "device added", + "message": "[{notification.target}]({notification.target_link}) was {notification.verb} at {notification.timestamp}", + "email_subject": "[{site.name}] A device has been added", + "web_notification": True, + "email_notification": True, # static URL for the actor object - 'actor': 'https://openwisp.org/admin/config/device', + "actor": "https://openwisp.org/admin/config/device", # URL generation using callable for target object - 'target': 'mymodule.target_object_link' + "target": "mymodule.target_object_link", } # Register your custom notification type - register_notification_type('custom_type', custom_type, models=[User]) + register_notification_type("custom_type", custom_type, models=[User]) -**Note**: It will raise ``ImproperlyConfigured`` exception if a notification type is already registered -with same name(not to be confused with ``verbose_name``). +**Note**: It will raise ``ImproperlyConfigured`` exception if a +notification type is already registered with same name(not to be confused +with ``verbose_name``). -**Note**: You can use ``site`` and ``notification`` variables while defining ``message`` and -``email_subject`` configuration of notification type. They refer to objects of -``django.contrib.sites.models.Site`` and ``openwisp_notifications.models.Notification`` respectively. -This allows you to use any of their attributes in your configuration. Similarly to ``message_template``, -``message`` property can also be formatted using markdown. +**Note**: You can use ``site`` and ``notification`` variables while +defining ``message`` and ``email_subject`` configuration of notification +type. They refer to objects of ``django.contrib.sites.models.Site`` and +``openwisp_notifications.models.Notification`` respectively. This allows +you to use any of their attributes in your configuration. Similarly to +``message_template``, ``message`` property can also be formatted using +markdown. unregister_notification_type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This function is used to unregister a notification type from anywhere in your code. +This function is used to unregister a notification type from anywhere in +your code. Syntax: @@ -739,11 +769,10 @@ Syntax: unregister_notification_type(type_name) -+---------------+---------------------------------------------------+ -| **Parameter** | **Description** | -+---------------+---------------------------------------------------+ -| type_name | A ``str`` defining name of the notification type. | -+---------------+---------------------------------------------------+ +============= ================================================= +**Parameter** **Description** +type_name A ``str`` defining name of the notification type. +============= ================================================= An example usage is shown below. @@ -752,18 +781,20 @@ An example usage is shown below. from openwisp_notifications.types import unregister_notification_type # Unregister previously registered notification type - unregister_notification_type('custom type') + unregister_notification_type("custom type") -**Note**: It will raise ``ImproperlyConfigured`` exception if the concerned -notification type is not registered. +**Note**: It will raise ``ImproperlyConfigured`` exception if the +concerned notification type is not registered. Passing extra data to notifications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If needed, additional data, not known beforehand, can be included in the notification message. +If needed, additional data, not known beforehand, can be included in the +notification message. -A perfect example for this case is an error notification, the error message will vary -depending on what has happened, so we cannot know until the notification is generated. +A perfect example for this case is an error notification, the error +message will vary depending on what has happened, so we cannot know until +the notification is generated. Here's how to do it: @@ -771,13 +802,16 @@ Here's how to do it: from openwisp_notifications.types import register_notification_type - register_notification_type('error_type', { - 'verbose_name': 'Error', - 'level': 'error', - 'verb': 'error', - 'message': 'Error: {error}', - 'email_subject': 'Error subject: {error}', - }) + register_notification_type( + "error_type", + { + "verbose_name": "Error", + "level": "error", + "verb": "error", + "message": "Error: {error}", + "email_subject": "Error subject: {error}", + }, + ) Then in the application code: @@ -788,84 +822,94 @@ Then in the application code: try: operation_which_can_fail() except Exception as error: - notify.send( - type='error_type', - sender=sender, - error=str(error) - ) + notify.send(type="error_type", sender=sender, error=str(error)) **Note**: It is recommended that all notification types are registered or -unregistered in ``ready`` method of your Django application's ``AppConfig``. +unregistered in ``ready`` method of your Django application's +``AppConfig``. Notification Preferences ------------------------ .. image:: https://github.com/openwisp/openwisp-notifications/raw/docs/docs/images/notification-settings.png -*OpenWISP Notifications* allows users to select their preferred way of receiving notifications. -Users can choose from web or email notifications. These settings have been categorized -over notification type and organization, therefore allowing users to only receive notifications -from selected organization or notification type. - -Notification settings are automatically created for all notification types and organizations for all users. -While superusers can add or delete notification settings for everyone, staff users can only modify their -preferred ways for receiving notifications. With provided functionality, users can choose to receive both -web and email notifications or only web notifications. Users can also stop receiving notifications -by disabling both web and email option for a notification setting. - -**Note**: If a user has not configured their email or web preference for a particular notification setting, -then ``email_notification`` or ``web_notification`` option of concerned notification type will be used +*OpenWISP Notifications* allows users to select their preferred way of +receiving notifications. Users can choose from web or email notifications. +These settings have been categorized over notification type and +organization, therefore allowing users to only receive notifications from +selected organization or notification type. + +Notification settings are automatically created for all notification types +and organizations for all users. While superusers can add or delete +notification settings for everyone, staff users can only modify their +preferred ways for receiving notifications. With provided functionality, +users can choose to receive both web and email notifications or only web +notifications. Users can also stop receiving notifications by disabling +both web and email option for a notification setting. + +**Note**: If a user has not configured their email or web preference for a +particular notification setting, then ``email_notification`` or +``web_notification`` option of concerned notification type will be used respectively. Deleting Notification Preferences ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Deleting the notification preferences is an advanced option. Users should turn off web and email -notifications instead of deleting notification preferences. Deleted notification preferences -may be re-created automatically if the system needs it. +Deleting the notification preferences is an advanced option. Users should +turn off web and email notifications instead of deleting notification +preferences. Deleted notification preferences may be re-created +automatically if the system needs it. Silencing notifications for specific objects temporarily or permanently ----------------------------------------------------------------------- .. image:: https://github.com/openwisp/openwisp-notifications/raw/docs/docs/images/silence-notifications.png - :align: center + :align: center -*OpenWISP Notifications* allows users to silence all notifications generated by -specific objects they are not interested in for a desired period of time or even permanently, -while other users will keep receiving notifications normally. +*OpenWISP Notifications* allows users to silence all notifications +generated by specific objects they are not interested in for a desired +period of time or even permanently, while other users will keep receiving +notifications normally. -Using the widget on an object's admin change form, a user can disable all notifications -generated by that object for a day, week, month or permanently. +Using the widget on an object's admin change form, a user can disable all +notifications generated by that object for a day, week, month or +permanently. **Note**: This feature requires configuring -`"OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN" <#openwisp_notifications_ignore_enabled_admin>`_ -to enable the widget in the admin section of the required models. +`"OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN" +<#openwisp_notifications_ignore_enabled_admin>`_ to enable the widget in +the admin section of the required models. Scheduled deletion of notifications ----------------------------------- *OpenWISP Notifications* provides a celery task to automatically delete -notifications older than a pre-configured number of days. In order to run this -task periodically, you will need to configure ``CELERY_BEAT_SCHEDULE`` setting as shown -in `setup instructions <#setup-integrate-into-an-existing-django-project>`_. +notifications older than a pre-configured number of days. In order to run +this task periodically, you will need to configure +``CELERY_BEAT_SCHEDULE`` setting as shown in `setup instructions +<#setup-integrate-into-an-existing-django-project>`_. -The celery task takes only one argument, i.e. number of days. You can provide -any number of days in `args` key while configuring ``CELERY_BEAT_SCHEDULE`` setting. +The celery task takes only one argument, i.e. number of days. You can +provide any number of days in `args` key while configuring +``CELERY_BEAT_SCHEDULE`` setting. -E.g., if you want notifications older than 10 days to get deleted automatically, -then configure ``CELERY_BEAT_SCHEDULE`` as follows: +E.g., if you want notifications older than 10 days to get deleted +automatically, then configure ``CELERY_BEAT_SCHEDULE`` as follows: .. code-block:: python CELERY_BEAT_SCHEDULE = { - 'delete_old_notifications': { - 'task': 'openwisp_notifications.tasks.delete_old_notifications', - 'schedule': timedelta(days=1), - 'args': (10,), # Here we have defined 10 instead of 90 as shown in setup instructions + "delete_old_notifications": { + "task": "openwisp_notifications.tasks.delete_old_notifications", + "schedule": timedelta(days=1), + "args": ( + 10, + ), # Here we have defined 10 instead of 90 as shown in setup instructions }, } -Please refer to `"Periodic Tasks" section of Celery's documentation `_ +Please refer to `"Periodic Tasks" section of Celery's documentation +`_ to learn more. Settings @@ -874,109 +918,114 @@ Settings ``OPENWISP_NOTIFICATIONS_HOST`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+---------+----------------------------------------+ -| type | ``str`` | -+---------+----------------------------------------+ -| default | Any domain defined in ``ALLOWED_HOST`` | -+---------+----------------------------------------+ +======= ====================================== +type ``str`` +default Any domain defined in ``ALLOWED_HOST`` +======= ====================================== -This setting defines the domain at which API and Web Socket communicate for -working of notification widget. +This setting defines the domain at which API and Web Socket communicate +for working of notification widget. -**Note**: You don't need to configure this setting if you -don't host your API endpoints on a different sub-domain. +**Note**: You don't need to configure this setting if you don't host your +API endpoints on a different sub-domain. -If your root domain is ``example.com`` and API and Web Socket are hosted at -``api.example.com``, then configure setting as follows: +If your root domain is ``example.com`` and API and Web Socket are hosted +at ``api.example.com``, then configure setting as follows: .. code-block:: python - OPENWISP_NOTIFICATIONS_HOST = 'https://api.example.com' + OPENWISP_NOTIFICATIONS_HOST = "https://api.example.com" -This feature requires you to allow `CORS `_ -on your server. We use ``django-cors-headers`` module to easily setup CORS headers. -Please refer `django-core-headers' setup documentation `_. +This feature requires you to allow `CORS +`_ on your server. +We use ``django-cors-headers`` module to easily setup CORS headers. Please +refer `django-core-headers' setup documentation +`_. Configure ``django-cors-headers`` settings as follows: .. code-block:: python CORS_ALLOW_CREDENTIALS = True - CORS_ORIGIN_WHITELIST = ['https://www.example.com'] + CORS_ORIGIN_WHITELIST = ["https://www.example.com"] Configure Django's settings as follows: .. code-block:: python - SESSION_COOKIE_DOMAIN = 'example.com' - CSRF_COOKIE_DOMAIN = 'example.com' + SESSION_COOKIE_DOMAIN = "example.com" + CSRF_COOKIE_DOMAIN = "example.com" -Please refer to `Django's settings documentation `_ -for more information on ``SESSION_COOKIE_DOMAIN`` and ``CSRF_COOKIE_DOMAIN`` settings. +Please refer to `Django's settings documentation +`_ for more +information on ``SESSION_COOKIE_DOMAIN`` and ``CSRF_COOKIE_DOMAIN`` +settings. ``OPENWISP_NOTIFICATIONS_SOUND`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+---------+-------------------------------------------------------------------------------------------+ -| type | ``str`` | -+---------+-------------------------------------------------------------------------------------------+ -| default | `notification_bell.mp3 `_ | -+---------+-------------------------------------------------------------------------------------------+ +======= =================================================================================================================================================== +type ``str`` +default `notification_bell.mp3 + `_ +======= =================================================================================================================================================== -This setting defines notification sound to be played when notification is received -in real-time on admin site. +This setting defines notification sound to be played when notification is +received in real-time on admin site. -Provide a relative path (hosted on your webserver) to audio file as show below. +Provide a relative path (hosted on your webserver) to audio file as show +below. .. code-block:: python - OPENWISP_NOTIFICATIONS_SOUND = 'your-appname/audio/notification.mp3' + OPENWISP_NOTIFICATIONS_SOUND = "your-appname/audio/notification.mp3" ``OPENWISP_NOTIFICATIONS_CACHE_TIMEOUT`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+---------+-----------------------------------+ -| type | ``int`` | -+---------+-----------------------------------+ -| default | ``172800`` `(2 days, in seconds)` | -+---------+-----------------------------------+ +======= ================================= +type ``int`` +default ``172800`` `(2 days, in seconds)` +======= ================================= -It sets the number of seconds the notification contents should be stored in the cache. -If you want cached notification content to never expire, then set it to ``None``. -Set it to ``0`` if you don't want to store notification contents in cache at all. +It sets the number of seconds the notification contents should be stored +in the cache. If you want cached notification content to never expire, +then set it to ``None``. Set it to ``0`` if you don't want to store +notification contents in cache at all. ``OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+-----------+-----------+ -| type | ``list`` | -+-----------+-----------+ -| default | [] | -+-----------+-----------+ +======= ======== +type ``list`` +default [] +======= ======== -This setting enables the widget which allows users to -`silence notifications for specific objects temporarily or permanently. <#silencing-notifications-for-specific-objects-temporarily-or-permanently>`_ +This setting enables the widget which allows users to `silence +notifications for specific objects temporarily or permanently. +<#silencing-notifications-for-specific-objects-temporarily-or-permanently>`_ in the change page of the specified ``ModelAdmin`` classes. -E.g., if you want to enable the widget for objects of ``openwisp_users.models.User`` -model, then configure the setting as following: +E.g., if you want to enable the widget for objects of +``openwisp_users.models.User`` model, then configure the setting as +following: .. code-block:: python - OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN = ['openwisp_users.admin.UserAdmin'] + OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN = [ + "openwisp_users.admin.UserAdmin" + ] ``OPENWISP_NOTIFICATIONS_POPULATE_PREFERENCES_ON_MIGRATE`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+---------+----------+ -| type | ``bool`` | -+---------+----------+ -| default | ``True`` | -+---------+----------+ +======= ======== +type ``bool`` +default ``True`` +======= ======== -This setting allows to disable creating `notification preferences <#notification-preferences>`_ -on running migrations. +This setting allows to disable creating `notification preferences +<#notification-preferences>`_ on running migrations. ``OPENWISP_NOTIFICATIONS_NOTIFICATION_STORM_PREVENTION`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -986,8 +1035,8 @@ general network outage (e.g.: a power outage, a global misconfiguration), the notification storm prevention mechanism avoids the constant displaying of new notification alerts as well as their sound, only the notification counter will continue updating periodically, although it won't emit any -sound or create any other visual element until the -notification storm is over. +sound or create any other visual element until the notification storm is +over. This setting allows tweaking how this mechanism works. @@ -997,44 +1046,44 @@ The default configuration is as follows: OPENWISP_NOTIFICATIONS_NOTIFICATION_STORM_PREVENTION = { # Time period for tracking burst of notifications (in seconds) - 'short_term_time_period': 10, + "short_term_time_period": 10, # Number of notifications considered as a notification burst - 'short_term_notification_count': 6, + "short_term_notification_count": 6, # Time period for tracking notifications in long time interval (in seconds) - 'long_term_time_period': 180, + "long_term_time_period": 180, # Number of notifications in long time interval to be considered as a notification storm - 'long_term_notification_count': 30, + "long_term_notification_count": 30, # Initial time for which notification updates should be skipped (in seconds) - 'initial_backoff': 1, + "initial_backoff": 1, # Time by which skipping of notification updates should be increased (in seconds) - 'backoff_increment': 1, + "backoff_increment": 1, # Maximum interval after which the notification widget should get updated (in seconds) - 'max_allowed_backoff': 15, + "max_allowed_backoff": 15, } ``OPENWISP_NOTIFICATIONS_EMAIL_BATCH_INTERVAL`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+---------+-----------------------------------+ -| type | ``int`` | -+---------+-----------------------------------+ -| default | ``1800`` `(30 mins, in seconds)` | -+---------+-----------------------------------+ +======= ================================ +type ``int`` +default ``1800`` `(30 mins, in seconds)` +======= ================================ -This setting defines the interval at which the email notifications are sent in batches to users within the specified interval. +This setting defines the interval at which the email notifications are +sent in batches to users within the specified interval. If you want to send email notifications immediately, then set it to ``0``. ``OPENWISP_NOTIFICATIONS_EMAIL_BATCH_DISPLAY_LIMIT`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+---------+-----------------------------------+ -| type | ``int`` | -+---------+-----------------------------------+ -| default | ``15`` | -+---------+-----------------------------------+ +======= ======= +type ``int`` +default ``15`` +======= ======= -This setting defines the number of email notifications to be displayed in a batched email. +This setting defines the number of email notifications to be displayed in +a batched email. Exceptions ---------- @@ -1046,11 +1095,13 @@ Exceptions openwisp_notifications.exceptions.NotificationRenderException -Raised when notification properties(``email`` or ``message``) cannot be rendered from -concerned *notification type*. It sub-classes ``Exception`` class. +Raised when notification properties(``email`` or ``message``) cannot be +rendered from concerned *notification type*. It sub-classes ``Exception`` +class. -It can be raised due to accessing non-existing keys like missing related objects -in ``email`` or ``message`` setting of concerned *notification type*. +It can be raised due to accessing non-existing keys like missing related +objects in ``email`` or ``message`` setting of concerned *notification +type*. REST API -------- @@ -1060,17 +1111,19 @@ Live documentation .. image:: https://github.com/openwisp/openwisp-notifications/raw/docs/docs/images/api-docs.png -A general live API documentation (following the OpenAPI specification) is available at ``/api/v1/docs/``. +A general live API documentation (following the OpenAPI specification) is +available at ``/api/v1/docs/``. Browsable web interface ~~~~~~~~~~~~~~~~~~~~~~~ .. image:: https://github.com/openwisp/openwisp-notifications/raw/docs/docs/images/api-ui.png -Additionally, opening any of the endpoints `listed below <#list-of-endpoints>`_ -directly in the browser will show the `browsable API interface of Django-REST-Framework -`_, -which makes it even easier to find out the details of each endpoint. +Additionally, opening any of the endpoints `listed below +<#list-of-endpoints>`_ directly in the browser will show the `browsable +API interface of Django-REST-Framework +`_, which +makes it even easier to find out the details of each endpoint. Authentication ~~~~~~~~~~~~~~ @@ -1085,8 +1138,8 @@ the session authentication by logging in the django admin. Pagination ~~~~~~~~~~ -The *list* endpoint support the ``page_size`` parameter that allows paginating -the results in conjunction with the ``page`` parameter. +The *list* endpoint support the ``page_size`` parameter that allows +paginating the results in conjunction with the ``page`` parameter. .. code-block:: text @@ -1096,13 +1149,14 @@ the results in conjunction with the ``page`` parameter. List of endpoints ~~~~~~~~~~~~~~~~~ -Since the detailed explanation is contained in the `Live documentation <#live-documentation>`_ -and in the `Browsable web page <#browsable-web-interface>`_ of each endpoint, -here we'll provide just a list of the available endpoints, -for further information please open the URL of the endpoint in your browser. +Since the detailed explanation is contained in the `Live documentation +<#live-documentation>`_ and in the `Browsable web page +<#browsable-web-interface>`_ of each endpoint, here we'll provide just a +list of the available endpoints, for further information please open the +URL of the endpoint in your browser. List user's notifications -######################### ++++++++++++++++++++++++++ .. code-block:: text @@ -1110,8 +1164,8 @@ List user's notifications **Available Filters** -You can filter the list of notifications based on -whether they are read or unread using the ``unread`` parameter. +You can filter the list of notifications based on whether they are read or +unread using the ``unread`` parameter. To list read notifications: @@ -1126,35 +1180,35 @@ To list unread notifications: GET /api/v1/notifications/notification/?unread=true Mark all user's notifications as read -##################################### ++++++++++++++++++++++++++++++++++++++ .. code-block:: text POST /api/v1/notifications/notification/read/ Get notification details -######################## +++++++++++++++++++++++++ .. code-block:: text GET /api/v1/notifications/notification/{pk}/ Mark a notification read -######################## +++++++++++++++++++++++++ .. code-block:: text PATCH /api/v1/notifications/notification/{pk}/ Delete a notification -##################### ++++++++++++++++++++++ .. code-block:: text DELETE /api/v1/notifications/notification/{pk}/ List user's notification setting -################################ +++++++++++++++++++++++++++++++++ .. code-block:: text @@ -1162,78 +1216,77 @@ List user's notification setting **Available Filters** -You can filter the list of user's -notification setting based on their ``organization_id``. +You can filter the list of user's notification setting based on their +``organization_id``. .. code-block:: text GET /api/v1/notifications/notification/user-setting/?organization={organization_id} -You can filter the list of user's -notification setting based on their ``organization_slug``. +You can filter the list of user's notification setting based on their +``organization_slug``. .. code-block:: text GET /api/v1/notifications/notification/user-setting/?organization_slug={organization_slug} -You can filter the list of user's -notification setting based on their ``type``. +You can filter the list of user's notification setting based on their +``type``. .. code-block:: text GET /api/v1/notifications/notification/user-setting/?type={type} Get notification setting details -################################ +++++++++++++++++++++++++++++++++ .. code-block:: text GET /api/v1/notifications/notification/user-setting/{pk}/ Update notification setting details -################################### ++++++++++++++++++++++++++++++++++++ .. code-block:: text PATCH /api/v1/notifications/notification/user-setting/{pk}/ List user's object notification setting -####################################### ++++++++++++++++++++++++++++++++++++++++ .. code-block:: text GET /api/v1/notifications/notification/ignore/ Get object notification setting details -####################################### ++++++++++++++++++++++++++++++++++++++++ .. code-block:: text GET /api/v1/notifications/notification/ignore/{app_label}/{model_name}/{object_id}/ Create object notification setting -################################## +++++++++++++++++++++++++++++++++++ .. code-block:: text PUT /api/v1/notifications/notification/ignore/{app_label}/{model_name}/{object_id}/ Delete object notification setting -################################## +++++++++++++++++++++++++++++++++++ .. code-block:: text DELETE /api/v1/notifications/notification/ignore/{app_label}/{model_name}/{object_id}/ - Management Commands ------------------- ``populate_notification_preferences`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This command will populate notification preferences for all users for organizations -they are member of. +This command will populate notification preferences for all users for +organizations they are member of. Example usage: @@ -1248,9 +1301,9 @@ running and **reachable** by celery workers. ``create_notification`` ~~~~~~~~~~~~~~~~~~~~~~~ -This command will create a dummy notification with ``default`` notification type -for the members of ``default`` organization. -This command is primarily provided for the sole purpose of testing notification +This command will create a dummy notification with ``default`` +notification type for the members of ``default`` organization. This +command is primarily provided for the sole purpose of testing notification in development only. Example usage: @@ -1263,58 +1316,67 @@ Example usage: Extending openwisp-notifications -------------------------------- -One of the core values of the OpenWISP project is `Software Reusability `_, -for this reason *OpenWISP Notifications* provides a set of base classes which can be imported, extended -and reused to create derivative apps. +One of the core values of the OpenWISP project is `Software Reusability +`_, +for this reason *OpenWISP Notifications* provides a set of base classes +which can be imported, extended and reused to create derivative apps. -In order to implement your custom version of *openwisp-notifications*, you need to perform the steps -described in the rest of this section. +In order to implement your custom version of *openwisp-notifications*, you +need to perform the steps described in the rest of this section. -When in doubt, the code in `test project `_ -and `sample_notifications `_ -will guide you in the correct direction: just replicate and adapt that code to get a basic derivative of -*openwisp-notifications* working. +When in doubt, the code in `test project +`_ +and `sample_notifications +`_ +will guide you in the correct direction: just replicate and adapt that +code to get a basic derivative of *openwisp-notifications* working. -**Premise**: if you plan on using a customized version of this module, we suggest to start with it since -the beginning, because migrating your data from the default module to your extended version may be time -consuming. +**Premise**: if you plan on using a customized version of this module, we +suggest to start with it since the beginning, because migrating your data +from the default module to your extended version may be time consuming. 1. Initialize your custom module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The first thing you need to do in order to extend *openwisp-notifications* is create a new django app which -will contain your custom version of that *openwisp-notifications* app. +The first thing you need to do in order to extend *openwisp-notifications* +is create a new django app which will contain your custom version of that +*openwisp-notifications* app. -A django app is nothing more than a `python package `_ -(a directory of python scripts), in the following examples we'll call this django app as ``mynotifications`` -but you can name it how you want: +A django app is nothing more than a `python package +`_ (a directory +of python scripts), in the following examples we'll call this django app +as ``mynotifications`` but you can name it how you want: .. code-block:: shell django-admin startapp mynotifications -Keep in mind that the command mentioned above must be called from a directory which is available in your -`PYTHON_PATH `_ so that you can then import -the result into your project. +Keep in mind that the command mentioned above must be called from a +directory which is available in your `PYTHON_PATH +`_ so that +you can then import the result into your project. -Now you need to add ``mynotifications`` to ``INSTALLED_APPS`` in your ``settings.py``, ensuring also that -``openwisp_notifications`` has been removed: +Now you need to add ``mynotifications`` to ``INSTALLED_APPS`` in your +``settings.py``, ensuring also that ``openwisp_notifications`` has been +removed: .. code-block:: python INSTALLED_APPS = [ # ... other apps ... # 'openwisp_notifications', <-- comment out or delete this line - 'mynotifications', + "mynotifications", ] -For more information about how to work with django projects and django apps, please refer to the -`django documentation `_. +For more information about how to work with django projects and django +apps, please refer to the `django documentation +`_. 2. Install ``openwisp-notifications`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Install (and add to the requirement of your project) *openwisp-notifications*: +Install (and add to the requirement of your project) +*openwisp-notifications*: .. code-block:: shell @@ -1327,42 +1389,44 @@ Add the following to your ``settings.py``: .. code-block:: python - EXTENDED_APPS = ['openwisp_notifications'] + EXTENDED_APPS = ["openwisp_notifications"] 4. Add ``openwisp_utils.staticfiles.DependencyFinder`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Add ``openwisp_utils.staticfiles.DependencyFinder`` to ``STATICFILES_FINDERS`` in your ``settings.py``: +Add ``openwisp_utils.staticfiles.DependencyFinder`` to +``STATICFILES_FINDERS`` in your ``settings.py``: .. code-block:: python STATICFILES_FINDERS = [ - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'openwisp_utils.staticfiles.DependencyFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", + "openwisp_utils.staticfiles.DependencyFinder", ] 5. Add ``openwisp_utils.loaders.DependencyLoader`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Add ``openwisp_utils.loaders.DependencyLoader`` to ``TEMPLATES`` in your ``settings.py``: +Add ``openwisp_utils.loaders.DependencyLoader`` to ``TEMPLATES`` in your +``settings.py``: .. code-block:: python TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'OPTIONS': { - 'loaders': [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - 'openwisp_utils.loaders.DependencyLoader', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "OPTIONS": { + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + "openwisp_utils.loaders.DependencyLoader", ], - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, } @@ -1373,22 +1437,27 @@ Add ``openwisp_utils.loaders.DependencyLoader`` to ``TEMPLATES`` in your ``setti Please refer to the following files in the sample app of the test project: -- `sample_notifications/__init__.py `_. -- `sample_notifications/apps.py `_. +- `sample_notifications/__init__.py + `_. +- `sample_notifications/apps.py + `_. -For more information regarding the concept of ``AppConfig`` please refer to the -`"Applications" section in the django documentation `_. +For more information regarding the concept of ``AppConfig`` please refer +to the `"Applications" section in the django documentation +`_. 7. Create your custom models ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For the purpose of showing an example, we added a simple "details" field to the -`models of the sample app in the test project `_. +For the purpose of showing an example, we added a simple "details" field +to the `models of the sample app in the test project +`_. You can add fields in a similar way in your ``models.py`` file. -**Note**: For doubts regarding how to use, extend or develop models please refer to -the `"Models" section in the django documentation `_. +**Note**: For doubts regarding how to use, extend or develop models please +refer to the `"Models" section in the django documentation +`_. 8. Add swapper configurations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1398,35 +1467,47 @@ Add the following to your ``settings.py``: .. code-block:: python # Setting models for swapper module - OPENWISP_NOTIFICATIONS_NOTIFICATION_MODEL = 'mynotifications.Notification' - OPENWISP_NOTIFICATIONS_NOTIFICATIONSETTING_MODEL = 'mynotifications.NotificationSetting' - OPENWISP_NOTIFICATIONS_IGNOREOBJECTNOTIFICATION_MODEL = 'mynotifications.IgnoreObjectNotification' + OPENWISP_NOTIFICATIONS_NOTIFICATION_MODEL = "mynotifications.Notification" + OPENWISP_NOTIFICATIONS_NOTIFICATIONSETTING_MODEL = ( + "mynotifications.NotificationSetting" + ) + OPENWISP_NOTIFICATIONS_IGNOREOBJECTNOTIFICATION_MODEL = ( + "mynotifications.IgnoreObjectNotification" + ) 9. Create database migrations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Create and apply database migrations:: +Create and apply database migrations: + +:: ./manage.py makemigrations ./manage.py migrate -For more information, refer to the -`"Migrations" section in the django documentation `_. +For more information, refer to the `"Migrations" section in the django +documentation +`_. 10. Create your custom admin ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Refer to the `admin.py file of the sample app `_. +Refer to the `admin.py file of the sample app +`_. -To introduce changes to the admin, you can do it in two main ways which are described below. +To introduce changes to the admin, you can do it in two main ways which +are described below. -**Note**: For more information regarding how the django admin works, or how it can be customized, -please refer to `"The django admin site" section in the django documentation `_. +**Note**: For more information regarding how the django admin works, or +how it can be customized, please refer to `"The django admin site" section +in the django documentation +`_. 1. Monkey patching -################## +++++++++++++++++++ -If the changes you need to add are relatively small, you can resort to monkey patching. +If the changes you need to add are relatively small, you can resort to +monkey patching. For example: @@ -1434,14 +1515,14 @@ For example: from openwisp_notifications.admin import NotificationSettingInline - NotificationSettingInline.list_display.insert(1, 'my_custom_field') - NotificationSettingInline.ordering = ['-my_custom_field'] + NotificationSettingInline.list_display.insert(1, "my_custom_field") + NotificationSettingInline.ordering = ["-my_custom_field"] 2. Inheriting admin classes -########################### ++++++++++++++++++++++++++++ -If you need to introduce significant changes and/or you don't want to resort to -monkey patching, you can proceed as follows: +If you need to introduce significant changes and/or you don't want to +resort to monkey patching, you can proceed as follows: .. code-block:: python @@ -1451,7 +1532,7 @@ monkey patching, you can proceed as follows: ) from openwisp_notifications.swapper import load_model - NotificationSetting = load_model('NotificationSetting') + NotificationSetting = load_model("NotificationSetting") admin.site.unregister(NotificationSettingAdmin) admin.site.unregister(NotificationSettingInline) @@ -1465,106 +1546,128 @@ monkey patching, you can proceed as follows: 11. Create root URL configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Please refer to the `urls.py `_ +Please refer to the `urls.py +`_ file in the test project. -For more information about URL configuration in django, please refer to the -`"URL dispatcher" section in the django documentation `_. +For more information about URL configuration in django, please refer to +the `"URL dispatcher" section in the django documentation +`_. 12. Create root routing configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Please refer to the `routing.py `_ +Please refer to the `routing.py +`_ file in the test project. -For more information about URL configuration in django, please refer to the -`"Routing" section in the Channels documentation `_. +For more information about URL configuration in django, please refer to +the `"Routing" section in the Channels documentation +`_. 13. Create celery.py ~~~~~~~~~~~~~~~~~~~~ -Please refer to the `celery.py `_ +Please refer to the `celery.py +`_ file in the test project. -For more information about the usage of celery in django, please refer to the -`"First steps with Django" section in the celery documentation `_. +For more information about the usage of celery in django, please refer to +the `"First steps with Django" section in the celery documentation +`_. 14. Import Celery Tasks ~~~~~~~~~~~~~~~~~~~~~~~ -Add the following in your settings.py to import celery tasks from ``openwisp_notifications`` app. +Add the following in your settings.py to import celery tasks from +``openwisp_notifications`` app. .. code-block:: python - CELERY_IMPORTS = ('openwisp_notifications.tasks',) + CELERY_IMPORTS = ("openwisp_notifications.tasks",) 15. Register Template Tags ~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you need to use template tags of *openwisp_notifications*, you will need to register as shown in -`"templatetags/notification_tags.py" of sample_notifications +If you need to use template tags of *openwisp_notifications*, you will +need to register as shown in `"templatetags/notification_tags.py" of +sample_notifications `_. For more information about template tags in django, please refer to the -`"Custom template tags and filters" section in the django documentation `_. +`"Custom template tags and filters" section in the django documentation +`_. 16. Register Notification Types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can register notification types as shown in the `section for registering notification types <#register_notification_type>`_. +You can register notification types as shown in the `section for +registering notification types <#register_notification_type>`_. A reference for registering a notification type is also provided in -`sample_notifications/apps.py `_. -The registered notification type of ``sample_notifications`` app is used for creating notifications -when an object of ``TestApp`` model is created. You can use -`sample_notifications/models.py `_ +`sample_notifications/apps.py +`_. +The registered notification type of ``sample_notifications`` app is used +for creating notifications when an object of ``TestApp`` model is created. +You can use `sample_notifications/models.py +`_ as reference for your implementation. 17. Import the automated tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When developing a custom application based on this module, it's a good idea to import and run the base tests -too, so that you can be sure the changes you're introducing are not breaking some of the existing feature +When developing a custom application based on this module, it's a good +idea to import and run the base tests too, so that you can be sure the +changes you're introducing are not breaking some of the existing feature of openwisp-notifications. -In case you need to add breaking changes, you can overwrite the tests defined in the base classes to test -your own behavior. +In case you need to add breaking changes, you can overwrite the tests +defined in the base classes to test your own behavior. See the `tests of the sample_notifications `_ to find out how to do this. -**Note**: Some tests will fail if ``templatetags`` and ``admin/base.html`` are not configured properly. -See preceeding sections to configure them properly. +**Note**: Some tests will fail if ``templatetags`` and ``admin/base.html`` +are not configured properly. See preceeding sections to configure them +properly. Other base classes that can be inherited and extended ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The following steps are not required and are intended for more advanced customization. +The following steps are not required and are intended for more advanced +customization. API views -######### ++++++++++ -The API view classes can be extended into other django applications as well. Note -that it is not required for extending openwisp-notifications to your app and this change -is required only if you plan to make changes to the API views. +The API view classes can be extended into other django applications as +well. Note that it is not required for extending openwisp-notifications to +your app and this change is required only if you plan to make changes to +the API views. -Create a view file as done in `sample_notifications/views.py `_ +Create a view file as done in `sample_notifications/views.py +`_ -For more information regarding Django REST Framework API views, please refer to the -`"Generic views" section in the Django REST Framework documentation `_. +For more information regarding Django REST Framework API views, please +refer to the `"Generic views" section in the Django REST Framework +documentation +`_. Web Socket Consumers -#################### +++++++++++++++++++++ -The Web Socket Consumer classes can be extended into other django applications as well. Note -that it is not required for extending openwisp-notifications to your app and this change -is required only if you plan to make changes to the consumers. +The Web Socket Consumer classes can be extended into other django +applications as well. Note that it is not required for extending +openwisp-notifications to your app and this change is required only if you +plan to make changes to the consumers. -Create a consumer file as done in `sample_notifications/consumers.py `_ +Create a consumer file as done in `sample_notifications/consumers.py +`_ For more information regarding Channels' Consumers, please refer to the -`"Consumers" section in the Channels documentation `_. +`"Consumers" section in the Channels documentation +`_. Contributing ------------