diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py
index af9b6aa21c..db8c5d11a7 100644
--- a/InvenTree/common/notifications.py
+++ b/InvenTree/common/notifications.py
@@ -192,6 +192,7 @@ class MethodStorageClass:
for item in current_method:
plugin = item.get_plugin(item)
ref = f'{plugin.package_path}_{item.METHOD_NAME}' if plugin else item.METHOD_NAME
+ item.plugin = plugin() if plugin else None
filtered_list[ref] = item
storage.liste = list(filtered_list.values())
diff --git a/InvenTree/plugin/base/integration/mixins.py b/InvenTree/plugin/base/integration/mixins.py
index 232394ca3d..97ca82ea15 100644
--- a/InvenTree/plugin/base/integration/mixins.py
+++ b/InvenTree/plugin/base/integration/mixins.py
@@ -742,3 +742,24 @@ class PanelMixin:
panels.append(panel)
return panels
+
+
+class SettingsContentMixin:
+ """Mixin which allows integration of custom HTML content into a plugins settings page.
+
+ The 'get_settings_content' method must return the HTML content to appear in the section
+ """
+
+ class MixinMeta:
+ """Meta for mixin."""
+
+ MIXIN_NAME = 'SettingsContent'
+
+ def __init__(self):
+ """Register mixin."""
+ super().__init__()
+ self.add_mixin('settingscontent', True, __class__)
+
+ def get_settings_content(self, view, request):
+ """This method *must* be implemented by the plugin class."""
+ raise MixinNotImplementedError(f"{__class__} is missing the 'get_settings_content' method")
diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py
index 67ca3e498e..9d57208875 100644
--- a/InvenTree/plugin/builtin/integration/core_notifications.py
+++ b/InvenTree/plugin/builtin/integration/core_notifications.py
@@ -3,13 +3,15 @@
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
+import requests
from allauth.account.models import EmailAddress
import common.models
import InvenTree.helpers
import InvenTree.tasks
-from plugin import InvenTreePlugin
-from plugin.mixins import BulkNotificationMethod, SettingsMixin
+from plugin import InvenTreePlugin, registry
+from plugin.mixins import (BulkNotificationMethod, SettingsContentMixin,
+ SettingsMixin)
class PlgMixin:
@@ -23,7 +25,7 @@ class PlgMixin:
return CoreNotificationsPlugin
-class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
+class CoreNotificationsPlugin(SettingsContentMixin, SettingsMixin, InvenTreePlugin):
"""Core notification methods for InvenTree."""
NAME = "CoreNotificationsPlugin"
@@ -39,8 +41,30 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
'default': False,
'validator': bool,
},
+ 'ENABLE_NOTIFICATION_SLACK': {
+ 'name': _('Enable slack notifications'),
+ 'description': _('Allow sending of slack channel messages for event notifications'),
+ 'default': False,
+ 'validator': bool,
+ },
+ 'NOTIFICATION_SLACK_URL': {
+ 'name': _('Slack incoming webhook url'),
+ 'description': _('URL that is used to send messages to a slack channel'),
+ 'protected': True,
+ },
}
+ def get_settings_content(self, request):
+ """Custom settings content for the plugin."""
+ return """
+
Setup for Slack:
+
+ - Create a new Slack app on this page
+ - Enable Incoming Webhooks for the channel you want the notifications posted to
+ - Set the webhook URL in the settings above
+ - Enable the plugin
+ """
+
class EmailNotification(PlgMixin, BulkNotificationMethod):
"""Notificationmethod for delivery via Email."""
@@ -94,3 +118,53 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
InvenTree.tasks.send_email(subject, '', targets, html_message=html_message)
return True
+
+ class SlackNotification(PlgMixin, BulkNotificationMethod):
+ """Notificationmethod for delivery via Slack channel messages."""
+
+ METHOD_NAME = 'slack'
+ METHOD_ICON = 'fa-envelope'
+ GLOBAL_SETTING = 'ENABLE_NOTIFICATION_SLACK'
+
+ def get_targets(self):
+ """Not used by this method."""
+ return self.targets
+
+ def send_bulk(self):
+ """Send the notifications out via slack."""
+
+ instance = registry.plugins.get(self.get_plugin().NAME.lower())
+ url = instance.get_setting('NOTIFICATION_SLACK_URL')
+
+ if not url:
+ return False
+
+ ret = requests.post(url, json={
+ 'text': str(self.context['message']),
+ 'blocks': [
+ {
+ "type": "section",
+ "text": {
+ "type": "plain_text",
+ "text": str(self.context['name'])
+ }
+ },
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": str(self.context['message'])
+ },
+ "accessory": {
+ "type": "button",
+ "text": {
+ "type": "plain_text",
+ "text": str(_("Open link")), "emoji": True
+ },
+ "value": f'{self.category}_{self.obj.pk}',
+ "url": self.context['link'],
+ "action_id": "button-action"
+ }
+ }]
+ })
+ return ret.ok
diff --git a/InvenTree/plugin/mixins/__init__.py b/InvenTree/plugin/mixins/__init__.py
index df4b9dcb0f..c42c57b528 100644
--- a/InvenTree/plugin/mixins/__init__.py
+++ b/InvenTree/plugin/mixins/__init__.py
@@ -8,8 +8,8 @@ from ..base.barcodes.mixins import BarcodeMixin
from ..base.event.mixins import EventMixin
from ..base.integration.mixins import (APICallMixin, AppMixin, NavigationMixin,
PanelMixin, ScheduleMixin,
- SettingsMixin, UrlsMixin,
- ValidationMixin)
+ SettingsContentMixin, SettingsMixin,
+ UrlsMixin, ValidationMixin)
from ..base.label.mixins import LabelPrintingMixin
from ..base.locate.mixins import LocateMixin
@@ -20,6 +20,7 @@ __all__ = [
'LabelPrintingMixin',
'NavigationMixin',
'ScheduleMixin',
+ 'SettingsContentMixin',
'SettingsMixin',
'UrlsMixin',
'PanelMixin',
diff --git a/InvenTree/plugin/templatetags/plugin_extras.py b/InvenTree/plugin/templatetags/plugin_extras.py
index 94ccef8116..4d34d264e6 100644
--- a/InvenTree/plugin/templatetags/plugin_extras.py
+++ b/InvenTree/plugin/templatetags/plugin_extras.py
@@ -29,6 +29,15 @@ def plugin_settings(plugin, *args, **kwargs):
return registry.mixins_settings.get(plugin)
+@register.simple_tag(takes_context=True)
+def plugin_settings_content(context, plugin, *args, **kwargs):
+ """Get the settings content for the plugin."""
+ plg = registry.get_plugin(plugin)
+ if hasattr(plg, 'get_settings_content'):
+ return plg.get_settings_content(context.request)
+ return None
+
+
@register.simple_tag()
def mixin_enabled(plugin, key, *args, **kwargs):
"""Is the mixin registerd and configured in the plugin?"""
@@ -71,3 +80,16 @@ def plugin_errors(*args, **kwargs):
def notification_settings_list(context, *args, **kwargs):
"""List of all user notification settings."""
return storage.get_usersettings(user=context.get('user', None))
+
+
+@register.simple_tag(takes_context=True)
+def notification_list(context, *args, **kwargs):
+ """List of all notification methods."""
+ return [{
+ 'slug': a.METHOD_NAME,
+ 'icon': a.METHOD_ICON,
+ 'setting': a.GLOBAL_SETTING,
+ 'plugin': a.plugin,
+ 'description': a.__doc__,
+ 'name': a.__name__
+ } for a in storage.liste]
diff --git a/InvenTree/templates/InvenTree/settings/mixins/settings_content.html b/InvenTree/templates/InvenTree/settings/mixins/settings_content.html
new file mode 100644
index 0000000000..956ef56a40
--- /dev/null
+++ b/InvenTree/templates/InvenTree/settings/mixins/settings_content.html
@@ -0,0 +1,6 @@
+{% load plugin_extras %}
+
+{% plugin_settings_content plugin_key as plugin_settings_content %}
+{% if plugin_settings_content %}
+{{ plugin_settings_content|safe }}
+{% endif %}
diff --git a/InvenTree/templates/InvenTree/settings/notifications.html b/InvenTree/templates/InvenTree/settings/notifications.html
new file mode 100644
index 0000000000..fac6d22c51
--- /dev/null
+++ b/InvenTree/templates/InvenTree/settings/notifications.html
@@ -0,0 +1,54 @@
+{% extends "panel.html" %}
+
+{% load i18n %}
+{% load inventree_extras %}
+{% load plugin_extras %}
+
+{% block label %}global-notifications{% endblock label %}
+
+{% block heading %}{% trans "Global Notification Settings" %}{% endblock heading %}
+
+{% block content %}
+
+
+
+
+ |
+ {% trans "Name" %} |
+ {% trans "Slug" %} |
+ {% trans "Description" %} |
+
+
+ {% notification_list as methods %}
+ {% for method in methods %}
+
+ {% if method.icon %}{% endif %} |
+
+ {{ method.name }}
+ {% if method.plugin %}
+
+ {% endif %}
+ |
+ {{ method.slug }} |
+ {{ method.description }} |
+
+ {% if method.setting %}
+
+ |
+
+
+
+ {% include "InvenTree/settings/setting.html" with key=method.setting plugin=method.plugin %}
+
+
+ |
+
+ {% endif %}
+ {% endfor %}
+
+
+
+
+{% endblock content %}
diff --git a/InvenTree/templates/InvenTree/settings/plugin_settings.html b/InvenTree/templates/InvenTree/settings/plugin_settings.html
index 1994ac872a..6a095ead2d 100644
--- a/InvenTree/templates/InvenTree/settings/plugin_settings.html
+++ b/InvenTree/templates/InvenTree/settings/plugin_settings.html
@@ -148,4 +148,6 @@
{% include 'InvenTree/settings/mixins/urls.html' %}
{% endif %}
+{% include 'InvenTree/settings/mixins/settings_content.html' %}
+
{% endblock %}
diff --git a/InvenTree/templates/InvenTree/settings/settings.html b/InvenTree/templates/InvenTree/settings/settings.html
index 7e553ad0a1..83939c418b 100644
--- a/InvenTree/templates/InvenTree/settings/settings.html
+++ b/InvenTree/templates/InvenTree/settings/settings.html
@@ -32,6 +32,7 @@
{% include "InvenTree/settings/global.html" %}
{% include "InvenTree/settings/login.html" %}
{% include "InvenTree/settings/barcode.html" %}
+{% include "InvenTree/settings/notifications.html" %}
{% include "InvenTree/settings/label.html" %}
{% include "InvenTree/settings/report.html" %}
{% include "InvenTree/settings/part.html" %}
diff --git a/InvenTree/templates/InvenTree/settings/sidebar.html b/InvenTree/templates/InvenTree/settings/sidebar.html
index 57070f463c..49826e0be8 100644
--- a/InvenTree/templates/InvenTree/settings/sidebar.html
+++ b/InvenTree/templates/InvenTree/settings/sidebar.html
@@ -32,6 +32,8 @@
{% include "sidebar_item.html" with label='login' text=text icon="fa-fingerprint" %}
{% trans "Barcode Support" as text %}
{% include "sidebar_item.html" with label='barcodes' text=text icon="fa-qrcode" %}
+{% trans "Notifications" as text %}
+{% include "sidebar_item.html" with label='global-notifications' text=text icon="fa-bell" %}
{% trans "Pricing" as text %}
{% include "sidebar_item.html" with label='pricing' text=text icon="fa-dollar-sign" %}
{% trans "Label Printing" as text %}