2
0
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:
Oliver
2022-05-06 20:00:56 +10:00
committed by GitHub
22 changed files with 620 additions and 124 deletions

View File

@ -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)

View 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

View File

@ -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)

View 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')},
},
),
]

View File

@ -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',
]

View File

@ -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}'

View File

@ -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

View File

@ -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)

View File

@ -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))