Skip to content

Commit 8b2e2a2

Browse files
authored
[FR] Slack notifications & notification method updates (inventree#4114)
* [FR] Slack notification Add slack sending method Fixes inventree#3843 * Add global panel with notification methods * add plugin information * fix plugin lookup and link * Add settings content mixin * Add instructions for Slack setup * fix rendering of custom settings content
1 parent 758f788 commit 8b2e2a2

10 files changed

Lines changed: 189 additions & 5 deletions

File tree

InvenTree/common/notifications.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ def collect(self, selected_classes=None):
192192
for item in current_method:
193193
plugin = item.get_plugin(item)
194194
ref = f'{plugin.package_path}_{item.METHOD_NAME}' if plugin else item.METHOD_NAME
195+
item.plugin = plugin() if plugin else None
195196
filtered_list[ref] = item
196197

197198
storage.liste = list(filtered_list.values())

InvenTree/plugin/base/integration/mixins.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,3 +742,24 @@ def render_panels(self, view, request, context):
742742
panels.append(panel)
743743

744744
return panels
745+
746+
747+
class SettingsContentMixin:
748+
"""Mixin which allows integration of custom HTML content into a plugins settings page.
749+
750+
The 'get_settings_content' method must return the HTML content to appear in the section
751+
"""
752+
753+
class MixinMeta:
754+
"""Meta for mixin."""
755+
756+
MIXIN_NAME = 'SettingsContent'
757+
758+
def __init__(self):
759+
"""Register mixin."""
760+
super().__init__()
761+
self.add_mixin('settingscontent', True, __class__)
762+
763+
def get_settings_content(self, view, request):
764+
"""This method *must* be implemented by the plugin class."""
765+
raise MixinNotImplementedError(f"{__class__} is missing the 'get_settings_content' method")

InvenTree/plugin/builtin/integration/core_notifications.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
from django.template.loader import render_to_string
44
from django.utils.translation import gettext_lazy as _
55

6+
import requests
67
from allauth.account.models import EmailAddress
78

89
import common.models
910
import InvenTree.helpers
1011
import InvenTree.tasks
11-
from plugin import InvenTreePlugin
12-
from plugin.mixins import BulkNotificationMethod, SettingsMixin
12+
from plugin import InvenTreePlugin, registry
13+
from plugin.mixins import (BulkNotificationMethod, SettingsContentMixin,
14+
SettingsMixin)
1315

1416

1517
class PlgMixin:
@@ -23,7 +25,7 @@ def get_plugin(self):
2325
return CoreNotificationsPlugin
2426

2527

26-
class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
28+
class CoreNotificationsPlugin(SettingsContentMixin, SettingsMixin, InvenTreePlugin):
2729
"""Core notification methods for InvenTree."""
2830

2931
NAME = "CoreNotificationsPlugin"
@@ -39,8 +41,30 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
3941
'default': False,
4042
'validator': bool,
4143
},
44+
'ENABLE_NOTIFICATION_SLACK': {
45+
'name': _('Enable slack notifications'),
46+
'description': _('Allow sending of slack channel messages for event notifications'),
47+
'default': False,
48+
'validator': bool,
49+
},
50+
'NOTIFICATION_SLACK_URL': {
51+
'name': _('Slack incoming webhook url'),
52+
'description': _('URL that is used to send messages to a slack channel'),
53+
'protected': True,
54+
},
4255
}
4356

57+
def get_settings_content(self, request):
58+
"""Custom settings content for the plugin."""
59+
return """
60+
<p>Setup for Slack:</p>
61+
<ol>
62+
<li>Create a new Slack app on <a href="https://api.slack.com/apps/new" target="_blank">this page</a></li>
63+
<li>Enable <i>Incoming Webhooks</i> for the channel you want the notifications posted to</li>
64+
<li>Set the webhook URL in the settings above</li>
65+
<li>Enable the plugin</li>
66+
"""
67+
4468
class EmailNotification(PlgMixin, BulkNotificationMethod):
4569
"""Notificationmethod for delivery via Email."""
4670

@@ -94,3 +118,53 @@ def send_bulk(self):
94118
InvenTree.tasks.send_email(subject, '', targets, html_message=html_message)
95119

96120
return True
121+
122+
class SlackNotification(PlgMixin, BulkNotificationMethod):
123+
"""Notificationmethod for delivery via Slack channel messages."""
124+
125+
METHOD_NAME = 'slack'
126+
METHOD_ICON = 'fa-envelope'
127+
GLOBAL_SETTING = 'ENABLE_NOTIFICATION_SLACK'
128+
129+
def get_targets(self):
130+
"""Not used by this method."""
131+
return self.targets
132+
133+
def send_bulk(self):
134+
"""Send the notifications out via slack."""
135+
136+
instance = registry.plugins.get(self.get_plugin().NAME.lower())
137+
url = instance.get_setting('NOTIFICATION_SLACK_URL')
138+
139+
if not url:
140+
return False
141+
142+
ret = requests.post(url, json={
143+
'text': str(self.context['message']),
144+
'blocks': [
145+
{
146+
"type": "section",
147+
"text": {
148+
"type": "plain_text",
149+
"text": str(self.context['name'])
150+
}
151+
},
152+
{
153+
"type": "section",
154+
"text": {
155+
"type": "mrkdwn",
156+
"text": str(self.context['message'])
157+
},
158+
"accessory": {
159+
"type": "button",
160+
"text": {
161+
"type": "plain_text",
162+
"text": str(_("Open link")), "emoji": True
163+
},
164+
"value": f'{self.category}_{self.obj.pk}',
165+
"url": self.context['link'],
166+
"action_id": "button-action"
167+
}
168+
}]
169+
})
170+
return ret.ok

InvenTree/plugin/mixins/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from ..base.event.mixins import EventMixin
99
from ..base.integration.mixins import (APICallMixin, AppMixin, NavigationMixin,
1010
PanelMixin, ScheduleMixin,
11-
SettingsMixin, UrlsMixin,
12-
ValidationMixin)
11+
SettingsContentMixin, SettingsMixin,
12+
UrlsMixin, ValidationMixin)
1313
from ..base.label.mixins import LabelPrintingMixin
1414
from ..base.locate.mixins import LocateMixin
1515

@@ -20,6 +20,7 @@
2020
'LabelPrintingMixin',
2121
'NavigationMixin',
2222
'ScheduleMixin',
23+
'SettingsContentMixin',
2324
'SettingsMixin',
2425
'UrlsMixin',
2526
'PanelMixin',

InvenTree/plugin/templatetags/plugin_extras.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ def plugin_settings(plugin, *args, **kwargs):
2929
return registry.mixins_settings.get(plugin)
3030

3131

32+
@register.simple_tag(takes_context=True)
33+
def plugin_settings_content(context, plugin, *args, **kwargs):
34+
"""Get the settings content for the plugin."""
35+
plg = registry.get_plugin(plugin)
36+
if hasattr(plg, 'get_settings_content'):
37+
return plg.get_settings_content(context.request)
38+
return None
39+
40+
3241
@register.simple_tag()
3342
def mixin_enabled(plugin, key, *args, **kwargs):
3443
"""Is the mixin registerd and configured in the plugin?"""
@@ -71,3 +80,16 @@ def plugin_errors(*args, **kwargs):
7180
def notification_settings_list(context, *args, **kwargs):
7281
"""List of all user notification settings."""
7382
return storage.get_usersettings(user=context.get('user', None))
83+
84+
85+
@register.simple_tag(takes_context=True)
86+
def notification_list(context, *args, **kwargs):
87+
"""List of all notification methods."""
88+
return [{
89+
'slug': a.METHOD_NAME,
90+
'icon': a.METHOD_ICON,
91+
'setting': a.GLOBAL_SETTING,
92+
'plugin': a.plugin,
93+
'description': a.__doc__,
94+
'name': a.__name__
95+
} for a in storage.liste]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{% load plugin_extras %}
2+
3+
{% plugin_settings_content plugin_key as plugin_settings_content %}
4+
{% if plugin_settings_content %}
5+
{{ plugin_settings_content|safe }}
6+
{% endif %}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{% extends "panel.html" %}
2+
3+
{% load i18n %}
4+
{% load inventree_extras %}
5+
{% load plugin_extras %}
6+
7+
{% block label %}global-notifications{% endblock label %}
8+
9+
{% block heading %}{% trans "Global Notification Settings" %}{% endblock heading %}
10+
11+
{% block content %}
12+
13+
<div class='row'>
14+
<table class='table table-striped table-condensed'>
15+
<thead>
16+
<th></th>
17+
<th>{% trans "Name" %}</th>
18+
<th>{% trans "Slug" %}</th>
19+
<th>{% trans "Description" %}</th>
20+
</thead>
21+
<tbody>
22+
{% notification_list as methods %}
23+
{% for method in methods %}
24+
<tr>
25+
<td>{% if method.icon %}<span class="fas {{ method.icon }}"></span>{% endif %}</td>
26+
<td>
27+
{{ method.name }}
28+
{% if method.plugin %}
29+
<a class='sidebar-selector' id='select-plugin-{{method.plugin.slug}}' data-bs-parent="#sidebar">
30+
<span class='badge bg-dark badge-right rounded-pill'>{{ method.plugin.slug }}</span>
31+
</a>
32+
{% endif %}
33+
</td>
34+
<td>{{ method.slug }}</td>
35+
<td>{{ method.description }}</td>
36+
</tr>
37+
{% if method.setting %}
38+
<tr>
39+
<td colspan="1"></td>
40+
<td colspan="3">
41+
<table class='table table-condensed'>
42+
<tbody>
43+
{% include "InvenTree/settings/setting.html" with key=method.setting plugin=method.plugin %}
44+
</tbody>
45+
</table>
46+
</td>
47+
</tr>
48+
{% endif %}
49+
{% endfor %}
50+
</tbody>
51+
</table>
52+
</div>
53+
54+
{% endblock content %}

InvenTree/templates/InvenTree/settings/plugin_settings.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,6 @@ <h4>{% trans "Package information" %}</h4>
148148
{% include 'InvenTree/settings/mixins/urls.html' %}
149149
{% endif %}
150150

151+
{% include 'InvenTree/settings/mixins/settings_content.html' %}
152+
151153
{% endblock %}

InvenTree/templates/InvenTree/settings/settings.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
{% include "InvenTree/settings/global.html" %}
3333
{% include "InvenTree/settings/login.html" %}
3434
{% include "InvenTree/settings/barcode.html" %}
35+
{% include "InvenTree/settings/notifications.html" %}
3536
{% include "InvenTree/settings/label.html" %}
3637
{% include "InvenTree/settings/report.html" %}
3738
{% include "InvenTree/settings/part.html" %}

InvenTree/templates/InvenTree/settings/sidebar.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
{% include "sidebar_item.html" with label='login' text=text icon="fa-fingerprint" %}
3333
{% trans "Barcode Support" as text %}
3434
{% include "sidebar_item.html" with label='barcodes' text=text icon="fa-qrcode" %}
35+
{% trans "Notifications" as text %}
36+
{% include "sidebar_item.html" with label='global-notifications' text=text icon="fa-bell" %}
3537
{% trans "Pricing" as text %}
3638
{% include "sidebar_item.html" with label='pricing' text=text icon="fa-dollar-sign" %}
3739
{% trans "Label Printing" as text %}

0 commit comments

Comments
 (0)