mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[FR] Slack notifications & notification method updates (#4114)
* [FR] Slack notification Add slack sending method Fixes #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
This commit is contained in:
parent
758f7886f1
commit
8b2e2a28d5
@ -192,6 +192,7 @@ class MethodStorageClass:
|
|||||||
for item in current_method:
|
for item in current_method:
|
||||||
plugin = item.get_plugin(item)
|
plugin = item.get_plugin(item)
|
||||||
ref = f'{plugin.package_path}_{item.METHOD_NAME}' if plugin else item.METHOD_NAME
|
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
|
filtered_list[ref] = item
|
||||||
|
|
||||||
storage.liste = list(filtered_list.values())
|
storage.liste = list(filtered_list.values())
|
||||||
|
@ -742,3 +742,24 @@ class PanelMixin:
|
|||||||
panels.append(panel)
|
panels.append(panel)
|
||||||
|
|
||||||
return panels
|
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")
|
||||||
|
@ -3,13 +3,15 @@
|
|||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
import requests
|
||||||
from allauth.account.models import EmailAddress
|
from allauth.account.models import EmailAddress
|
||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
import InvenTree.tasks
|
import InvenTree.tasks
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin, registry
|
||||||
from plugin.mixins import BulkNotificationMethod, SettingsMixin
|
from plugin.mixins import (BulkNotificationMethod, SettingsContentMixin,
|
||||||
|
SettingsMixin)
|
||||||
|
|
||||||
|
|
||||||
class PlgMixin:
|
class PlgMixin:
|
||||||
@ -23,7 +25,7 @@ class PlgMixin:
|
|||||||
return CoreNotificationsPlugin
|
return CoreNotificationsPlugin
|
||||||
|
|
||||||
|
|
||||||
class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
|
class CoreNotificationsPlugin(SettingsContentMixin, SettingsMixin, InvenTreePlugin):
|
||||||
"""Core notification methods for InvenTree."""
|
"""Core notification methods for InvenTree."""
|
||||||
|
|
||||||
NAME = "CoreNotificationsPlugin"
|
NAME = "CoreNotificationsPlugin"
|
||||||
@ -39,8 +41,30 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
|
|||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'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 """
|
||||||
|
<p>Setup for Slack:</p>
|
||||||
|
<ol>
|
||||||
|
<li>Create a new Slack app on <a href="https://api.slack.com/apps/new" target="_blank">this page</a></li>
|
||||||
|
<li>Enable <i>Incoming Webhooks</i> for the channel you want the notifications posted to</li>
|
||||||
|
<li>Set the webhook URL in the settings above</li>
|
||||||
|
<li>Enable the plugin</li>
|
||||||
|
"""
|
||||||
|
|
||||||
class EmailNotification(PlgMixin, BulkNotificationMethod):
|
class EmailNotification(PlgMixin, BulkNotificationMethod):
|
||||||
"""Notificationmethod for delivery via Email."""
|
"""Notificationmethod for delivery via Email."""
|
||||||
|
|
||||||
@ -94,3 +118,53 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
|
|||||||
InvenTree.tasks.send_email(subject, '', targets, html_message=html_message)
|
InvenTree.tasks.send_email(subject, '', targets, html_message=html_message)
|
||||||
|
|
||||||
return True
|
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
|
||||||
|
@ -8,8 +8,8 @@ from ..base.barcodes.mixins import BarcodeMixin
|
|||||||
from ..base.event.mixins import EventMixin
|
from ..base.event.mixins import EventMixin
|
||||||
from ..base.integration.mixins import (APICallMixin, AppMixin, NavigationMixin,
|
from ..base.integration.mixins import (APICallMixin, AppMixin, NavigationMixin,
|
||||||
PanelMixin, ScheduleMixin,
|
PanelMixin, ScheduleMixin,
|
||||||
SettingsMixin, UrlsMixin,
|
SettingsContentMixin, SettingsMixin,
|
||||||
ValidationMixin)
|
UrlsMixin, ValidationMixin)
|
||||||
from ..base.label.mixins import LabelPrintingMixin
|
from ..base.label.mixins import LabelPrintingMixin
|
||||||
from ..base.locate.mixins import LocateMixin
|
from ..base.locate.mixins import LocateMixin
|
||||||
|
|
||||||
@ -20,6 +20,7 @@ __all__ = [
|
|||||||
'LabelPrintingMixin',
|
'LabelPrintingMixin',
|
||||||
'NavigationMixin',
|
'NavigationMixin',
|
||||||
'ScheduleMixin',
|
'ScheduleMixin',
|
||||||
|
'SettingsContentMixin',
|
||||||
'SettingsMixin',
|
'SettingsMixin',
|
||||||
'UrlsMixin',
|
'UrlsMixin',
|
||||||
'PanelMixin',
|
'PanelMixin',
|
||||||
|
@ -29,6 +29,15 @@ def plugin_settings(plugin, *args, **kwargs):
|
|||||||
return registry.mixins_settings.get(plugin)
|
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()
|
@register.simple_tag()
|
||||||
def mixin_enabled(plugin, key, *args, **kwargs):
|
def mixin_enabled(plugin, key, *args, **kwargs):
|
||||||
"""Is the mixin registerd and configured in the plugin?"""
|
"""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):
|
def notification_settings_list(context, *args, **kwargs):
|
||||||
"""List of all user notification settings."""
|
"""List of all user notification settings."""
|
||||||
return storage.get_usersettings(user=context.get('user', None))
|
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]
|
||||||
|
@ -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 %}
|
54
InvenTree/templates/InvenTree/settings/notifications.html
Normal file
54
InvenTree/templates/InvenTree/settings/notifications.html
Normal file
@ -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 %}
|
||||||
|
|
||||||
|
<div class='row'>
|
||||||
|
<table class='table table-striped table-condensed'>
|
||||||
|
<thead>
|
||||||
|
<th></th>
|
||||||
|
<th>{% trans "Name" %}</th>
|
||||||
|
<th>{% trans "Slug" %}</th>
|
||||||
|
<th>{% trans "Description" %}</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% notification_list as methods %}
|
||||||
|
{% for method in methods %}
|
||||||
|
<tr>
|
||||||
|
<td>{% if method.icon %}<span class="fas {{ method.icon }}"></span>{% endif %}</td>
|
||||||
|
<td>
|
||||||
|
{{ method.name }}
|
||||||
|
{% if method.plugin %}
|
||||||
|
<a class='sidebar-selector' id='select-plugin-{{method.plugin.slug}}' data-bs-parent="#sidebar">
|
||||||
|
<span class='badge bg-dark badge-right rounded-pill'>{{ method.plugin.slug }}</span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ method.slug }}</td>
|
||||||
|
<td>{{ method.description }}</td>
|
||||||
|
</tr>
|
||||||
|
{% if method.setting %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="1"></td>
|
||||||
|
<td colspan="3">
|
||||||
|
<table class='table table-condensed'>
|
||||||
|
<tbody>
|
||||||
|
{% include "InvenTree/settings/setting.html" with key=method.setting plugin=method.plugin %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock content %}
|
@ -148,4 +148,6 @@
|
|||||||
{% include 'InvenTree/settings/mixins/urls.html' %}
|
{% include 'InvenTree/settings/mixins/urls.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% include 'InvenTree/settings/mixins/settings_content.html' %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
{% include "InvenTree/settings/global.html" %}
|
{% include "InvenTree/settings/global.html" %}
|
||||||
{% include "InvenTree/settings/login.html" %}
|
{% include "InvenTree/settings/login.html" %}
|
||||||
{% include "InvenTree/settings/barcode.html" %}
|
{% include "InvenTree/settings/barcode.html" %}
|
||||||
|
{% include "InvenTree/settings/notifications.html" %}
|
||||||
{% include "InvenTree/settings/label.html" %}
|
{% include "InvenTree/settings/label.html" %}
|
||||||
{% include "InvenTree/settings/report.html" %}
|
{% include "InvenTree/settings/report.html" %}
|
||||||
{% include "InvenTree/settings/part.html" %}
|
{% include "InvenTree/settings/part.html" %}
|
||||||
|
@ -32,6 +32,8 @@
|
|||||||
{% include "sidebar_item.html" with label='login' text=text icon="fa-fingerprint" %}
|
{% include "sidebar_item.html" with label='login' text=text icon="fa-fingerprint" %}
|
||||||
{% trans "Barcode Support" as text %}
|
{% trans "Barcode Support" as text %}
|
||||||
{% include "sidebar_item.html" with label='barcodes' text=text icon="fa-qrcode" %}
|
{% 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 %}
|
{% trans "Pricing" as text %}
|
||||||
{% include "sidebar_item.html" with label='pricing' text=text icon="fa-dollar-sign" %}
|
{% include "sidebar_item.html" with label='pricing' text=text icon="fa-dollar-sign" %}
|
||||||
{% trans "Label Printing" as text %}
|
{% trans "Label Printing" as text %}
|
||||||
|
Loading…
Reference in New Issue
Block a user