mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 11:10:54 +00:00
Merge pull request #2805 from matmair/matmair/issue2385
Plugins for notifications
This commit is contained in:
@ -70,4 +70,20 @@ class PluginConfigAdmin(admin.ModelAdmin):
|
||||
inlines = [PluginSettingInline, ]
|
||||
|
||||
|
||||
class NotificationUserSettingAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin class for NotificationUserSetting
|
||||
"""
|
||||
|
||||
model = models.NotificationUserSetting
|
||||
|
||||
read_only_fields = [
|
||||
'key',
|
||||
]
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
|
||||
admin.site.register(models.PluginConfig, PluginConfigAdmin)
|
||||
admin.site.register(models.NotificationUserSetting, NotificationUserSettingAdmin)
|
||||
|
76
InvenTree/plugin/builtin/integration/core_notifications.py
Normal file
76
InvenTree/plugin/builtin/integration/core_notifications.py
Normal file
@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Core set of Notifications as a Plugin"""
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from allauth.account.models import EmailAddress
|
||||
|
||||
from plugin import IntegrationPluginBase
|
||||
from plugin.mixins import BulkNotificationMethod, SettingsMixin
|
||||
import InvenTree.tasks
|
||||
|
||||
|
||||
class PlgMixin:
|
||||
def get_plugin(self):
|
||||
return CoreNotificationsPlugin
|
||||
|
||||
|
||||
class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase):
|
||||
"""
|
||||
Core notification methods for InvenTree
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = "CoreNotificationsPlugin"
|
||||
AUTHOR = _('InvenTree contributors')
|
||||
DESCRIPTION = _('Integrated outgoing notificaton methods')
|
||||
|
||||
SETTINGS = {
|
||||
'ENABLE_NOTIFICATION_EMAILS': {
|
||||
'name': _('Enable email notifications'),
|
||||
'description': _('Allow sending of emails for event notifications'),
|
||||
'default': False,
|
||||
'validator': bool,
|
||||
},
|
||||
}
|
||||
|
||||
class EmailNotification(PlgMixin, BulkNotificationMethod):
|
||||
METHOD_NAME = 'mail'
|
||||
METHOD_ICON = 'fa-envelope'
|
||||
CONTEXT_EXTRA = [
|
||||
('template', ),
|
||||
('template', 'html', ),
|
||||
('template', 'subject', ),
|
||||
]
|
||||
GLOBAL_SETTING = 'ENABLE_NOTIFICATION_EMAILS'
|
||||
USER_SETTING = {
|
||||
'name': _('Enable email notifications'),
|
||||
'description': _('Allow sending of emails for event notifications'),
|
||||
'default': True,
|
||||
'validator': bool,
|
||||
}
|
||||
|
||||
def get_targets(self):
|
||||
"""
|
||||
Return a list of target email addresses,
|
||||
only for users which allow email notifications
|
||||
"""
|
||||
|
||||
allowed_users = []
|
||||
|
||||
for user in self.targets:
|
||||
allows_emails = self.usersetting(user)
|
||||
|
||||
if allows_emails:
|
||||
allowed_users.append(user)
|
||||
|
||||
return EmailAddress.objects.filter(
|
||||
user__in=allowed_users,
|
||||
)
|
||||
|
||||
def send_bulk(self):
|
||||
html_message = render_to_string(self.context['template']['html'], self.context)
|
||||
targets = self.targets.values_list('email', flat=True)
|
||||
|
||||
InvenTree.tasks.send_email(self.context['template']['subject'], '', targets, html_message=html_message)
|
||||
|
||||
return True
|
@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from plugin.models import NotificationUserSetting
|
||||
from part.test_part import BaseNotificationIntegrationTest
|
||||
from plugin.builtin.integration.core_notifications import CoreNotificationsPlugin
|
||||
from plugin import registry
|
||||
|
||||
|
||||
class CoreNotificationTestTests(BaseNotificationIntegrationTest):
|
||||
|
||||
def test_email(self):
|
||||
"""
|
||||
Ensure that the email notifications run
|
||||
"""
|
||||
|
||||
# enable plugin and set mail setting to true
|
||||
plugin = registry.plugins.get('corenotificationsplugin')
|
||||
plugin.set_setting('ENABLE_NOTIFICATION_EMAILS', True)
|
||||
NotificationUserSetting.set_setting(
|
||||
key='NOTIFICATION_METHOD_MAIL',
|
||||
value=True,
|
||||
change_user=self.user,
|
||||
user=self.user,
|
||||
method=CoreNotificationsPlugin.EmailNotification.METHOD_NAME
|
||||
)
|
||||
|
||||
# run through
|
||||
self._notification_run(CoreNotificationsPlugin.EmailNotification)
|
29
InvenTree/plugin/migrations/0005_notificationusersetting.py
Normal file
29
InvenTree/plugin/migrations/0005_notificationusersetting.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Generated by Django 3.2.12 on 2022-04-03 23:38
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('plugin', '0004_alter_pluginsetting_key'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='NotificationUserSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(help_text='Settings key (must be unique - case insensitive)', max_length=50)),
|
||||
('value', models.CharField(blank=True, help_text='Settings value', max_length=200)),
|
||||
('method', models.CharField(max_length=255, verbose_name='Method')),
|
||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('method', 'user', 'key')},
|
||||
},
|
||||
),
|
||||
]
|
@ -3,6 +3,7 @@ Utility class to enable simpler imports
|
||||
"""
|
||||
|
||||
from ..builtin.integration.mixins import APICallMixin, AppMixin, LabelPrintingMixin, SettingsMixin, EventMixin, ScheduleMixin, UrlsMixin, NavigationMixin
|
||||
from common.notifications import SingleNotificationMethod, BulkNotificationMethod
|
||||
|
||||
from ..builtin.action.mixins import ActionMixin
|
||||
from ..builtin.barcode.mixins import BarcodeMixin
|
||||
@ -18,4 +19,6 @@ __all__ = [
|
||||
'UrlsMixin',
|
||||
'ActionMixin',
|
||||
'BarcodeMixin',
|
||||
'SingleNotificationMethod',
|
||||
'BulkNotificationMethod',
|
||||
]
|
||||
|
@ -7,6 +7,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
import common.models
|
||||
|
||||
@ -101,7 +102,7 @@ class PluginConfig(models.Model):
|
||||
return ret
|
||||
|
||||
|
||||
class PluginSetting(common.models.BaseInvenTreeSetting):
|
||||
class PluginSetting(common.models.GenericReferencedSettingClass, common.models.BaseInvenTreeSetting):
|
||||
"""
|
||||
This model represents settings for individual plugins
|
||||
"""
|
||||
@ -111,41 +112,7 @@ class PluginSetting(common.models.BaseInvenTreeSetting):
|
||||
('plugin', 'key'),
|
||||
]
|
||||
|
||||
def clean(self, **kwargs):
|
||||
|
||||
kwargs['plugin'] = self.plugin
|
||||
|
||||
super().clean(**kwargs)
|
||||
|
||||
"""
|
||||
We override the following class methods,
|
||||
so that we can pass the plugin instance
|
||||
"""
|
||||
|
||||
def is_bool(self, **kwargs):
|
||||
|
||||
kwargs['plugin'] = self.plugin
|
||||
|
||||
return super().is_bool(**kwargs)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__class__.get_setting_name(self.key, plugin=self.plugin)
|
||||
|
||||
@property
|
||||
def default_value(self):
|
||||
return self.__class__.get_setting_default(self.key, plugin=self.plugin)
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return self.__class__.get_setting_description(self.key, plugin=self.plugin)
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
return self.__class__.get_setting_units(self.key, plugin=self.plugin)
|
||||
|
||||
def choices(self):
|
||||
return self.__class__.get_setting_choices(self.key, plugin=self.plugin)
|
||||
REFERENCE_NAME = 'plugin'
|
||||
|
||||
@classmethod
|
||||
def get_setting_definition(cls, key, **kwargs):
|
||||
@ -182,3 +149,40 @@ class PluginSetting(common.models.BaseInvenTreeSetting):
|
||||
verbose_name=_('Plugin'),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
|
||||
class NotificationUserSetting(common.models.GenericReferencedSettingClass, common.models.BaseInvenTreeSetting):
|
||||
"""
|
||||
This model represents notification settings for a user
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
unique_together = [
|
||||
('method', 'user', 'key'),
|
||||
]
|
||||
|
||||
REFERENCE_NAME = 'method'
|
||||
|
||||
@classmethod
|
||||
def get_setting_definition(cls, key, **kwargs):
|
||||
from common.notifications import storage
|
||||
|
||||
kwargs['settings'] = storage.user_settings
|
||||
|
||||
return super().get_setting_definition(key, **kwargs)
|
||||
|
||||
method = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_('Method'),
|
||||
)
|
||||
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
blank=True, null=True,
|
||||
verbose_name=_('User'),
|
||||
help_text=_('User'),
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.key} (for {self.user}): {self.value}'
|
||||
|
@ -523,7 +523,10 @@ class PluginsRegistry:
|
||||
# check all models
|
||||
for model in app_config.get_models():
|
||||
# remove model from admin site
|
||||
admin.site.unregister(model)
|
||||
try:
|
||||
admin.site.unregister(model)
|
||||
except: # pragma: no cover
|
||||
pass
|
||||
models += [model._meta.model_name]
|
||||
except LookupError: # pragma: no cover
|
||||
# if an error occurs the app was never loaded right -> so nothing to do anymore
|
||||
|
@ -15,8 +15,8 @@ from django.utils import timezone
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from plugin.models import PluginConfig, PluginSetting
|
||||
from common.serializers import SettingsSerializer
|
||||
from plugin.models import PluginConfig, PluginSetting, NotificationUserSetting
|
||||
from common.serializers import GenericReferencedSettingSerializer
|
||||
|
||||
|
||||
class PluginConfigSerializer(serializers.ModelSerializer):
|
||||
@ -128,22 +128,25 @@ class PluginConfigInstallSerializer(serializers.Serializer):
|
||||
return ret
|
||||
|
||||
|
||||
class PluginSettingSerializer(SettingsSerializer):
|
||||
class PluginSettingSerializer(GenericReferencedSettingSerializer):
|
||||
"""
|
||||
Serializer for the PluginSetting model
|
||||
"""
|
||||
|
||||
MODEL = PluginSetting
|
||||
EXTRA_FIELDS = [
|
||||
'plugin',
|
||||
]
|
||||
|
||||
plugin = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = PluginSetting
|
||||
fields = [
|
||||
'pk',
|
||||
'key',
|
||||
'value',
|
||||
'name',
|
||||
'description',
|
||||
'type',
|
||||
'choices',
|
||||
'plugin',
|
||||
]
|
||||
|
||||
class NotificationUserSettingSerializer(GenericReferencedSettingSerializer):
|
||||
"""
|
||||
Serializer for the PluginSetting model
|
||||
"""
|
||||
|
||||
MODEL = NotificationUserSetting
|
||||
EXTRA_FIELDS = ['method', ]
|
||||
|
||||
method = serializers.CharField(read_only=True)
|
||||
|
@ -8,7 +8,7 @@ from django.urls import reverse
|
||||
|
||||
from common.models import InvenTreeSetting
|
||||
from plugin import registry
|
||||
|
||||
from common.notifications import storage
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@ -73,3 +73,11 @@ def plugin_errors(*args, **kwargs):
|
||||
All plugin errors in the current session
|
||||
"""
|
||||
return registry.errors
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def notification_settings_list(context, *args, **kwargs):
|
||||
"""
|
||||
List of all user notification settings
|
||||
"""
|
||||
return storage.get_usersettings(user=context.get('user', None))
|
||||
|
Reference in New Issue
Block a user