Skip to content

Commit 11fde76

Browse files
pandafynemesifier
authored andcommitted
[feature] Added notification support from openwisp_notifications #191
- Added openwisp-notification as requirement - Added notifications to be sent for following events: - A new device is registered automatically - Congiguration status has changed to "error" - Added ingore notification widget to "DeviceAdmin" Closes #191
1 parent 44d148a commit 11fde76

File tree

12 files changed

+251
-9
lines changed

12 files changed

+251
-9
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ addons:
1313
- spatialite-bin
1414
- libsqlite3-mod-spatialite
1515

16+
services:
17+
- docker
18+
- redis-server
19+
1620
python:
1721
- "3.6"
1822
- "3.7"

README.rst

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ Other popular building blocks that are part of the OpenWISP ecosystem are:
6161
of the state of the network
6262
- `openwisp-ipam <https://github.com/openwisp/openwisp-ipam>`_:
6363
it allows to manage the IP address space of networks
64+
- `openwisp-notifications <https://github.com/openwisp/openwisp-notifications>`_:
65+
it allows to create and manage notifications from the network.
6466

6567
.. image:: https://raw.githubusercontent.com/openwisp/openwisp2-docs/master/assets/design/openwisp-logo-black.svg
6668
:target: http://openwisp.org
@@ -561,6 +563,17 @@ their hardware ID instead of their name.
561563

562564
If you still want to reference devices by their name, set this to ``False``.
563565

566+
Default Alerts / Notifications
567+
------------------------------
568+
569+
+-----------------------+---------------------------------------------------------------------+
570+
| Notification Type | Use |
571+
+-----------------------+---------------------------------------------------------------------+
572+
| ``config_error`` | Fires when status of a device configuration changes to ``error``. |
573+
+-----------------------+---------------------------------------------------------------------+
574+
| ``device_registered`` | Fires when a new device is registered automatically on the network. |
575+
+-----------------------+---------------------------------------------------------------------+
576+
564577
Installing for development
565578
--------------------------
566579

@@ -879,15 +892,31 @@ This signal is emitted once the device gets registered automatically through the
879892
Setup (Integrate into other Apps)
880893
---------------------------------
881894

882-
Add ``openwisp_controller`` to ``INSTALLED_APPS``:
895+
Add ``openwisp_controller`` applications to ``INSTALLED_APPS``:
883896

884897
.. code-block:: python
885898
886899
INSTALLED_APPS = [
887-
# other apps
888-
'openwisp_controller',
900+
...
901+
# openwisp2 modules
902+
'openwisp_controller.config',
903+
'openwisp_controller.pki',
904+
'openwisp_controller.geo',
905+
'openwisp_controller.connection',
906+
'openwisp_controller.notifications',
907+
'openwisp_users',
908+
'openwisp_notifications',
909+
# openwisp2 admin theme
910+
# (must be loaded here)
911+
'openwisp_utils.admin_theme',
912+
'django.contrib.admin',
913+
'django.forms',
914+
...
889915
]
890916
917+
**Note**: The order of applications in ``INSTALLED_APPS`` should be maintained,
918+
otherwise it might not work properly.
919+
891920
Add the URLs to your main ``urls.py``:
892921

893922
.. code-block:: python
@@ -904,6 +933,9 @@ Then run:
904933
905934
./manage.py migrate
906935
936+
**Note**: In order to properly configure notifications for your project,
937+
please follow `setup guide of openwisp-notifications <https://github.com/openwisp/openwisp-notifications#setup-integrate-into-an-existing-django-project>`_.
938+
907939
Extending openwisp-controller
908940
-----------------------------
909941

@@ -1442,6 +1474,17 @@ Remember to change ``geo_views`` location in ``urls.py`` in point 11 for extendi
14421474
14431475
For more information about django views, please refer to the `views section in the django documentation <https://docs.djangoproject.com/en/dev/topics/http/views/>`_.
14441476
1477+
Registering new notification types
1478+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1479+
1480+
You can define your own notification types using ``register_notification_type`` function from OpenWISP
1481+
Notifications. For more information, see the relevant
1482+
`documentation section about registering notification types in openwisp-notifications <https://github.com/openwisp/openwisp-notifications#registering--unregistering-notification-types>`_.
1483+
1484+
Once a new notification type is registered, you have to use the `"notify" signal provided in
1485+
openwisp-notifications <https://github.com/openwisp/openwisp-notifications#sending-notifications>`_
1486+
to send notifications for this type.
1487+
14451488
Talks
14461489
-----
14471490

install-dev.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#!/bin/bash
22
set -e
3+
4+
# TODO: remove when openwisp-notifications 0.2 is released
5+
pip install -U https://github.com/openwisp/openwisp-notifications/tarball/issues/38-disable-object-notification
6+
37
# TODO: can be remove when openwisp-users 0.4.1 is released
48
# Commit needed for failures in tests:
59
# https://github.com/openwisp/openwisp-users/commit/ef347927136ddb4676479cb54cdc7cd08049e2e5

openwisp_controller/config/apps.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
from django.apps import AppConfig
22
from django.conf import settings
3+
from django.core.exceptions import ImproperlyConfigured
34
from django.db.models.signals import m2m_changed, post_delete
45
from django.utils.translation import ugettext_lazy as _
6+
from openwisp_notifications.types import (
7+
register_notification_type,
8+
unregister_notification_type,
9+
)
510
from swapper import get_model_name, load_model
611

712
from . import settings as app_settings
@@ -20,17 +25,22 @@ def ready(self, *args, **kwargs):
2025
self.__setmodels__()
2126
self.connect_signals()
2227
self.add_default_menu_items()
28+
self.register_notification_types()
29+
self.add_ignore_notification_widget()
2330

2431
def __setmodels__(self):
2532
self.config_model = load_model('config', 'Config')
2633
self.vpnclient_model = load_model('config', 'VpnClient')
2734

2835
def connect_signals(self):
2936
"""
37+
* handlers for creating notifications
3038
* m2m validation before templates are added/removed to a config
3139
* automatic vpn client management on m2m_changed
3240
* automatic vpn client removal
3341
"""
42+
from . import handlers # noqa
43+
3444
m2m_changed.connect(
3545
self.config_model.clean_templates,
3646
sender=self.config_model.templates.through,
@@ -64,3 +74,62 @@ def add_default_menu_items(self):
6474
else:
6575
current_menu = getattr(settings, menu_setting)
6676
current_menu += items
77+
78+
def register_notification_types(self):
79+
register_notification_type(
80+
'config_error',
81+
{
82+
'verbose_name': _('Configuration ERROR'),
83+
'verb': _('encountered an error'),
84+
'level': 'error',
85+
'email_subject': _(
86+
'[{site.name}] ERROR: "{notification.target}" configuration '
87+
'{notification.verb}'
88+
),
89+
'message': _(
90+
'The configuration of [{notification.target}]'
91+
'({notification.target_link}) has {notification.verb}. '
92+
'The last working configuration has been restored from a backup '
93+
'present on the filesystem of the device.'
94+
),
95+
},
96+
)
97+
98+
register_notification_type(
99+
'device_registered',
100+
{
101+
'verbose_name': _('Device Registration'),
102+
'verb': _('registered successfully'),
103+
'level': 'success',
104+
'email_subject': _(
105+
'[{site.name}] SUCCESS: "{notification.target}"'
106+
' {notification.verb}'
107+
),
108+
'message': _(
109+
'A new device [{notification.target}]'
110+
'({notification.target_link}) has {notification.verb}.'
111+
),
112+
},
113+
)
114+
115+
# Unregister default notification type
116+
try:
117+
unregister_notification_type('default')
118+
except ImproperlyConfigured:
119+
pass
120+
121+
def add_ignore_notification_widget(self):
122+
"""
123+
Adds ingore notification widget from openwisp-notifications to DeviceAdmin.
124+
"""
125+
obj_notification_widget = getattr(
126+
settings, 'OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN', []
127+
)
128+
device_admin = 'openwisp_controller.config.admin.DeviceAdmin'
129+
if device_admin not in obj_notification_widget:
130+
obj_notification_widget.append(device_admin)
131+
setattr(
132+
settings,
133+
'OPENWISP_NOTIFICATIONS_IGNORE_ENABLED_ADMIN',
134+
obj_notification_widget,
135+
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from django.dispatch import receiver
2+
from openwisp_notifications.signals import notify
3+
from swapper import load_model
4+
5+
from .signals import config_status_changed, device_registered
6+
7+
Config = load_model('config', 'Config')
8+
Device = load_model('config', 'Device')
9+
10+
11+
@receiver(
12+
config_status_changed,
13+
sender=Config,
14+
dispatch_uid='config_status_error_notification',
15+
)
16+
def config_status_error_notification(sender, instance, **kwargs):
17+
"""
18+
Creates notification when status of a configuration changes to "error".
19+
"""
20+
if instance.status == 'error':
21+
notify.send(sender=instance, type='config_error', target=instance.device)
22+
23+
24+
@receiver(
25+
device_registered, sender=Config, dispatch_uid='device_registered_notification'
26+
)
27+
def device_registered_notification(sender, instance, **kwargs):
28+
"""
29+
Creates notification when a new device is registered automatically
30+
through controller.
31+
"""
32+
notify.send(sender=instance, type='device_registered', target=instance)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from django.apps.registry import apps
2+
from django.test import TestCase
3+
from openwisp_notifications.types import unregister_notification_type
4+
from swapper import load_model
5+
6+
from openwisp_controller.config.tests.utils import CreateConfigMixin
7+
from openwisp_users.tests.utils import TestOrganizationMixin
8+
9+
from ..signals import device_registered
10+
11+
Config = load_model('config', 'Config')
12+
Notification = load_model('openwisp_notifications', 'Notification')
13+
14+
notification_qs = Notification.objects.all()
15+
16+
17+
class TestNotifications(CreateConfigMixin, TestOrganizationMixin, TestCase):
18+
def setUp(self):
19+
self.admin = self._get_admin()
20+
21+
def test_config_problem_notification(self):
22+
config = self._create_config()
23+
config.set_status_error()
24+
25+
self.assertEqual(config.status, 'error')
26+
self.assertEqual(notification_qs.count(), 1)
27+
notification = notification_qs.first()
28+
self.assertEqual(notification.actor, config)
29+
self.assertEqual(notification.target, config.device)
30+
self.assertEqual(notification.type, 'config_error')
31+
self.assertEqual(
32+
notification.email_subject,
33+
f'[example.com] ERROR: "{config.device}"'
34+
' configuration encountered an error',
35+
)
36+
self.assertIn('encountered an error', notification.message)
37+
38+
def test_device_registered(self):
39+
# To avoid adding repetitive code for registering a device,
40+
# we simulate that "device_registered" signal is emitted
41+
config = self._create_config()
42+
device = config.device
43+
device_registered.send(sender=Config, instance=config.device)
44+
45+
self.assertEqual(notification_qs.count(), 1)
46+
notification = notification_qs.first()
47+
self.assertEqual(notification.actor, device)
48+
self.assertEqual(notification.target, device)
49+
self.assertEqual(notification.type, 'device_registered')
50+
self.assertEqual(
51+
notification.email_subject,
52+
f'[example.com] SUCCESS: "{device}" registered successfully',
53+
)
54+
self.assertIn('registered successfully', notification.message)
55+
56+
def test_default_notification_type_already_unregistered(self):
57+
# Simulates if 'default notification type is already unregistered
58+
# by some other module
59+
60+
# Unregister "config_error" and "device_registered" notification
61+
# types to avoid getting rasing ImproperlyConfigured exceptions
62+
unregister_notification_type('config_error')
63+
unregister_notification_type('device_registered')
64+
65+
# This will try to unregister 'default' notification type
66+
# which is already got unregistered when Django loaded.
67+
# No exception should be raised as the exception is already handled.
68+
app = apps.get_app_config('config')
69+
app.ready()

openwisp_controller/geo/channels/routing.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from channels.security.websocket import AllowedHostsOriginValidator
44
from django.conf.urls import url
55
from django_loci.channels.base import location_broadcast_path
6+
from openwisp_notifications.websockets.routing import get_routes
67

78
from .consumers import LocationBroadcast
89

@@ -11,7 +12,7 @@
1112
channel_routing = ProtocolTypeRouter(
1213
{
1314
'websocket': AllowedHostsOriginValidator(
14-
AuthMiddlewareStack(URLRouter(geo_routes)),
15+
AuthMiddlewareStack(URLRouter(get_routes() + geo_routes)),
1516
)
1617
}
1718
)

openwisp_controller/tests/test_users_integration.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,18 @@ class TestUsersIntegration(TestUsersAdmin):
1010
test_only_superuser_has_add_delete_org_perm = None
1111
test_can_change_inline_org_owner = None
1212

13+
@property
14+
def add_user_inline_params(self):
15+
params = super().add_user_inline_params
16+
params.update(
17+
{
18+
'notificationsetting_set-TOTAL_FORMS': 0,
19+
'notificationsetting_set-INITIAL_FORMS': 0,
20+
'notificationsetting_set-MIN_NUM_FORMS': 0,
21+
'notificationsetting_set-MAX_NUM_FORMS': 0,
22+
}
23+
)
24+
return params
25+
1326

1427
del TestUsersAdmin

requirements-test.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
coveralls
2+
channels_redis~=3.1
3+
django_redis~=4.12
24
openwisp-utils[qa]~=0.6.0
35
pytest~=6.0
46
pytest-django>=3.8.0,<4.0.0

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ celery>=4.4.3,<4.5.0
1515
redis>=3.4.1,<4.0.0
1616
swapper~=1.1.0
1717
django-flat-json-widget~=0.1
18+
# TODO: uncomment when openwisp-notifications is released
19+
# 'openwisp_notifications~=0.1',

0 commit comments

Comments
 (0)