From 279be874485ae6379b89c6c01fb15d4b8f15c716 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Apr 2022 02:46:41 +0200 Subject: [PATCH 01/71] Move email notification method into plugin Fixes #2385 --- InvenTree/common/notifications.py | 42 -------------- .../builtin/integration/core_notifications.py | 55 +++++++++++++++++++ InvenTree/plugin/mixins/__init__.py | 3 + 3 files changed, 58 insertions(+), 42 deletions(-) create mode 100644 InvenTree/plugin/builtin/integration/core_notifications.py diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index fe737fc919..6e87797eb8 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -1,16 +1,9 @@ import logging from datetime import timedelta -from django.template.loader import render_to_string - -from allauth.account.models import EmailAddress - from InvenTree.helpers import inheritors from InvenTree.ready import isImportingData from common.models import NotificationEntry, NotificationMessage -from common.models import InvenTreeUserSetting - -import InvenTree.tasks logger = logging.getLogger('inventree') @@ -101,41 +94,6 @@ class BulkNotificationMethod(NotificationMethod): raise NotImplementedError('The `send` method must be overriden!') -class EmailNotification(BulkNotificationMethod): - METHOD_NAME = 'mail' - CONTEXT_EXTRA = [ - ('template', ), - ('template', 'html', ), - ('template', 'subject', ), - ] - - 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 = InvenTreeUserSetting.get_setting('NOTIFICATION_SEND_EMAILS', user=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.get_targets().values_list('email', flat=True) - - InvenTree.tasks.send_email(self.context['template']['subject'], '', targets, html_message=html_message) - - return True - - class UIMessageNotification(SingleNotificationMethod): METHOD_NAME = 'ui_message' diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py new file mode 100644 index 0000000000..18c62cfdf0 --- /dev/null +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -0,0 +1,55 @@ +# -*- 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 +from common.models import InvenTreeUserSetting +import InvenTree.tasks + + +class CoreNotificationsPlugin(IntegrationPluginBase): + """ + Core notification methods for InvenTree + """ + + PLUGIN_NAME = "CoreNotificationsPlugin" + AUTHOR = _('InvenTree contributors') + DESCRIPTION = _('Integrated outgoing notificaton methods') + + class EmailNotification(BulkNotificationMethod): + METHOD_NAME = 'mail' + CONTEXT_EXTRA = [ + ('template', ), + ('template', 'html', ), + ('template', 'subject', ), + ] + + 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 = InvenTreeUserSetting.get_setting('NOTIFICATION_SEND_EMAILS', user=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 diff --git a/InvenTree/plugin/mixins/__init__.py b/InvenTree/plugin/mixins/__init__.py index 86e5e92f37..900289ae37 100644 --- a/InvenTree/plugin/mixins/__init__.py +++ b/InvenTree/plugin/mixins/__init__.py @@ -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', ] From f6aa9e47197165a4cb55b4ef2b11d461168d6b8c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Apr 2022 02:46:55 +0200 Subject: [PATCH 02/71] remove dead code --- InvenTree/common/notifications.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 6e87797eb8..236cb9164e 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -77,9 +77,6 @@ class NotificationMethod: def setup(self): return True - # def send(self, targets) - # def send_bulk(self) - def cleanup(self): return True From 40ff2f61aa29b6b7603d6978db9ba1474a70260e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Apr 2022 02:52:35 +0200 Subject: [PATCH 03/71] make comperator simpler to read --- InvenTree/common/notifications.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 236cb9164e..a733e191c8 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -155,7 +155,11 @@ def trigger_notifaction(obj, category=None, obj_ref='pk', **kwargs): if delivery_methods is None: delivery_methods = inheritors(NotificationMethod) - for method in [a for a in delivery_methods if a not in [SingleNotificationMethod, BulkNotificationMethod]]: + ignored_classes = set([ + SingleNotificationMethod, + BulkNotificationMethod, + ]) + for method in (delivery_methods - ignored_classes): logger.info(f"Triggering method '{method.METHOD_NAME}'") try: deliver_notification(method, obj, category, targets, context) From e95cc7c699bbdf1ad8b9819be2317eaa27abcf61 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Apr 2022 02:57:40 +0200 Subject: [PATCH 04/71] only calculate excluded classes once --- InvenTree/common/notifications.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index a733e191c8..103a070542 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -91,6 +91,12 @@ class BulkNotificationMethod(NotificationMethod): raise NotImplementedError('The `send` method must be overriden!') +IGNORED_NOTIFICATION_CLS = set([ + SingleNotificationMethod, + BulkNotificationMethod, +]) + + class UIMessageNotification(SingleNotificationMethod): METHOD_NAME = 'ui_message' @@ -155,11 +161,7 @@ def trigger_notifaction(obj, category=None, obj_ref='pk', **kwargs): if delivery_methods is None: delivery_methods = inheritors(NotificationMethod) - ignored_classes = set([ - SingleNotificationMethod, - BulkNotificationMethod, - ]) - for method in (delivery_methods - ignored_classes): + for method in (delivery_methods - IGNORED_NOTIFICATION_CLS): logger.info(f"Triggering method '{method.METHOD_NAME}'") try: deliver_notification(method, obj, category, targets, context) From c7329da7e0746870a44a15fe03bd9fff4e75cab4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Apr 2022 03:03:41 +0200 Subject: [PATCH 05/71] move settings --- InvenTree/common/models.py | 8 -------- .../builtin/integration/core_notifications.py | 13 +++++++++++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index bb7de56e99..70084adfde 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -1239,14 +1239,6 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'default': True, 'validator': bool, }, - - 'NOTIFICATION_SEND_EMAILS': { - 'name': _('Enable email notifications'), - 'description': _('Allow sending of emails for event notifications'), - 'default': True, - 'validator': bool, - }, - 'LABEL_ENABLE': { 'name': _('Enable label printing'), 'description': _('Enable label printing from the web interface'), diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index 18c62cfdf0..ef1598205c 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -6,12 +6,12 @@ from django.utils.translation import ugettext_lazy as _ from allauth.account.models import EmailAddress from plugin import IntegrationPluginBase -from plugin.mixins import BulkNotificationMethod +from plugin.mixins import BulkNotificationMethod, SettingsMixin from common.models import InvenTreeUserSetting import InvenTree.tasks -class CoreNotificationsPlugin(IntegrationPluginBase): +class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): """ Core notification methods for InvenTree """ @@ -20,6 +20,15 @@ class CoreNotificationsPlugin(IntegrationPluginBase): AUTHOR = _('InvenTree contributors') DESCRIPTION = _('Integrated outgoing notificaton methods') + SETTINGS = { + 'NOTIFICATION_SEND_EMAILS': { + 'name': _('Enable email notifications'), + 'description': _('Allow sending of emails for event notifications'), + 'default': True, + 'validator': bool, + }, + } + class EmailNotification(BulkNotificationMethod): METHOD_NAME = 'mail' CONTEXT_EXTRA = [ From 6124b1207d5aa78b848c1c6d3da7938296bb1c28 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Apr 2022 03:16:14 +0200 Subject: [PATCH 06/71] make setting global --- InvenTree/plugin/builtin/integration/core_notifications.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index ef1598205c..2883971d04 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -21,7 +21,7 @@ class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): DESCRIPTION = _('Integrated outgoing notificaton methods') SETTINGS = { - 'NOTIFICATION_SEND_EMAILS': { + 'ENABLE_NOTIFICATION_EMAILS': { 'name': _('Enable email notifications'), 'description': _('Allow sending of emails for event notifications'), 'default': True, @@ -43,6 +43,10 @@ class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): only for users which allow email notifications """ + # Check if method globally enabled + if not self.get_setting('ENABLE_NOTIFICATION_EMAILS'): + return + allowed_users = [] for user in self.targets: From 6a300ea24a02c07beb89575fe54a5f71359555ca Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Apr 2022 03:32:27 +0200 Subject: [PATCH 07/71] fix global settings check --- InvenTree/plugin/builtin/integration/core_notifications.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index 2883971d04..ba4ba138cf 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from allauth.account.models import EmailAddress -from plugin import IntegrationPluginBase +from plugin import IntegrationPluginBase, registry from plugin.mixins import BulkNotificationMethod, SettingsMixin from common.models import InvenTreeUserSetting import InvenTree.tasks @@ -44,7 +44,8 @@ class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): """ # Check if method globally enabled - if not self.get_setting('ENABLE_NOTIFICATION_EMAILS'): + plg = registry.plugins.get(CoreNotificationsPlugin.PLUGIN_NAME.lower()) + if plg and not plg.get_setting('ENABLE_NOTIFICATION_EMAILS'): return allowed_users = [] From 926f56bb41f1e62b3bfbcbe3a51cb562cedbb41b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Apr 2022 04:11:29 +0200 Subject: [PATCH 08/71] move plugins checks to method --- InvenTree/common/notifications.py | 31 +++++++++++++++++++ .../builtin/integration/core_notifications.py | 14 +++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 103a070542..6dacac3918 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -4,6 +4,7 @@ from datetime import timedelta from InvenTree.helpers import inheritors from InvenTree.ready import isImportingData from common.models import NotificationEntry, NotificationMessage +from plugin import registry logger = logging.getLogger('inventree') @@ -17,6 +18,7 @@ class NotificationMethod: METHOD_NAME = '' CONTEXT_BUILTIN = ['name', 'message', ] CONTEXT_EXTRA = [] + GLOBAL_SETTING = None def __init__(self, obj, category, targets, context) -> None: # Check if a sending fnc is defined @@ -27,6 +29,11 @@ class NotificationMethod: if self.METHOD_NAME in ('', None): raise NotImplementedError(f'The NotificationMethod {self.__class__} did not provide a METHOD_NAME') + # Check if plugin is disabled - if so do not gather targets etc. + if self.global_setting_disable(): + self.targets = None + return + # Define arguments self.obj = obj self.category = category @@ -80,6 +87,30 @@ class NotificationMethod: def cleanup(self): return True + # region plugins + def get_plugin(self): + """Returns plugin class""" + return False + + def global_setting_disable(self): + """Check if the method is defined in a plugin and has a global setting""" + # Check if plugin has a setting + if not self.GLOBAL_SETTING: + return False + + # Check if plugin is set + plg_cls = self.get_plugin() + if not plg_cls: + return False + + # Check if method globally enabled + plg_instance = registry.plugins.get(plg_cls.PLUGIN_NAME.lower()) + if plg_instance and not plg_instance.get_setting(self.GLOBAL_SETTING): + return True + + # Lets go! + return False + # endregion class SingleNotificationMethod(NotificationMethod): def send(self, target): diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index ba4ba138cf..dc8b36114f 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -11,6 +11,11 @@ from common.models import InvenTreeUserSetting import InvenTree.tasks +class PlgMixin: + def get_plugin(self): + return CoreNotificationsPlugin + + class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): """ Core notification methods for InvenTree @@ -29,13 +34,15 @@ class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): }, } - class EmailNotification(BulkNotificationMethod): + class EmailNotification(PlgMixin, BulkNotificationMethod): METHOD_NAME = 'mail' CONTEXT_EXTRA = [ ('template', ), ('template', 'html', ), ('template', 'subject', ), ] + GLOBAL_SETTING = 'ENABLE_NOTIFICATION_EMAILS' + } def get_targets(self): """ @@ -43,11 +50,6 @@ class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): only for users which allow email notifications """ - # Check if method globally enabled - plg = registry.plugins.get(CoreNotificationsPlugin.PLUGIN_NAME.lower()) - if plg and not plg.get_setting('ENABLE_NOTIFICATION_EMAILS'): - return - allowed_users = [] for user in self.targets: From 905b164ecbe9d21297f361e0073b3fdd4ac94f25 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 3 Apr 2022 03:47:48 +0200 Subject: [PATCH 09/71] store methods on load --- InvenTree/InvenTree/apps.py | 10 ++++++++++ InvenTree/common/notifications.py | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/apps.py b/InvenTree/InvenTree/apps.py index 6b2f2b1610..7787bbfb0c 100644 --- a/InvenTree/InvenTree/apps.py +++ b/InvenTree/InvenTree/apps.py @@ -31,6 +31,8 @@ class InvenTreeConfig(AppConfig): if not isInTestMode(): # pragma: no cover self.update_exchange_rates() + self.collect_notification_methods() + if canAppAccessDatabase() or settings.TESTING_ENV: self.add_user_on_startup() @@ -197,3 +199,11 @@ class InvenTreeConfig(AppConfig): # do not try again settings.USER_ADDED = True + + def collect_notification_methods(self): + """ + Collect all notification methods + """ + from common.notifications import storage + + storage.collect() diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 6dacac3918..63cd75ec3d 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -122,10 +122,18 @@ class BulkNotificationMethod(NotificationMethod): raise NotImplementedError('The `send` method must be overriden!') +class MethodStorageClass: + liste = None + + def collect(self): + storage.liste = inheritors(NotificationMethod) - IGNORED_NOTIFICATION_CLS + + IGNORED_NOTIFICATION_CLS = set([ SingleNotificationMethod, BulkNotificationMethod, ]) +storage = MethodStorageClass() class UIMessageNotification(SingleNotificationMethod): @@ -190,9 +198,11 @@ def trigger_notifaction(obj, category=None, obj_ref='pk', **kwargs): # Collect possible methods if delivery_methods is None: - delivery_methods = inheritors(NotificationMethod) + delivery_methods = storage.liste + else: + delivery_methods = (delivery_methods - IGNORED_NOTIFICATION_CLS) - for method in (delivery_methods - IGNORED_NOTIFICATION_CLS): + for method in delivery_methods: logger.info(f"Triggering method '{method.METHOD_NAME}'") try: deliver_notification(method, obj, category, targets, context) From 2fee6b02db2a7ad1880ccd0c047134f74472d265 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 3 Apr 2022 03:48:27 +0200 Subject: [PATCH 10/71] structure comment --- InvenTree/common/notifications.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 63cd75ec3d..749af6e63d 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -10,6 +10,7 @@ from plugin import registry logger = logging.getLogger('inventree') +# region methods class NotificationMethod: """ Base class for notification methods @@ -112,6 +113,7 @@ class NotificationMethod: return False # endregion + class SingleNotificationMethod(NotificationMethod): def send(self, target): raise NotImplementedError('The `send` method must be overriden!') @@ -120,6 +122,7 @@ class SingleNotificationMethod(NotificationMethod): class BulkNotificationMethod(NotificationMethod): def send_bulk(self): raise NotImplementedError('The `send` method must be overriden!') +# endregion class MethodStorageClass: From 1eb511e8a07c9a55351aace0477ab935fbd4358e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 4 Apr 2022 23:46:19 +0200 Subject: [PATCH 11/71] user notification settings --- InvenTree/common/models.py | 6 ++ InvenTree/common/notifications.py | 25 +++++++ .../part/templatetags/inventree_extras.py | 5 +- .../builtin/integration/core_notifications.py | 5 ++ .../0005_notificationusersetting.py | 29 ++++++++ InvenTree/plugin/models.py | 69 +++++++++++++++++++ .../plugin/templatetags/plugin_extras.py | 9 ++- .../templates/InvenTree/settings/setting.html | 2 + .../settings/user_notifications.html | 6 +- 9 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 InvenTree/plugin/migrations/0005_notificationusersetting.py diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 70084adfde..c52911feed 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -265,6 +265,12 @@ class BaseInvenTreeSetting(models.Model): filters['plugin'] = plugin kwargs['plugin'] = plugin + # Filter by method + method = kwargs.get('method', None) + + if method is not None: + filters['method'] = method + try: setting = settings.filter(**filters).first() except (ValueError, cls.DoesNotExist): diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 749af6e63d..a7177c9da5 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -5,6 +5,7 @@ from InvenTree.helpers import inheritors from InvenTree.ready import isImportingData from common.models import NotificationEntry, NotificationMessage from plugin import registry +from plugin.models import NotificationUserSetting logger = logging.getLogger('inventree') @@ -20,6 +21,7 @@ class NotificationMethod: CONTEXT_BUILTIN = ['name', 'message', ] CONTEXT_EXTRA = [] GLOBAL_SETTING = None + USER_SETTING = None def __init__(self, obj, category, targets, context) -> None: # Check if a sending fnc is defined @@ -127,10 +129,33 @@ class BulkNotificationMethod(NotificationMethod): class MethodStorageClass: liste = None + user_settings = {} def collect(self): storage.liste = inheritors(NotificationMethod) - IGNORED_NOTIFICATION_CLS + def get_usersettings(self, user): + methods = [] + for item in storage.liste: + if item.USER_SETTING: + new_key = f'NOTIFICATION_METHOD_{item.METHOD_NAME.upper()}' + + # make sure the setting exists + self.user_settings[new_key] = item.USER_SETTING + NotificationUserSetting.get_setting( + key=new_key, + user=user, + method=item.METHOD_NAME, + ) + + # save definition + methods.append({ + 'key': new_key, + 'icon': 'envelope', + 'method': item.METHOD_NAME, + }) + return methods + IGNORED_NOTIFICATION_CLS = set([ SingleNotificationMethod, diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index dc93e00efa..455c66a922 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -27,7 +27,7 @@ import InvenTree.helpers from common.models import InvenTreeSetting, ColorTheme, InvenTreeUserSetting from common.settings import currency_code_default -from plugin.models import PluginSetting +from plugin.models import PluginSetting, NotificationUserSetting register = template.Library() @@ -306,6 +306,9 @@ def setting_object(key, *args, **kwargs): return PluginSetting.get_setting_object(key, plugin=plugin) + if 'method' in kwargs: + return NotificationUserSetting.get_setting_object(key, user=kwargs['user'], method=kwargs['method']) + if 'user' in kwargs: return InvenTreeUserSetting.get_setting_object(key, user=kwargs['user']) diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index dc8b36114f..8fef0be44c 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -42,6 +42,11 @@ class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): ('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): diff --git a/InvenTree/plugin/migrations/0005_notificationusersetting.py b/InvenTree/plugin/migrations/0005_notificationusersetting.py new file mode 100644 index 0000000000..4ea1959f90 --- /dev/null +++ b/InvenTree/plugin/migrations/0005_notificationusersetting.py @@ -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')}, + }, + ), + ] diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 44eeafd012..0109e3f890 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -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 @@ -182,3 +183,71 @@ class PluginSetting(common.models.BaseInvenTreeSetting): verbose_name=_('Plugin'), on_delete=models.CASCADE, ) + + +class NotificationUserSetting(common.models.BaseInvenTreeSetting): + """ + This model represents notification settings for a user + """ + + class Meta: + unique_together = [ + ('method', 'user', 'key'), + ] + + def clean(self, **kwargs): + + kwargs['method'] = self.method + + super().clean(**kwargs) + + """ + We override the following class methods, + so that we can pass the method instance + """ + + def is_bool(self, **kwargs): + + kwargs['method'] = self.method + + return super().is_bool(**kwargs) + + @property + def name(self): + return self.__class__.get_setting_name(self.key, method=self.method) + + @property + def default_value(self): + return self.__class__.get_setting_default(self.key, method=self.method) + + @property + def description(self): + return self.__class__.get_setting_description(self.key, method=self.method) + + @property + def units(self): + return self.__class__.get_setting_units(self.key, method=self.method) + + def choices(self): + return self.__class__.get_setting_choices(self.key, method=self.method) + + @classmethod + def get_setting_definition(cls, key, method, **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'), + ) diff --git a/InvenTree/plugin/templatetags/plugin_extras.py b/InvenTree/plugin/templatetags/plugin_extras.py index 9e83cf96aa..d51e990cc4 100644 --- a/InvenTree/plugin/templatetags/plugin_extras.py +++ b/InvenTree/plugin/templatetags/plugin_extras.py @@ -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,10 @@ 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)) diff --git a/InvenTree/templates/InvenTree/settings/setting.html b/InvenTree/templates/InvenTree/settings/setting.html index 9ef6008292..db1cdec15f 100644 --- a/InvenTree/templates/InvenTree/settings/setting.html +++ b/InvenTree/templates/InvenTree/settings/setting.html @@ -5,6 +5,8 @@ {% setting_object key plugin=plugin as setting %} {% elif user_setting %} {% setting_object key user=request.user as setting %} +{% elif notification_setting %} +{% setting_object key method=method user=request.user as setting %} {% else %} {% setting_object key as setting %} {% endif %} diff --git a/InvenTree/templates/InvenTree/settings/user_notifications.html b/InvenTree/templates/InvenTree/settings/user_notifications.html index 4e9889ca69..6d67d34851 100644 --- a/InvenTree/templates/InvenTree/settings/user_notifications.html +++ b/InvenTree/templates/InvenTree/settings/user_notifications.html @@ -2,6 +2,7 @@ {% load i18n %} {% load inventree_extras %} +{% load plugin_extras %} {% block label %}user-notifications{% endblock label %} @@ -12,7 +13,10 @@
- {% include "InvenTree/settings/setting.html" with key="NOTIFICATION_SEND_EMAILS" icon='fa-envelope' user_setting=True %} + {% notification_settings_list as settings %} + {% for setting in settings %} + {% include "InvenTree/settings/setting.html" with key=setting.key icon=setting.icon method=setting.method notification_setting=True %} + {% endfor %}
From e3ca47804272c716e0ba0efe22f0b226ace219ff Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 4 Apr 2022 23:51:51 +0200 Subject: [PATCH 12/71] PEP fix --- InvenTree/plugin/builtin/integration/core_notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index 8fef0be44c..9f35e58de8 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from allauth.account.models import EmailAddress -from plugin import IntegrationPluginBase, registry +from plugin import IntegrationPluginBase from plugin.mixins import BulkNotificationMethod, SettingsMixin from common.models import InvenTreeUserSetting import InvenTree.tasks From 138aebdb089070bd8caf122db5bbbee39c6ad640 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 4 Apr 2022 23:52:00 +0200 Subject: [PATCH 13/71] and another one --- InvenTree/plugin/templatetags/plugin_extras.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/plugin/templatetags/plugin_extras.py b/InvenTree/plugin/templatetags/plugin_extras.py index d51e990cc4..a30f7ec2e4 100644 --- a/InvenTree/plugin/templatetags/plugin_extras.py +++ b/InvenTree/plugin/templatetags/plugin_extras.py @@ -74,6 +74,7 @@ def plugin_errors(*args, **kwargs): """ return registry.errors + @register.simple_tag(takes_context=True) def notification_settings_list(context, *args, **kwargs): """ From 8d01df0b5e15693af71c0555811cd73d53595c1d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:08:07 +0200 Subject: [PATCH 14/71] refactor settings to reduce duplication --- InvenTree/plugin/models.py | 90 +++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 0109e3f890..39d863d32e 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -102,7 +102,43 @@ class PluginConfig(models.Model): return ret -class PluginSetting(common.models.BaseInvenTreeSetting): +class GenericSettingClassMixin: + + # region generic helpers + REFERENCE_NAME = None + + def _get_reference(self): + return { + self.REFERENCE_NAME: getattr(self, self.REFERENCE_NAME) + } + + def is_bool(self, **kwargs): + + kwargs[self.REFERENCE_NAME] = getattr(self, self.REFERENCE_NAME) + + return super().is_bool(**kwargs) + + @property + def name(self): + return self.__class__.get_setting_name(self.key, **self._get_reference()) + + @property + def default_value(self): + return self.__class__.get_setting_default(self.key, **self._get_reference()) + + @property + def description(self): + return self.__class__.get_setting_description(self.key, **self._get_reference()) + + @property + def units(self): + return self.__class__.get_setting_units(self.key, **self._get_reference()) + + def choices(self): + return self.__class__.get_setting_choices(self.key, **self._get_reference()) + + +class PluginSetting(GenericSettingClassMixin, common.models.BaseInvenTreeSetting): """ This model represents settings for individual plugins """ @@ -123,30 +159,7 @@ class PluginSetting(common.models.BaseInvenTreeSetting): 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): @@ -206,33 +219,10 @@ class NotificationUserSetting(common.models.BaseInvenTreeSetting): so that we can pass the method instance """ - def is_bool(self, **kwargs): - - kwargs['method'] = self.method - - return super().is_bool(**kwargs) - - @property - def name(self): - return self.__class__.get_setting_name(self.key, method=self.method) - - @property - def default_value(self): - return self.__class__.get_setting_default(self.key, method=self.method) - - @property - def description(self): - return self.__class__.get_setting_description(self.key, method=self.method) - - @property - def units(self): - return self.__class__.get_setting_units(self.key, method=self.method) - - def choices(self): - return self.__class__.get_setting_choices(self.key, method=self.method) + REFERENCE_NAME = 'method' @classmethod - def get_setting_definition(cls, key, method, **kwargs): + def get_setting_definition(cls, key, **kwargs): from common.notifications import storage kwargs['settings'] = storage.user_settings From 205916e0b241925bff432f4df147432ab43c8b3f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:14:39 +0200 Subject: [PATCH 15/71] refactor clean method to reduce duplication --- InvenTree/plugin/models.py | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 39d863d32e..3b295c5a54 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -112,6 +112,17 @@ class GenericSettingClassMixin: self.REFERENCE_NAME: getattr(self, self.REFERENCE_NAME) } + """ + We override the following class methods, + so that we can pass the modified key instance as an additional argument + """ + + def clean(self, **kwargs): + + kwargs[self.REFERENCE_NAME] = getattr(self, self.REFERENCE_NAME) + + super().clean(**kwargs) + def is_bool(self, **kwargs): kwargs[self.REFERENCE_NAME] = getattr(self, self.REFERENCE_NAME) @@ -148,17 +159,6 @@ class PluginSetting(GenericSettingClassMixin, 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 - """ - REFERENCE_NAME = 'plugin' @classmethod @@ -208,17 +208,6 @@ class NotificationUserSetting(common.models.BaseInvenTreeSetting): ('method', 'user', 'key'), ] - def clean(self, **kwargs): - - kwargs['method'] = self.method - - super().clean(**kwargs) - - """ - We override the following class methods, - so that we can pass the method instance - """ - REFERENCE_NAME = 'method' @classmethod From 23f57bec19d1f82331fe2a04a780901e48d7bc48 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:15:22 +0200 Subject: [PATCH 16/71] some docs for future users :-) --- InvenTree/plugin/models.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 3b295c5a54..6e81df9b48 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -108,6 +108,15 @@ class GenericSettingClassMixin: REFERENCE_NAME = None def _get_reference(self): + """ + Returns dict that can be used as an argument for kwargs calls. + Helps to make overriden calls generic for simple reuse. + + Usage: + ```python + some_random_function(argument0, kwarg1=value1, **self._get_reference()) + ``` + """ return { self.REFERENCE_NAME: getattr(self, self.REFERENCE_NAME) } From 586812e5c692063ce94fbed6858f272d9bc00797 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:23:09 +0200 Subject: [PATCH 17/71] add doc for generic mixin --- InvenTree/plugin/models.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 6e81df9b48..141318f05f 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -103,8 +103,34 @@ class PluginConfig(models.Model): class GenericSettingClassMixin: + """ + This mixin can be used to add reference keys to static properties + Sample: + ```python + class SampleSetting(GenericSettingClassMixin, common.models.BaseInvenTreeSetting): + class Meta: + unique_together = [ + ('sample', 'key'), + ] + + REFERENCE_NAME = 'sample' + + @classmethod + def get_setting_definition(cls, key, **kwargs): + # mysampledict contains the dict with all settings for this SettingClass - this could also be a dynamic lookup + + kwargs['settings'] = mysampledict + return super().get_setting_definition(key, **kwargs) + + sample = models.charKey( # the name for this field is the additonal key and must be set in the Meta class an REFERENCE_NAME + max_length=256, + verbose_name=_('sample') + ) + ``` + """ # region generic helpers + REFERENCE_NAME = None def _get_reference(self): From 352ab34edaa4a24b5a481622a60c5cefa0f3b458 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:23:30 +0200 Subject: [PATCH 18/71] remove struc docstring --- InvenTree/plugin/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 141318f05f..8c67f1497d 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -129,7 +129,6 @@ class GenericSettingClassMixin: ) ``` """ - # region generic helpers REFERENCE_NAME = None From d634afad4813e76b7b091fd33d73f03845a424cb Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:24:55 +0200 Subject: [PATCH 19/71] move to common/models --- InvenTree/common/models.py | 82 +++++++++++++++++++++++++++++++++++++ InvenTree/plugin/models.py | 84 +------------------------------------- 2 files changed, 83 insertions(+), 83 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index c52911feed..c1885e0aad 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -636,6 +636,88 @@ class BaseInvenTreeSetting(models.Model): return setting.get('protected', False) +class GenericSettingClassMixin: + """ + This mixin can be used to add reference keys to static properties + + Sample: + ```python + class SampleSetting(GenericSettingClassMixin, common.models.BaseInvenTreeSetting): + class Meta: + unique_together = [ + ('sample', 'key'), + ] + + REFERENCE_NAME = 'sample' + + @classmethod + def get_setting_definition(cls, key, **kwargs): + # mysampledict contains the dict with all settings for this SettingClass - this could also be a dynamic lookup + + kwargs['settings'] = mysampledict + return super().get_setting_definition(key, **kwargs) + + sample = models.charKey( # the name for this field is the additonal key and must be set in the Meta class an REFERENCE_NAME + max_length=256, + verbose_name=_('sample') + ) + ``` + """ + + REFERENCE_NAME = None + + def _get_reference(self): + """ + Returns dict that can be used as an argument for kwargs calls. + Helps to make overriden calls generic for simple reuse. + + Usage: + ```python + some_random_function(argument0, kwarg1=value1, **self._get_reference()) + ``` + """ + return { + self.REFERENCE_NAME: getattr(self, self.REFERENCE_NAME) + } + + """ + We override the following class methods, + so that we can pass the modified key instance as an additional argument + """ + + def clean(self, **kwargs): + + kwargs[self.REFERENCE_NAME] = getattr(self, self.REFERENCE_NAME) + + super().clean(**kwargs) + + def is_bool(self, **kwargs): + + kwargs[self.REFERENCE_NAME] = getattr(self, self.REFERENCE_NAME) + + return super().is_bool(**kwargs) + + @property + def name(self): + return self.__class__.get_setting_name(self.key, **self._get_reference()) + + @property + def default_value(self): + return self.__class__.get_setting_default(self.key, **self._get_reference()) + + @property + def description(self): + return self.__class__.get_setting_description(self.key, **self._get_reference()) + + @property + def units(self): + return self.__class__.get_setting_units(self.key, **self._get_reference()) + + def choices(self): + return self.__class__.get_setting_choices(self.key, **self._get_reference()) + + + def settings_group_options(): """ Build up group tuple for settings based on your choices diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 8c67f1497d..9975015d01 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -101,89 +101,7 @@ class PluginConfig(models.Model): return ret - -class GenericSettingClassMixin: - """ - This mixin can be used to add reference keys to static properties - - Sample: - ```python - class SampleSetting(GenericSettingClassMixin, common.models.BaseInvenTreeSetting): - class Meta: - unique_together = [ - ('sample', 'key'), - ] - - REFERENCE_NAME = 'sample' - - @classmethod - def get_setting_definition(cls, key, **kwargs): - # mysampledict contains the dict with all settings for this SettingClass - this could also be a dynamic lookup - - kwargs['settings'] = mysampledict - return super().get_setting_definition(key, **kwargs) - - sample = models.charKey( # the name for this field is the additonal key and must be set in the Meta class an REFERENCE_NAME - max_length=256, - verbose_name=_('sample') - ) - ``` - """ - - REFERENCE_NAME = None - - def _get_reference(self): - """ - Returns dict that can be used as an argument for kwargs calls. - Helps to make overriden calls generic for simple reuse. - - Usage: - ```python - some_random_function(argument0, kwarg1=value1, **self._get_reference()) - ``` - """ - return { - self.REFERENCE_NAME: getattr(self, self.REFERENCE_NAME) - } - - """ - We override the following class methods, - so that we can pass the modified key instance as an additional argument - """ - - def clean(self, **kwargs): - - kwargs[self.REFERENCE_NAME] = getattr(self, self.REFERENCE_NAME) - - super().clean(**kwargs) - - def is_bool(self, **kwargs): - - kwargs[self.REFERENCE_NAME] = getattr(self, self.REFERENCE_NAME) - - return super().is_bool(**kwargs) - - @property - def name(self): - return self.__class__.get_setting_name(self.key, **self._get_reference()) - - @property - def default_value(self): - return self.__class__.get_setting_default(self.key, **self._get_reference()) - - @property - def description(self): - return self.__class__.get_setting_description(self.key, **self._get_reference()) - - @property - def units(self): - return self.__class__.get_setting_units(self.key, **self._get_reference()) - - def choices(self): - return self.__class__.get_setting_choices(self.key, **self._get_reference()) - - -class PluginSetting(GenericSettingClassMixin, common.models.BaseInvenTreeSetting): +class PluginSetting(common.models.GenericSettingClassMixin, common.models.BaseInvenTreeSetting): """ This model represents settings for individual plugins """ From 2728bfab373549ee746a58b9b3514066a9d86460 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:25:39 +0200 Subject: [PATCH 20/71] fix: add genericSettingClassMixin back in --- InvenTree/plugin/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 9975015d01..29bea5d02e 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -150,7 +150,7 @@ class PluginSetting(common.models.GenericSettingClassMixin, common.models.BaseIn ) -class NotificationUserSetting(common.models.BaseInvenTreeSetting): +class NotificationUserSetting(common.models.GenericSettingClassMixin, common.models.BaseInvenTreeSetting): """ This model represents notification settings for a user """ From dc7b88515e35fcfd627f7660976737392fd21815 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:27:13 +0200 Subject: [PATCH 21/71] rename class to make it clearer --- InvenTree/common/models.py | 4 ++-- InvenTree/plugin/models.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index c1885e0aad..5b1debc7aa 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -636,13 +636,13 @@ class BaseInvenTreeSetting(models.Model): return setting.get('protected', False) -class GenericSettingClassMixin: +class GenericReferencedSettingClass: """ This mixin can be used to add reference keys to static properties Sample: ```python - class SampleSetting(GenericSettingClassMixin, common.models.BaseInvenTreeSetting): + class SampleSetting(GenericReferencedSettingClass, common.models.BaseInvenTreeSetting): class Meta: unique_together = [ ('sample', 'key'), diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 29bea5d02e..4614a079cc 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -101,7 +101,7 @@ class PluginConfig(models.Model): return ret -class PluginSetting(common.models.GenericSettingClassMixin, common.models.BaseInvenTreeSetting): +class PluginSetting(common.models.GenericReferencedSettingClass, common.models.BaseInvenTreeSetting): """ This model represents settings for individual plugins """ @@ -150,7 +150,7 @@ class PluginSetting(common.models.GenericSettingClassMixin, common.models.BaseIn ) -class NotificationUserSetting(common.models.GenericSettingClassMixin, common.models.BaseInvenTreeSetting): +class NotificationUserSetting(common.models.GenericReferencedSettingClass, common.models.BaseInvenTreeSetting): """ This model represents notification settings for a user """ From 01fcc728ad0706d74662f8ccf97fd40dbcb01770 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:34:30 +0200 Subject: [PATCH 22/71] PEP fixes --- InvenTree/common/models.py | 1 - InvenTree/plugin/models.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 5b1debc7aa..1c1060a3b2 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -717,7 +717,6 @@ class GenericReferencedSettingClass: return self.__class__.get_setting_choices(self.key, **self._get_reference()) - def settings_group_options(): """ Build up group tuple for settings based on your choices diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 4614a079cc..f80a4ced85 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -101,6 +101,7 @@ class PluginConfig(models.Model): return ret + class PluginSetting(common.models.GenericReferencedSettingClass, common.models.BaseInvenTreeSetting): """ This model represents settings for individual plugins From 7063db7a3280d52ab2b3c697909beb373f5a2c63 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:43:29 +0200 Subject: [PATCH 23/71] add missing model ruleset --- InvenTree/users/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index b29341d1b8..806db9d53a 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -78,6 +78,7 @@ class RuleSet(models.Model): 'otp_static_staticdevice', 'plugin_pluginconfig', 'plugin_pluginsetting', + 'plugin_notificationusersetting', ], 'part_category': [ 'part_partcategory', From 32918348b9248df86d1c4ba2e2ee4b2175d95e2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:55:53 +0200 Subject: [PATCH 24/71] fix docstrings --- InvenTree/common/test_notifications.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/InvenTree/common/test_notifications.py b/InvenTree/common/test_notifications.py index 3c0009fb51..a436b99da3 100644 --- a/InvenTree/common/test_notifications.py +++ b/InvenTree/common/test_notifications.py @@ -83,7 +83,10 @@ class BaseNotificationTests(BaseNotificationIntegrationTest): class BulkNotificationMethodTests(BaseNotificationIntegrationTest): def test_BulkNotificationMethod(self): - """ensure the implementation requirements are tested""" + """ + Ensure the implementation requirements are tested. + NotImplementedError needs to raise if the send_bulk() method is not set. + """ class WrongImplementation(BulkNotificationMethod): METHOD_NAME = 'WrongImplementationBulk' @@ -98,7 +101,10 @@ class BulkNotificationMethodTests(BaseNotificationIntegrationTest): class SingleNotificationMethodTests(BaseNotificationIntegrationTest): def test_SingleNotificationMethod(self): - """ensure the implementation requirements are tested""" + """ + Ensure the implementation requirements are tested. + NotImplementedError needs to raise if the send() method is not set. + """ class WrongImplementation(SingleNotificationMethod): METHOD_NAME = 'WrongImplementationSingle' From 41ccacaa05984c8a941dcdd08e03abc40b508f5f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 00:56:13 +0200 Subject: [PATCH 25/71] disable email by default --- InvenTree/plugin/builtin/integration/core_notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index 9f35e58de8..ac08db6a83 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -29,7 +29,7 @@ class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): 'ENABLE_NOTIFICATION_EMAILS': { 'name': _('Enable email notifications'), 'description': _('Allow sending of emails for event notifications'), - 'default': True, + 'default': False, 'validator': bool, }, } From ad9d067033c519261a0b8c3fd9ea9cfea1753657 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 01:08:35 +0200 Subject: [PATCH 26/71] typo fix --- InvenTree/part/test_part.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index 040b2c9e68..e1db3a78f3 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -494,7 +494,7 @@ class BaseNotificationIntegrationTest(TestCase): self.part = Part.objects.get(name='R_2K2_0805') def _notification_run(self): - # There should be no notification runs + # There should be no notification runs self.assertEqual(NotificationEntry.objects.all().count(), 0) # Test that notifications run through without errors From 42f630cff14fcff2be3af5fb3986dba7dc1a1837 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 01:10:52 +0200 Subject: [PATCH 27/71] fix tests due to changes in the loading mechansim they need to be fethced now --- InvenTree/part/test_part.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index e1db3a78f3..5e5c84c3ea 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -19,6 +19,7 @@ from .templatetags import inventree_extras import part.settings from common.models import InvenTreeSetting, NotificationEntry, NotificationMessage +from common.notifications import storage class TemplateTagTest(TestCase): @@ -494,6 +495,9 @@ class BaseNotificationIntegrationTest(TestCase): self.part = Part.objects.get(name='R_2K2_0805') def _notification_run(self): + # reload notification methods + storage.collect() + # There should be no notification runs self.assertEqual(NotificationEntry.objects.all().count(), 0) From 785cdc526737322cbef89a7ecf5a2dff429fefee Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 01:29:19 +0200 Subject: [PATCH 28/71] patch loading methods for selective loading --- InvenTree/common/notifications.py | 9 +++++++-- InvenTree/common/test_notifications.py | 4 ++-- InvenTree/part/test_part.py | 8 ++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index a7177c9da5..083cbba5e1 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -131,8 +131,13 @@ class MethodStorageClass: liste = None user_settings = {} - def collect(self): - storage.liste = inheritors(NotificationMethod) - IGNORED_NOTIFICATION_CLS + def collect(self, selected_classes=None): + current_method = inheritors(NotificationMethod) - IGNORED_NOTIFICATION_CLS + + # for testing selective loading is made available + if selected_classes: + current_method = [item for item in current_method if item is selected_classes] + storage.liste = current_method def get_usersettings(self, user): methods = [] diff --git a/InvenTree/common/test_notifications.py b/InvenTree/common/test_notifications.py index a436b99da3..30d4713a3e 100644 --- a/InvenTree/common/test_notifications.py +++ b/InvenTree/common/test_notifications.py @@ -95,7 +95,7 @@ class BulkNotificationMethodTests(BaseNotificationIntegrationTest): return [1, ] with self.assertRaises(NotImplementedError): - self._notification_run() + self._notification_run(WrongImplementation) class SingleNotificationMethodTests(BaseNotificationIntegrationTest): @@ -113,6 +113,6 @@ class SingleNotificationMethodTests(BaseNotificationIntegrationTest): return [1, ] with self.assertRaises(NotImplementedError): - self._notification_run() + self._notification_run(WrongImplementation) # A integration test for notifications is provided in test_part.PartNotificationTest diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index 5e5c84c3ea..7ceeed8f01 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -494,9 +494,13 @@ class BaseNotificationIntegrationTest(TestCase): # Define part that will be tested self.part = Part.objects.get(name='R_2K2_0805') - def _notification_run(self): + def _notification_run(self, run_class=None): + """ + Run a notification test suit through. + If you only want to test one class pass it to run_class + """ # reload notification methods - storage.collect() + storage.collect(run_class) # There should be no notification runs self.assertEqual(NotificationEntry.objects.all().count(), 0) From 5b9f84a90fc3ed57dfe9f7c93432fb1a0b54744f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 01:30:12 +0200 Subject: [PATCH 29/71] fix test --- InvenTree/common/test_notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/common/test_notifications.py b/InvenTree/common/test_notifications.py index 30d4713a3e..6eec11e056 100644 --- a/InvenTree/common/test_notifications.py +++ b/InvenTree/common/test_notifications.py @@ -77,7 +77,7 @@ class BaseNotificationTests(BaseNotificationIntegrationTest): def send(self, target): raise KeyError('This could be any error') - self._notification_run() + self._notification_run(ErrorImplementation) class BulkNotificationMethodTests(BaseNotificationIntegrationTest): From 5f4c8b3a3e8c6bfa000ec7535195ebed9d75a097 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 01:53:41 +0200 Subject: [PATCH 30/71] add icon support for user settings --- InvenTree/common/notifications.py | 3 ++- InvenTree/plugin/builtin/integration/core_notifications.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 083cbba5e1..fbe0bd1365 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -18,6 +18,7 @@ class NotificationMethod: """ METHOD_NAME = '' + METHOD_ICON = None CONTEXT_BUILTIN = ['name', 'message', ] CONTEXT_EXTRA = [] GLOBAL_SETTING = None @@ -156,7 +157,7 @@ class MethodStorageClass: # save definition methods.append({ 'key': new_key, - 'icon': 'envelope', + 'icon': getattr(item, 'METHOD_ICON', ''), 'method': item.METHOD_NAME, }) return methods diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index ac08db6a83..f21fe33703 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -36,6 +36,7 @@ class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): class EmailNotification(PlgMixin, BulkNotificationMethod): METHOD_NAME = 'mail' + METHOD_ICON = 'fa-envelope' CONTEXT_EXTRA = [ ('template', ), ('template', 'html', ), From 347be2aa3a6c3fba1a2c298582243efff5c373e4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 01:58:28 +0200 Subject: [PATCH 31/71] fix test --- InvenTree/part/test_part.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index 7ceeed8f01..82d2579868 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -19,7 +19,7 @@ from .templatetags import inventree_extras import part.settings from common.models import InvenTreeSetting, NotificationEntry, NotificationMessage -from common.notifications import storage +from common.notifications import storage, UIMessageNotification class TemplateTagTest(TestCase): @@ -524,7 +524,7 @@ class PartNotificationTest(BaseNotificationIntegrationTest): """ Integration test for part notifications """ def test_notification(self): - self._notification_run() + self._notification_run(UIMessageNotification) # There should be 1 notification message right now self.assertEqual(NotificationMessage.objects.all().count(), 1) From 47f9bd911a8230de08c2a33c45459613702de1e9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 23:41:37 +0200 Subject: [PATCH 32/71] add tests for notifications --- InvenTree/common/test_notifications.py | 48 +++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/test_notifications.py b/InvenTree/common/test_notifications.py index 6eec11e056..366850e9d1 100644 --- a/InvenTree/common/test_notifications.py +++ b/InvenTree/common/test_notifications.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from common.notifications import NotificationMethod, SingleNotificationMethod, BulkNotificationMethod +from common.notifications import NotificationMethod, SingleNotificationMethod, BulkNotificationMethod, storage +from plugin.models import NotificationUserSetting from part.test_part import BaseNotificationIntegrationTest +import plugin.templatetags.plugin_extras as plugin_tags class BaseNotificationTests(BaseNotificationIntegrationTest): @@ -116,3 +118,47 @@ class SingleNotificationMethodTests(BaseNotificationIntegrationTest): self._notification_run(WrongImplementation) # A integration test for notifications is provided in test_part.PartNotificationTest + +class NotificationUserSettingTests(BaseNotificationIntegrationTest): + """ Tests for NotificationUserSetting """ + + def setUp(self): + super().setUp() + self.client.login(username=self.user.username, password='password') + + def test_setting_attributes(self): + """check notification method plugin methods: usersettings and tags """ + + class SampleImplementation(BulkNotificationMethod): + METHOD_NAME = 'test' + GLOBAL_SETTING = 'ENABLE_NOTIFICATION_TEST' + USER_SETTING = { + 'name': 'Enable test notifications', + 'description': 'Allow sending of test for event notifications', + 'default': True, + 'validator': bool, + 'units': 'alpha', + } + + def get_targets(self): + return [1, ] + + def send_bulk(self): + return True + + # run thorugh notification + self._notification_run(SampleImplementation) + # make sure the array fits + array = storage.get_usersettings(self.user) + setting = NotificationUserSetting.objects.all().first() + + # assertions for settings + self.assertEqual(setting.name, 'Enable test notifications') + self.assertEqual(setting.default_value, True) + self.assertEqual(setting.description, 'Allow sending of test for event notifications') + self.assertEqual(setting.units, 'alpha') + + # test tag and array + self.assertEqual(plugin_tags.notification_settings_list({'user': self.user}), array) + self.assertEqual(array[0]['key'], 'NOTIFICATION_METHOD_TEST') + self.assertEqual(array[0]['method'], 'test') From f3aab952de02b3bc3b64f537bb027a6a0b0a6e80 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 23:43:50 +0200 Subject: [PATCH 33/71] remove empty class --- InvenTree/common/test_notifications.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/InvenTree/common/test_notifications.py b/InvenTree/common/test_notifications.py index 366850e9d1..af30456c0d 100644 --- a/InvenTree/common/test_notifications.py +++ b/InvenTree/common/test_notifications.py @@ -38,15 +38,6 @@ class BaseNotificationTests(BaseNotificationIntegrationTest): def send(self): """a comment so we do not need a pass""" - class WrongDeliveryImplementation(SingleNotificationMethod): - METHOD_NAME = 'WrongDeliveryImplementation' - - def get_targets(self): - return [1, ] - - def send(self, target): - return False - # no send / send bulk with self.assertRaises(NotImplementedError): FalseNotificationMethod('', '', '', '', ) From 77f5fa9360a40766589e42f76a193cc9d9003194 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 23:45:35 +0200 Subject: [PATCH 34/71] pep fix --- InvenTree/common/test_notifications.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/test_notifications.py b/InvenTree/common/test_notifications.py index af30456c0d..5fd78f6fa0 100644 --- a/InvenTree/common/test_notifications.py +++ b/InvenTree/common/test_notifications.py @@ -112,7 +112,7 @@ class SingleNotificationMethodTests(BaseNotificationIntegrationTest): class NotificationUserSettingTests(BaseNotificationIntegrationTest): """ Tests for NotificationUserSetting """ - + def setUp(self): super().setUp() self.client.login(username=self.user.username, password='password') @@ -120,6 +120,7 @@ class NotificationUserSettingTests(BaseNotificationIntegrationTest): def test_setting_attributes(self): """check notification method plugin methods: usersettings and tags """ + class SampleImplementation(BulkNotificationMethod): METHOD_NAME = 'test' GLOBAL_SETTING = 'ENABLE_NOTIFICATION_TEST' From e1d261f989b897f757576c94b816de97505c2326 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 23:47:26 +0200 Subject: [PATCH 35/71] pep fix --- InvenTree/common/test_notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/common/test_notifications.py b/InvenTree/common/test_notifications.py index 5fd78f6fa0..719d9291de 100644 --- a/InvenTree/common/test_notifications.py +++ b/InvenTree/common/test_notifications.py @@ -110,6 +110,7 @@ class SingleNotificationMethodTests(BaseNotificationIntegrationTest): # A integration test for notifications is provided in test_part.PartNotificationTest + class NotificationUserSettingTests(BaseNotificationIntegrationTest): """ Tests for NotificationUserSetting """ @@ -120,7 +121,6 @@ class NotificationUserSettingTests(BaseNotificationIntegrationTest): def test_setting_attributes(self): """check notification method plugin methods: usersettings and tags """ - class SampleImplementation(BulkNotificationMethod): METHOD_NAME = 'test' GLOBAL_SETTING = 'ENABLE_NOTIFICATION_TEST' From b4654a7c719ccae3b5851ccd5143f165c74c2076 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 6 Apr 2022 01:22:52 +0200 Subject: [PATCH 36/71] add tests for email notification --- .../builtin/integration/core_notifications.py | 4 +-- .../integration/test_core_notifications.py | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 InvenTree/plugin/builtin/integration/test_core_notifications.py diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index f21fe33703..3d5ce06cb6 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -7,7 +7,7 @@ from allauth.account.models import EmailAddress from plugin import IntegrationPluginBase from plugin.mixins import BulkNotificationMethod, SettingsMixin -from common.models import InvenTreeUserSetting +from plugin.models import NotificationUserSetting import InvenTree.tasks @@ -59,7 +59,7 @@ class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): allowed_users = [] for user in self.targets: - allows_emails = InvenTreeUserSetting.get_setting('NOTIFICATION_SEND_EMAILS', user=user) + allows_emails = NotificationUserSetting.get_setting('NOTIFICATION_METHOD_MAIL', user=user, method=self.METHOD_NAME) if allows_emails: allowed_users.append(user) diff --git a/InvenTree/plugin/builtin/integration/test_core_notifications.py b/InvenTree/plugin/builtin/integration/test_core_notifications.py new file mode 100644 index 0000000000..a65c36eca2 --- /dev/null +++ b/InvenTree/plugin/builtin/integration/test_core_notifications.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from allauth.account.models import EmailAddress + +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) From b53bf6de9ce3c1ae72ad568e3388e9b71720e938 Mon Sep 17 00:00:00 2001 From: Matthias Mair <66015116+matmair@users.noreply.github.com> Date: Wed, 6 Apr 2022 01:42:24 +0200 Subject: [PATCH 37/71] pep fix --- InvenTree/plugin/builtin/integration/core_notifications.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index 3d5ce06cb6..dbcf606c7f 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -3,8 +3,6 @@ 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 from plugin.models import NotificationUserSetting From b087422290bb0e27296f9ac9f89e1e8f3d46f661 Mon Sep 17 00:00:00 2001 From: Matthias Mair <66015116+matmair@users.noreply.github.com> Date: Wed, 6 Apr 2022 01:45:19 +0200 Subject: [PATCH 38/71] add import back in --- InvenTree/plugin/builtin/integration/core_notifications.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index dbcf606c7f..3d5ce06cb6 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -3,6 +3,8 @@ 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 from plugin.models import NotificationUserSetting From 547c51b81b768add1ec80437a8b8a26f9734856e Mon Sep 17 00:00:00 2001 From: Matthias Mair <66015116+matmair@users.noreply.github.com> Date: Wed, 6 Apr 2022 01:47:11 +0200 Subject: [PATCH 39/71] pep fix --- InvenTree/plugin/builtin/integration/test_core_notifications.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/test_core_notifications.py b/InvenTree/plugin/builtin/integration/test_core_notifications.py index a65c36eca2..02c91784e5 100644 --- a/InvenTree/plugin/builtin/integration/test_core_notifications.py +++ b/InvenTree/plugin/builtin/integration/test_core_notifications.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from allauth.account.models import EmailAddress - from plugin.models import NotificationUserSetting from part.test_part import BaseNotificationIntegrationTest from plugin.builtin.integration.core_notifications import CoreNotificationsPlugin From 4c1a0ea9cef3d7b29deffc6a9716921693808092 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 6 Apr 2022 23:58:11 +0200 Subject: [PATCH 40/71] mkae user setting access simpler --- InvenTree/common/notifications.py | 8 +++++++- .../plugin/builtin/integration/core_notifications.py | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index fbe0bd1365..1f1b743c1c 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -114,6 +114,12 @@ class NotificationMethod: # Lets go! return False + + def usersetting(self, target): + """ + Returns setting for this method for a given user + """ + return NotificationUserSetting.get_setting(f'NOTIFICATION_METHOD_{self.METHOD_NAME.upper()}', user=target, method=self.METHOD_NAME) # endregion @@ -220,7 +226,7 @@ def trigger_notifaction(obj, category=None, obj_ref='pk', **kwargs): if NotificationEntry.check_recent(category, obj_ref_value, delta): logger.info(f"Notification '{category}' has recently been sent for '{str(obj)}' - SKIPPING") - return + # return logger.info(f"Gathering users for notification '{category}'") # Collect possible targets diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index 3d5ce06cb6..a26c69e877 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -7,7 +7,6 @@ from allauth.account.models import EmailAddress from plugin import IntegrationPluginBase from plugin.mixins import BulkNotificationMethod, SettingsMixin -from plugin.models import NotificationUserSetting import InvenTree.tasks @@ -59,7 +58,7 @@ class CoreNotificationsPlugin(SettingsMixin, IntegrationPluginBase): allowed_users = [] for user in self.targets: - allows_emails = NotificationUserSetting.get_setting('NOTIFICATION_METHOD_MAIL', user=user, method=self.METHOD_NAME) + allows_emails = self.usersetting(user) if allows_emails: allowed_users.append(user) From ddcaecb11fcf2bf28d9d6e81a78219aedc06cbc3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 7 Apr 2022 23:39:05 +0200 Subject: [PATCH 41/71] How did I miss this? --- InvenTree/common/notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 1f1b743c1c..8e36dd02b3 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -226,7 +226,7 @@ def trigger_notifaction(obj, category=None, obj_ref='pk', **kwargs): if NotificationEntry.check_recent(category, obj_ref_value, delta): logger.info(f"Notification '{category}' has recently been sent for '{str(obj)}' - SKIPPING") - # return + return logger.info(f"Gathering users for notification '{category}'") # Collect possible targets From 8490589fa9b70287fef21dca4ac1a7dd3300e38b Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 1 May 2022 21:46:31 +0000 Subject: [PATCH 42/71] check for only unique methods to be added --- InvenTree/common/notifications.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 8e36dd02b3..c9dd08e8da 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -139,12 +139,21 @@ class MethodStorageClass: user_settings = {} def collect(self, selected_classes=None): + print('collecting') current_method = inheritors(NotificationMethod) - IGNORED_NOTIFICATION_CLS # for testing selective loading is made available if selected_classes: current_method = [item for item in current_method if item is selected_classes] - storage.liste = current_method + + # make sure only one of each method is added + filtered_list = {} + for item in current_method: + plugin = item.get_plugin(item) + ref = f'{plugin.package_path}_{item.METHOD_NAME}' if plugin else item.METHOD_NAME + filtered_list[ref] = item + + storage.liste = list(filtered_list.values()) def get_usersettings(self, user): methods = [] From e418a54c255a62a31bc58904c1670b1f4b6083a1 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 1 May 2022 21:48:45 +0000 Subject: [PATCH 43/71] PEP fix --- InvenTree/common/notifications.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index c9dd08e8da..fe6b195a4f 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -145,12 +145,12 @@ class MethodStorageClass: # for testing selective loading is made available if selected_classes: current_method = [item for item in current_method if item is selected_classes] - + # make sure only one of each method is added filtered_list = {} - for item in current_method: + for item in current_method: 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 filtered_list[ref] = item storage.liste = list(filtered_list.values()) From df9ca599a21f25fa9796d0b5b70323c3c38fe56f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 May 2022 15:18:36 +0200 Subject: [PATCH 44/71] use full names consistently --- InvenTree/order/serializers.py | 4 ++-- InvenTree/order/test_views.py | 2 +- InvenTree/report/tests.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 7d26ce741d..f818667d0f 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -875,7 +875,7 @@ class SalesOrderShipmentCompleteSerializer(serializers.ModelSerializer): shipment.complete_shipment(user, tracking_number=tracking_number) -class SOShipmentAllocationItemSerializer(serializers.Serializer): +class SalesOrderShipmentAllocationItemSerializer(serializers.Serializer): """ A serializer for allocating a single stock-item against a SalesOrder shipment """ @@ -1148,7 +1148,7 @@ class SalesOrderShipmentAllocationSerializer(serializers.Serializer): 'shipment', ] - items = SOShipmentAllocationItemSerializer(many=True) + items = SalesOrderShipmentAllocationItemSerializer(many=True) shipment = serializers.PrimaryKeyRelatedField( queryset=order.models.SalesOrderShipment.objects.all(), diff --git a/InvenTree/order/test_views.py b/InvenTree/order/test_views.py index 220c1688db..beb0f09265 100644 --- a/InvenTree/order/test_views.py +++ b/InvenTree/order/test_views.py @@ -59,7 +59,7 @@ class OrderListTest(OrderViewTestCase): self.assertEqual(response.status_code, 200) -class POTests(OrderViewTestCase): +class PurchaseOrderTests(OrderViewTestCase): """ Tests for PurchaseOrder views """ def test_detail_view(self): diff --git a/InvenTree/report/tests.py b/InvenTree/report/tests.py index f99128999c..a40990b527 100644 --- a/InvenTree/report/tests.py +++ b/InvenTree/report/tests.py @@ -227,7 +227,7 @@ class BOMReportTest(ReportTest): print_url = 'api-bom-report-print' -class POReportTest(ReportTest): +class PurchaseOrderReportTest(ReportTest): model = report_models.PurchaseOrderReport @@ -236,7 +236,7 @@ class POReportTest(ReportTest): print_url = 'api-po-report-print' -class SOReportTest(ReportTest): +class SalesOrderReportTest(ReportTest): model = report_models.SalesOrderReport From c684e7d5e0bd6618093b69619b2052b64c3949f6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 May 2022 16:57:39 +0200 Subject: [PATCH 45/71] Add generic serializer --- InvenTree/common/serializers.py | 42 +++++++++++++++++++++++++++++++++ InvenTree/plugin/serializers.py | 22 ++++++----------- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index 86d45cd881..b5b0b0928d 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -99,6 +99,48 @@ class UserSettingsSerializer(SettingsSerializer): ] +class GenericReferencedSettingSerializer(SettingsSerializer): + """ + Serializer for a GenericReferencedSetting model + + Args: + MODEL: model class for the serializer + EXTRA_FIELDS: fields that need to be appended to the serializer + field must also be defined in the custom class + """ + + MODEL = None + EXTRA_FIELDS = None + + def __init__(self, instance=None, data=..., **kwargs): + """Init overrides the Meta class to make it dynamic""" + # set Meta class + self.Meta = self.CustomMeta + self.Meta.model = self.MODEL + + # extend the fields + if not self.CustomMeta.DEFINED: + self.Meta.fields.extend(self.EXTRA_FIELDS) + self.CustomMeta.DEFINED = True + + # resume operations + super().__init__(instance, data, **kwargs) + + class CustomMeta: + """Scaffold for custom Meta class""" + DEFINED: bool = False + + fields = [ + 'pk', + 'key', + 'value', + 'name', + 'description', + 'type', + 'choices', + ] + + class NotificationMessageSerializer(InvenTreeModelSerializer): """ Serializer for the InvenTreeUserSetting model diff --git a/InvenTree/plugin/serializers.py b/InvenTree/plugin/serializers.py index 7a76c48067..6a7cfcf0c9 100644 --- a/InvenTree/plugin/serializers.py +++ b/InvenTree/plugin/serializers.py @@ -16,7 +16,7 @@ from django.utils import timezone from rest_framework import serializers from plugin.models import PluginConfig, PluginSetting -from common.serializers import SettingsSerializer +from common.serializers import GenericReferencedSettingSerializer class PluginConfigSerializer(serializers.ModelSerializer): @@ -128,22 +128,14 @@ class PluginConfigInstallSerializer(serializers.Serializer): return ret -class PluginSettingSerializer(SettingsSerializer): +class PluginSettingSerializer(GenericReferencedSettingSerializer): """ Serializer for the PluginSetting model """ - plugin = serializers.PrimaryKeyRelatedField(read_only=True) + MODEL = PluginSetting + EXTRA_FIELDS = [ + 'plugin', + ] - class Meta: - model = PluginSetting - fields = [ - 'pk', - 'key', - 'value', - 'name', - 'description', - 'type', - 'choices', - 'plugin', - ] + plugin = serializers.PrimaryKeyRelatedField(read_only=True) From 34855bbb375d01479e7ab4381f3aa74b3be8ac00 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 May 2022 17:38:50 +0200 Subject: [PATCH 46/71] add API endpoint for notifications --- InvenTree/common/api.py | 49 +++++++++++++++++++++++++++++++++ InvenTree/plugin/serializers.py | 13 ++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/api.py b/InvenTree/common/api.py index 2f7f3e2ca8..3132f6a16d 100644 --- a/InvenTree/common/api.py +++ b/InvenTree/common/api.py @@ -24,6 +24,8 @@ from django_q.tasks import async_task import common.models import common.serializers from InvenTree.helpers import inheritors +from plugin.models import NotificationUserSetting +from plugin.serializers import NotificationUserSettingSerializer class CsrfExemptMixin(object): @@ -219,6 +221,44 @@ class UserSettingsDetail(generics.RetrieveUpdateAPIView): ] +class NotificationUserSettingsList(SettingsList): + """ + API endpoint for accessing a list of notification user settings objects + """ + + queryset = NotificationUserSetting.objects.all() + serializer_class = NotificationUserSettingSerializer + + def filter_queryset(self, queryset): + """ + Only list settings which apply to the current user + """ + + try: + user = self.request.user + except AttributeError: + return NotificationUserSetting.objects.none() + + queryset = super().filter_queryset(queryset) + queryset = queryset.filter(user=user) + return queryset + + +class NotificationUserSettingsDetail(generics.RetrieveUpdateAPIView): + """ + Detail view for an individual "notification user setting" object + + - User can only view / edit settings their own settings objects + """ + + queryset = NotificationUserSetting.objects.all() + serializer_class = NotificationUserSettingSerializer + + permission_classes = [ + UserSettingsPermissions, + ] + + class NotificationList(generics.ListAPIView): queryset = common.models.NotificationMessage.objects.all() serializer_class = common.serializers.NotificationMessageSerializer @@ -344,6 +384,15 @@ settings_api_urls = [ re_path(r'^.*$', UserSettingsList.as_view(), name='api-user-setting-list'), ])), + # Notification settings + re_path(r'^notification/', include([ + # Notification Settings Detail + re_path(r'^(?P\d+)/', NotificationUserSettingsDetail.as_view(), name='api-notification-setting-detail'), + + # Notification Settings List + re_path(r'^.*$', NotificationUserSettingsList.as_view(), name='api-notifcation-setting-list'), + ])), + # Global settings re_path(r'^global/', include([ # Global Settings Detail diff --git a/InvenTree/plugin/serializers.py b/InvenTree/plugin/serializers.py index 6a7cfcf0c9..276604b390 100644 --- a/InvenTree/plugin/serializers.py +++ b/InvenTree/plugin/serializers.py @@ -15,7 +15,7 @@ from django.utils import timezone from rest_framework import serializers -from plugin.models import PluginConfig, PluginSetting +from plugin.models import PluginConfig, PluginSetting, NotificationUserSetting from common.serializers import GenericReferencedSettingSerializer @@ -139,3 +139,14 @@ class PluginSettingSerializer(GenericReferencedSettingSerializer): ] plugin = serializers.PrimaryKeyRelatedField(read_only=True) + + +class NotificationUserSettingSerializer(GenericReferencedSettingSerializer): + """ + Serializer for the PluginSetting model + """ + + MODEL = NotificationUserSetting + EXTRA_FIELDS = ['method', ] + + method = serializers.CharField(read_only=True) From 0ff407348d6316fd731b28b3bf87d17a547a1928 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 May 2022 17:39:37 +0200 Subject: [PATCH 47/71] Add admin for NotificationUserSetting --- InvenTree/plugin/admin.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/InvenTree/plugin/admin.py b/InvenTree/plugin/admin.py index ba5148e6e3..fbc9cc7167 100644 --- a/InvenTree/plugin/admin.py +++ b/InvenTree/plugin/admin.py @@ -70,4 +70,19 @@ 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) From b16305afef495076913cd207258e966f0047d78d Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 May 2022 17:40:04 +0200 Subject: [PATCH 48/71] add html for notification setting --- InvenTree/templates/InvenTree/settings/setting.html | 2 +- InvenTree/templates/InvenTree/settings/settings.html | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/templates/InvenTree/settings/setting.html b/InvenTree/templates/InvenTree/settings/setting.html index db1cdec15f..95865700fe 100644 --- a/InvenTree/templates/InvenTree/settings/setting.html +++ b/InvenTree/templates/InvenTree/settings/setting.html @@ -24,7 +24,7 @@ {% if setting.is_bool %}
- +
{% else %}
diff --git a/InvenTree/templates/InvenTree/settings/settings.html b/InvenTree/templates/InvenTree/settings/settings.html index 0039e8ee1a..b35ec0107a 100644 --- a/InvenTree/templates/InvenTree/settings/settings.html +++ b/InvenTree/templates/InvenTree/settings/settings.html @@ -70,6 +70,7 @@ $('table').find('.boolean-setting').change(function() { var pk = $(this).attr('pk'); var plugin = $(this).attr('plugin'); var user = $(this).attr('user'); + var notification = $(this).attr('notification'); var checked = this.checked; @@ -80,6 +81,8 @@ $('table').find('.boolean-setting').change(function() { url = `/api/plugin/settings/${pk}/`; } else if (user) { url = `/api/settings/user/${pk}/`; + } else if (notification) { + url = `/api/settings/notification/${pk}/`; } inventreePut( From cf986e557cbe59afe5ad9ecf20c0c55e2d14cd58 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 May 2022 17:40:44 +0200 Subject: [PATCH 49/71] make unloading safer --- InvenTree/plugin/registry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 304932f6f8..1f50233976 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -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: + 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 From 05d1f9703e7f419889853570cb69ca9211528dc2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 May 2022 17:42:45 +0200 Subject: [PATCH 50/71] Add nice repr for NotificationUserSetting --- InvenTree/plugin/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index f80a4ced85..0624693abc 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -183,3 +183,6 @@ class NotificationUserSetting(common.models.GenericReferencedSettingClass, commo verbose_name=_('User'), help_text=_('User'), ) + + def __str__(self) -> str: + return f'{self.key} (for {self.user}): {self.value}' From 17127e914dc1d2c5707138ed42d9136efc1809e9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 May 2022 17:44:27 +0200 Subject: [PATCH 51/71] PEP fix --- InvenTree/plugin/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/plugin/admin.py b/InvenTree/plugin/admin.py index fbc9cc7167..256b5ce0da 100644 --- a/InvenTree/plugin/admin.py +++ b/InvenTree/plugin/admin.py @@ -84,5 +84,6 @@ class NotificationUserSettingAdmin(admin.ModelAdmin): def has_add_permission(self, request): return False + admin.site.register(models.PluginConfig, PluginConfigAdmin) admin.site.register(models.NotificationUserSetting, NotificationUserSettingAdmin) From 164a8acf3306fd9e46d5f04fb18820fabe902f2c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 May 2022 01:31:19 +0200 Subject: [PATCH 52/71] make genericref simpler --- InvenTree/common/serializers.py | 39 ++++++++++++++------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index b5b0b0928d..f8cb2b6bd3 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -112,33 +112,28 @@ class GenericReferencedSettingSerializer(SettingsSerializer): MODEL = None EXTRA_FIELDS = None - def __init__(self, instance=None, data=..., **kwargs): + def __init__(self, **kwargs): """Init overrides the Meta class to make it dynamic""" - # set Meta class - self.Meta = self.CustomMeta - self.Meta.model = self.MODEL + class CustomMeta: + """Scaffold for custom Meta class""" + fields = [ + 'pk', + 'key', + 'value', + 'name', + 'description', + 'type', + 'choices', + ] + # set Meta class + self.Meta = CustomMeta + self.Meta.model = self.MODEL # extend the fields - if not self.CustomMeta.DEFINED: - self.Meta.fields.extend(self.EXTRA_FIELDS) - self.CustomMeta.DEFINED = True + self.Meta.fields.extend(self.EXTRA_FIELDS) # resume operations - super().__init__(instance, data, **kwargs) - - class CustomMeta: - """Scaffold for custom Meta class""" - DEFINED: bool = False - - fields = [ - 'pk', - 'key', - 'value', - 'name', - 'description', - 'type', - 'choices', - ] + super().__init__(**kwargs) class NotificationMessageSerializer(InvenTreeModelSerializer): From fe4b264ae176fea31f8ff0dfb24c9e7047eb32da Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 00:11:26 +0200 Subject: [PATCH 53/71] Fix user check --- InvenTree/common/models.py | 2 +- InvenTree/part/templatetags/inventree_extras.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index f1b5b8c82f..1434bba95e 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -1537,7 +1537,7 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): ) @classmethod - def get_setting_object(cls, key, user): + def get_setting_object(cls, key, user=None): return super().get_setting_object(key, user=user) def validate_unique(self, exclude=None, **kwargs): diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 9545708811..902f0b26d2 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -329,6 +329,8 @@ def settings_value(key, *args, **kwargs): """ if 'user' in kwargs: + if not kwargs['user']: + return InvenTreeUserSetting.get_setting(key) return InvenTreeUserSetting.get_setting(key, user=kwargs['user']) return InvenTreeSetting.get_setting(key) From 1676cb8eb8eb5dcd1b2d051182ecd91ffe2659d0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 00:13:58 +0200 Subject: [PATCH 54/71] fix error with anon connections and language check --- InvenTree/part/templatetags/inventree_extras.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 3eba8368af..35d1878081 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -548,7 +548,8 @@ class I18nStaticNode(StaticNode): # Store the original (un-rendered) path template, as it gets overwritten below self.original = self.path.var - self.path.var = self.original.format(lng=context.request.LANGUAGE_CODE) + if hasattr(context, 'request'): + self.path.var = self.original.format(lng=context.request.LANGUAGE_CODE) ret = super().render(context) From ccefaade926d78d04c1de42df63261dee165c94a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 00:48:20 +0200 Subject: [PATCH 55/71] Add args back in to serializer --- InvenTree/common/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index f8cb2b6bd3..9d637c3e39 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -112,7 +112,7 @@ class GenericReferencedSettingSerializer(SettingsSerializer): MODEL = None EXTRA_FIELDS = None - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): """Init overrides the Meta class to make it dynamic""" class CustomMeta: """Scaffold for custom Meta class""" @@ -133,7 +133,7 @@ class GenericReferencedSettingSerializer(SettingsSerializer): self.Meta.fields.extend(self.EXTRA_FIELDS) # resume operations - super().__init__(**kwargs) + super().__init__(*args, **kwargs) class NotificationMessageSerializer(InvenTreeModelSerializer): From 50b17d6ae84a70b261617b3293b339f15bf59435 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 01:03:11 +0200 Subject: [PATCH 56/71] add mixin test --- InvenTree/plugin/test_plugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/InvenTree/plugin/test_plugin.py b/InvenTree/plugin/test_plugin.py index c0835c2fb3..6a9cf38ecd 100644 --- a/InvenTree/plugin/test_plugin.py +++ b/InvenTree/plugin/test_plugin.py @@ -35,6 +35,10 @@ class InvenTreePluginTests(TestCase): """check if a basic plugin is active""" self.assertEqual(self.plugin.is_active(), False) + def test_mixins(self): + """check if mixins fnc works""" + self.assertEqual(self.named_plugin.mixins(), {}) + class PluginTagTests(TestCase): """ Tests for the plugin extras """ From a77382d0b597cf0fd3ea91278e73a11a7de8802f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 01:04:00 +0200 Subject: [PATCH 57/71] sadd test for notification user settings --- InvenTree/common/tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index e2966fb8d5..0747b6ea48 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -377,6 +377,16 @@ class UserSettingsApiTest(InvenTreeAPITestCase): ) +class NotificationUserSettingsApiTest(InvenTreeAPITestCase): + """Tests for the notification user settings API""" + + def test_api_list(self): + """Test list URL""" + url = reverse('api-notifcation-setting-list') + + self.get(url, expected_code=200) + + class WebhookMessageTests(TestCase): def setUp(self): self.endpoint_def = WebhookEndpoint.objects.create() From 1a70829f5ca7ca9590caed54d2c1c1e2a51e9453 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 01:12:11 +0200 Subject: [PATCH 58/71] do not cover dev error catcher --- InvenTree/plugin/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index d3e8000fd9..d817b862de 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -525,7 +525,7 @@ class PluginsRegistry: # remove model from admin site try: admin.site.unregister(model) - except: + except: # pragma: no cover pass models += [model._meta.model_name] except LookupError: # pragma: no cover From 7f8d20da0fb32f318c45145acbe7a99f539412d3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 01:12:22 +0200 Subject: [PATCH 59/71] test _str --- InvenTree/common/tests.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 0747b6ea48..c6b062ebf4 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -10,6 +10,7 @@ from django.urls import reverse from InvenTree.api_tester import InvenTreeAPITestCase from InvenTree.helpers import str2bool +from plugin.models import NotificationUserSetting from .models import InvenTreeSetting, InvenTreeUserSetting, WebhookEndpoint, WebhookMessage, NotificationEntry from .api import WebhookView @@ -386,6 +387,11 @@ class NotificationUserSettingsApiTest(InvenTreeAPITestCase): self.get(url, expected_code=200) + def test_setting(self): + """Test the string name for NotificationUserSetting""" + test_setting = NotificationUserSetting.get_setting_object('NOTIFICATION_METHOD_MAIL', user=self.user) + self.assertEqual(str(test_setting), 'NOTIFICATION_METHOD_MAIL (for testuser): ') + class WebhookMessageTests(TestCase): def setUp(self): From 2b1d4ba9c1c361593620e638728b2511f077b44e Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 01:15:12 +0200 Subject: [PATCH 60/71] add test for plugin settings --- InvenTree/common/tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index c6b062ebf4..23b330be0d 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -393,6 +393,16 @@ class NotificationUserSettingsApiTest(InvenTreeAPITestCase): self.assertEqual(str(test_setting), 'NOTIFICATION_METHOD_MAIL (for testuser): ') +class PluginSettingsApiTest(InvenTreeAPITestCase): + """Tests for the plugin settings API""" + + def test_api_list(self): + """Test list URL""" + url = reverse('api-plugin-setting-list') + + self.get(url, expected_code=200) + + class WebhookMessageTests(TestCase): def setUp(self): self.endpoint_def = WebhookEndpoint.objects.create() From 8f220837d092c00b3b30d2705121b983a618c6c5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 01:25:22 +0200 Subject: [PATCH 61/71] only log messages about notification methods --- InvenTree/common/notifications.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index fe6b195a4f..ef4de4fc61 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -139,7 +139,7 @@ class MethodStorageClass: user_settings = {} def collect(self, selected_classes=None): - print('collecting') + logger.info('collecting notification methods') current_method = inheritors(NotificationMethod) - IGNORED_NOTIFICATION_CLS # for testing selective loading is made available @@ -154,6 +154,7 @@ class MethodStorageClass: filtered_list[ref] = item storage.liste = list(filtered_list.values()) + logger.info(f'found {len(storage.liste)} notification methods') def get_usersettings(self, user): methods = [] From d5c95f2225e1132a94c2f095391204db48122147 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 01:29:16 +0200 Subject: [PATCH 62/71] only safety test - api is not callable as non-user --- InvenTree/common/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/common/api.py b/InvenTree/common/api.py index 3132f6a16d..fc50f0f50e 100644 --- a/InvenTree/common/api.py +++ b/InvenTree/common/api.py @@ -200,7 +200,7 @@ class UserSettingsPermissions(permissions.BasePermission): try: user = request.user - except AttributeError: + except AttributeError: # pragma: no cover return False return user == obj.user From a2331829c9d79977250563e2cfe4488473a2f441 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 01:31:11 +0200 Subject: [PATCH 63/71] this can not happen with normal operation --- InvenTree/common/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/common/api.py b/InvenTree/common/api.py index fc50f0f50e..f8aaa2ded9 100644 --- a/InvenTree/common/api.py +++ b/InvenTree/common/api.py @@ -147,7 +147,7 @@ class GlobalSettingsPermissions(permissions.BasePermission): user = request.user return user.is_staff - except AttributeError: + except AttributeError: # pragma: no cover return False @@ -181,7 +181,7 @@ class UserSettingsList(SettingsList): try: user = self.request.user - except AttributeError: + except AttributeError: # pragma: no cover return common.models.InvenTreeUserSetting.objects.none() queryset = super().filter_queryset(queryset) From 9a9a7e0ff48627b2c007ee0e89db05805ee4dfbc Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 01:31:23 +0200 Subject: [PATCH 64/71] also cover list API --- InvenTree/common/tests.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 23b330be0d..a8ab25a8d8 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -534,6 +534,11 @@ class NotificationTest(TestCase): self.assertTrue(NotificationEntry.check_recent('test.notification', 1, delta)) + def test_api_list(self): + """Test list URL""" + url = reverse('api-notifications-list') + self.get(url, expected_code=200) + class LoadingTest(TestCase): """ From baf7e4505e436b23d8de97931e8269623877d2d4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 01:52:32 +0200 Subject: [PATCH 65/71] do not cover pacakge code - is not CI'd --- InvenTree/plugin/loader.py | 2 +- InvenTree/plugin/plugin.py | 2 +- InvenTree/plugin/registry.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/plugin/loader.py b/InvenTree/plugin/loader.py index aaba9fe060..538bd2358b 100644 --- a/InvenTree/plugin/loader.py +++ b/InvenTree/plugin/loader.py @@ -15,5 +15,5 @@ class PluginTemplateLoader(FilesystemLoader): for plugin in registry.plugins.values(): new_path = Path(plugin.path) / dirname if Path(new_path).is_dir(): - template_dirs.append(new_path) + template_dirs.append(new_path) # pragma: no cover return tuple(template_dirs) diff --git a/InvenTree/plugin/plugin.py b/InvenTree/plugin/plugin.py index b83297dcfd..c6af7f20d8 100644 --- a/InvenTree/plugin/plugin.py +++ b/InvenTree/plugin/plugin.py @@ -80,7 +80,7 @@ class InvenTreePluginBase(): if cfg: return cfg.active else: - return False + return False # pragma: no cover # TODO @matmair remove after InvenTree 0.7.0 release diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 240bd3446b..9462b780cb 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -503,7 +503,7 @@ class PluginsRegistry: try: # for local path plugins plugin_path = '.'.join(pathlib.Path(plugin.path).relative_to(settings.BASE_DIR).parts) - except ValueError: + except ValueError: # pragma: no cover # plugin is shipped as package plugin_path = plugin.PLUGIN_NAME return plugin_path From 0eff4a45266112e120013b54134721f525fbe172 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 02:07:59 +0200 Subject: [PATCH 66/71] can not be covered in testing --- InvenTree/part/templatetags/inventree_extras.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 3eba8368af..5d991e9fd5 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -540,7 +540,7 @@ class I18nStaticNode(StaticNode): custom StaticNode replaces a variable named *lng* in the path with the current language """ - def render(self, context): + def render(self, context): # pragma: no cover self.original = getattr(self, 'original', None) @@ -563,7 +563,7 @@ if settings.DEBUG: """ simple tag to enable {% url %} functionality instead of {% static %} """ return reverse(url_name) -else: +else: # pragma: no cover @register.tag('i18n_static') def do_i18n_static(parser, token): From 20d552f8097a2a460d341b3e8c785687f4c37387 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 02:13:25 +0200 Subject: [PATCH 67/71] extend tag tests --- InvenTree/part/test_part.py | 55 +++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index 811acebc69..8cc622ba83 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -18,17 +18,51 @@ from .templatetags import inventree_extras import part.settings +from InvenTree import version from common.models import InvenTreeSetting, NotificationEntry, NotificationMessage class TemplateTagTest(TestCase): """ Tests for the custom template tag code """ + def setUp(self): + # Create a user for auth + user = get_user_model() + self.user = user.objects.create_user('testuser', 'test@testing.com', 'password') + self.client.login(username='testuser', password='password') + + def test_define(self): + self.assertEqual(int(inventree_extras.define(3)), 3) + + def test_str2bool(self): + self.assertEqual(int(inventree_extras.str2bool('true')), True) + self.assertEqual(int(inventree_extras.str2bool('yes')), True) + self.assertEqual(int(inventree_extras.str2bool('none')), False) + self.assertEqual(int(inventree_extras.str2bool('off')), False) + + def test_inrange(self): + self.assertEqual(int(inventree_extras.inrange(3)), range(3)) + def test_multiply(self): self.assertEqual(int(inventree_extras.multiply(3, 5)), 15) - def test_version(self): - self.assertEqual(type(inventree_extras.inventree_version()), str) + def test_add(self): + self.assertEqual(int(inventree_extras.add(3, 5)), 8) + + def test_plugins_enabled(self): + self.assertEqual(inventree_extras.plugins_enabled(), True) + + def test_inventree_instance_name(self): + self.assertEqual(inventree_extras.inventree_instance_name(), 'InvenTree server') + + def test_inventree_base_url(self): + self.assertEqual(inventree_extras.inventree_base_url(), '') + + def test_inventree_is_release(self): + self.assertEqual(inventree_extras.inventree_is_release(), not version.isInvenTreeDevelopmentVersion()) + + def test_inventree_docs_version(self): + self.assertEqual(inventree_extras.inventree_docs_version(), version.inventreeDocsVersion()) def test_hash(self): result_hash = inventree_extras.inventree_commit_hash() @@ -44,6 +78,23 @@ class TemplateTagTest(TestCase): def test_docs(self): self.assertIn('inventree.readthedocs.io', inventree_extras.inventree_docs_url()) + def test_keyvalue(self): + self.assertEqual(inventree_extras.keyvalue({'a': 'a'}), 'a') + + def test_mail_configured(self): + self.assertEqual(inventree_extras.mail_configured(), False) + + def test_user_settings(self): + result = inventree_extras.user_settings(self.user) + self.assertEqual(len(result), 5) + + def test_global_settings(self): + result = inventree_extras.global_settings() + self.assertEqual(len(result), 20) + + def test_visible_global_settings(self): + result = inventree_extras.visible_global_settings() + self.assertEqual(len(result), 20) class PartTest(TestCase): """ Tests for the Part model """ From 33e3385830947e9ddeeb46f9c1434b2ad07f4a24 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 02:17:53 +0200 Subject: [PATCH 68/71] fix assertations --- InvenTree/part/test_part.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index 8cc622ba83..5e183d18c4 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -41,7 +41,7 @@ class TemplateTagTest(TestCase): self.assertEqual(int(inventree_extras.str2bool('off')), False) def test_inrange(self): - self.assertEqual(int(inventree_extras.inrange(3)), range(3)) + self.assertEqual(inventree_extras.inrange(3), range(3)) def test_multiply(self): self.assertEqual(int(inventree_extras.multiply(3, 5)), 15) @@ -79,22 +79,22 @@ class TemplateTagTest(TestCase): self.assertIn('inventree.readthedocs.io', inventree_extras.inventree_docs_url()) def test_keyvalue(self): - self.assertEqual(inventree_extras.keyvalue({'a': 'a'}), 'a') + self.assertEqual(inventree_extras.keyvalue({'a': 'a'}, 'a'), 'a') def test_mail_configured(self): - self.assertEqual(inventree_extras.mail_configured(), False) + self.assertEqual(inventree_extras.mail_configured(), True) def test_user_settings(self): result = inventree_extras.user_settings(self.user) - self.assertEqual(len(result), 5) + self.assertEqual(len(result), 36) def test_global_settings(self): result = inventree_extras.global_settings() - self.assertEqual(len(result), 20) + self.assertEqual(len(result), 61) def test_visible_global_settings(self): result = inventree_extras.visible_global_settings() - self.assertEqual(len(result), 20) + self.assertEqual(len(result), 60) class PartTest(TestCase): """ Tests for the Part model """ From 2261e7a615edb5baff1ca57398dd8e3d87683095 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 02:20:38 +0200 Subject: [PATCH 69/71] PEP fix --- InvenTree/part/test_part.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index 5e183d18c4..c8e07ba605 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -96,6 +96,7 @@ class TemplateTagTest(TestCase): result = inventree_extras.visible_global_settings() self.assertEqual(len(result), 60) + class PartTest(TestCase): """ Tests for the Part model """ From 0d0b713f6e1485230fb47355c8506149f52bcbb9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 02:26:58 +0200 Subject: [PATCH 70/71] Fix tests --- InvenTree/common/tests.py | 2 +- InvenTree/plugin/test_plugin.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index a8ab25a8d8..dee776f7d9 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -515,7 +515,7 @@ class WebhookMessageTests(TestCase): assert message.body == {"this": "is a message"} -class NotificationTest(TestCase): +class NotificationTest(InvenTreeAPITestCase): def test_check_notification_entries(self): diff --git a/InvenTree/plugin/test_plugin.py b/InvenTree/plugin/test_plugin.py index 6a9cf38ecd..c0835c2fb3 100644 --- a/InvenTree/plugin/test_plugin.py +++ b/InvenTree/plugin/test_plugin.py @@ -35,10 +35,6 @@ class InvenTreePluginTests(TestCase): """check if a basic plugin is active""" self.assertEqual(self.plugin.is_active(), False) - def test_mixins(self): - """check if mixins fnc works""" - self.assertEqual(self.named_plugin.mixins(), {}) - class PluginTagTests(TestCase): """ Tests for the plugin extras """ From 09ceada63997272e3fca635de72b87f6e2b48a0a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 May 2022 03:03:08 +0200 Subject: [PATCH 71/71] fix test assertation --- InvenTree/part/test_part.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index c8e07ba605..5a5fc6b055 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -82,7 +82,7 @@ class TemplateTagTest(TestCase): self.assertEqual(inventree_extras.keyvalue({'a': 'a'}, 'a'), 'a') def test_mail_configured(self): - self.assertEqual(inventree_extras.mail_configured(), True) + self.assertEqual(inventree_extras.mail_configured(), False) def test_user_settings(self): result = inventree_extras.user_settings(self.user)