From 547db3322fb3f67e3c6f9fbef329c7c6bd73501d Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 1 Jan 2022 22:00:43 +1100 Subject: [PATCH] Adds new "PluginSetting" class - Adds settings which are unique to a particular plugin --- InvenTree/common/models.py | 168 ++++++++++++++++++------------------- InvenTree/plugin/admin.py | 12 ++- InvenTree/plugin/models.py | 52 ++++++++++-- 3 files changed, 136 insertions(+), 96 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index c514d7c4a9..0babfaa8e3 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -65,13 +65,13 @@ class BaseInvenTreeSetting(models.Model): self.key = str(self.key).upper() - self.clean() + self.clean(**kwargs) self.validate_unique() super().save() @classmethod - def allValues(cls, user=None, exclude_hidden=False): + def allValues(cls, user=None, plugin=None, exclude_hidden=False): """ Return a dict of "all" defined global settings. @@ -82,9 +82,14 @@ class BaseInvenTreeSetting(models.Model): results = cls.objects.all() + # Optionally filter by user if user is not None: results = results.filter(user=user) + # Optionally filter by plugin + if plugin is not None: + results = results.filter(plugin=plugin) + # Query the database settings = {} @@ -123,98 +128,92 @@ class BaseInvenTreeSetting(models.Model): return settings @classmethod - def get_setting_name(cls, key): + def get_setting_definition(cls, key, **kwargs): + """ + Return the 'definition' of a particular settings value, as a dict object. + + - The 'settings' dict can be passed as a kwarg + - If not passed, look for cls.GLOBAL_SETTINGS + - Returns an empty dict if the key is not found + """ + + settings = kwargs.get('settings', cls.GLOBAL_SETTINGS) + + key = str(key).strip().upper() + + if settings is not None and key in settings: + return settings[key] + else: + return {} + + @classmethod + def get_setting_name(cls, key, **kwargs): """ Return the name of a particular setting. If it does not exist, return an empty string. """ - key = str(key).strip().upper() - - if key in cls.GLOBAL_SETTINGS: - setting = cls.GLOBAL_SETTINGS[key] - return setting.get('name', '') - else: - return '' + setting = cls.get_setting_definition(key, **kwargs) + return setting.get('name', '') @classmethod - def get_setting_description(cls, key): + def get_setting_description(cls, key, **kwargs): """ Return the description for a particular setting. If it does not exist, return an empty string. """ - key = str(key).strip().upper() + setting = cls.get_setting_definition(key, **kwargs) - if key in cls.GLOBAL_SETTINGS: - setting = cls.GLOBAL_SETTINGS[key] - return setting.get('description', '') - else: - return '' + return setting.get('description', '') @classmethod - def get_setting_units(cls, key): + def get_setting_units(cls, key, **kwargs): """ Return the units for a particular setting. If it does not exist, return an empty string. """ - key = str(key).strip().upper() + setting = cls.get_setting_definition(key, **kwargs) - if key in cls.GLOBAL_SETTINGS: - setting = cls.GLOBAL_SETTINGS[key] - return setting.get('units', '') - else: - return '' + return setting.get('units', '') @classmethod - def get_setting_validator(cls, key): + def get_setting_validator(cls, key, **kwargs): """ Return the validator for a particular setting. If it does not exist, return None """ - key = str(key).strip().upper() + setting = cls.get_setting_definition(key, **kwargs) - if key in cls.GLOBAL_SETTINGS: - setting = cls.GLOBAL_SETTINGS[key] - return setting.get('validator', None) - else: - return None + return setting.get('validator', None) @classmethod - def get_setting_default(cls, key): + def get_setting_default(cls, key, **kwargs): """ Return the default value for a particular setting. If it does not exist, return an empty string """ - key = str(key).strip().upper() + setting = cls.get_setting_definition(key, **kwargs) - if key in cls.GLOBAL_SETTINGS: - setting = cls.GLOBAL_SETTINGS[key] - return setting.get('default', '') - else: - return '' + return setting.get('default', '') @classmethod - def get_setting_choices(cls, key): + def get_setting_choices(cls, key, **kwargs): """ Return the validator choices available for a particular setting. """ - key = str(key).strip().upper() + setting = cls.get_setting_definition(key, **kwargs) - if key in cls.GLOBAL_SETTINGS: - setting = cls.GLOBAL_SETTINGS[key] - choices = setting.get('choices', None) - else: - choices = None + choices = setting.get('choices', None) if callable(choices): # Evaluate the function (we expect it will return a list of tuples...) @@ -237,8 +236,20 @@ class BaseInvenTreeSetting(models.Model): key = str(key).strip().upper() + settings = cls.objects.all() + + user = kwargs.get('user', None) + + if user is not None: + settings = settings.filter(user=user) + + plugin = kwargs.get('plugin', None) + + if plugin is not None: + settings = settings.filter(plugin=plugin) + try: - setting = cls.objects.filter(**cls.get_filters(key, **kwargs)).first() + setting = settings.filter(**cls.get_filters(key, **kwargs)).first() except (ValueError, cls.DoesNotExist): setting = None except (IntegrityError, OperationalError): @@ -247,7 +258,12 @@ class BaseInvenTreeSetting(models.Model): # Setting does not exist! (Try to create it) if not setting: - setting = cls(key=key, value=cls.get_setting_default(key), **kwargs) + # Attempt to create a new settings object + setting = cls( + key=key, + value=cls.get_setting_default(key, **kwargs), + **kwargs + ) try: # Wrap this statement in "atomic", so it can be rolled back if it fails @@ -259,21 +275,6 @@ class BaseInvenTreeSetting(models.Model): return setting - @classmethod - def get_setting_pk(cls, key): - """ - Return the primary-key value for a given setting. - - If the setting does not exist, return None - """ - - setting = cls.get_setting_object(cls) - - if setting: - return setting.pk - else: - return None - @classmethod def get_setting(cls, key, backup_value=None, **kwargs): """ @@ -283,18 +284,19 @@ class BaseInvenTreeSetting(models.Model): # If no backup value is specified, atttempt to retrieve a "default" value if backup_value is None: - backup_value = cls.get_setting_default(key) + backup_value = cls.get_setting_default(key, **kwargs) setting = cls.get_setting_object(key, **kwargs) if setting: value = setting.value - # If the particular setting is defined as a boolean, cast the value to a boolean - if setting.is_bool(): + # Cast to boolean if necessary + if setting.is_bool(**kwargs): value = InvenTree.helpers.str2bool(value) - if setting.is_int(): + # Cast to integer if necessary + if setting.is_int(**kwargs): try: value = int(value) except (ValueError, TypeError): @@ -357,7 +359,7 @@ class BaseInvenTreeSetting(models.Model): def units(self): return self.__class__.get_setting_units(self.key) - def clean(self): + def clean(self, **kwargs): """ If a validator (or multiple validators) are defined for a particular setting key, run them against the 'value' field. @@ -365,7 +367,7 @@ class BaseInvenTreeSetting(models.Model): super().clean() - validator = self.__class__.get_setting_validator(self.key) + validator = self.__class__.get_setting_validator(self.key, **kwargs) if self.is_bool(): self.value = InvenTree.helpers.str2bool(self.value) @@ -459,12 +461,12 @@ class BaseInvenTreeSetting(models.Model): return [opt[0] for opt in choices] - def is_bool(self): + def is_bool(self, **kwargs): """ Check if this setting is required to be a boolean value """ - validator = self.__class__.get_setting_validator(self.key) + validator = self.__class__.get_setting_validator(self.key, **kwargs) return self.__class__.validator_is_bool(validator) @@ -477,15 +479,15 @@ class BaseInvenTreeSetting(models.Model): return InvenTree.helpers.str2bool(self.value) - def setting_type(self): + def setting_type(self, **kwargs): """ Return the field type identifier for this setting object """ - if self.is_bool(): + if self.is_bool(**kwargs): return 'boolean' - elif self.is_int(): + elif self.is_int(**kwargs): return 'integer' else: @@ -504,12 +506,12 @@ class BaseInvenTreeSetting(models.Model): return False - def is_int(self): + def is_int(self, **kwargs): """ Check if the setting is required to be an integer value: """ - validator = self.__class__.get_setting_validator(self.key) + validator = self.__class__.get_setting_validator(self.key, **kwargs) return self.__class__.validator_is_int(validator) @@ -541,17 +543,14 @@ class BaseInvenTreeSetting(models.Model): return value @classmethod - def is_protected(cls, key): + def is_protected(cls, key, **kwargs): """ Check if the setting value is protected """ - key = str(key).strip().upper() + setting = cls.get_setting_definition(key, **kwargs) - if key in cls.GLOBAL_SETTINGS: - return cls.GLOBAL_SETTINGS[key].get('protected', False) - else: - return False + return setting.get('protected', False) def settings_group_options(): @@ -977,13 +976,6 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'validator': bool, 'requires_restart': True, }, - 'ENABLE_PLUGINS_GLOBALSETTING': { - 'name': _('Enable global setting integration'), - 'description': _('Enable plugins to integrate into inventree global settings'), - 'default': False, - 'validator': bool, - 'requires_restart': True, - }, 'ENABLE_PLUGINS_APP': { 'name': _('Enable app integration'), 'description': _('Enable plugins to add apps'), diff --git a/InvenTree/plugin/admin.py b/InvenTree/plugin/admin.py index 3a96a4b9ea..4bd4664212 100644 --- a/InvenTree/plugin/admin.py +++ b/InvenTree/plugin/admin.py @@ -35,12 +35,20 @@ def plugin_deactivate(modeladmin, request, queryset): plugin_update(queryset, False) +class PluginSettingInline(admin.TabularInline): + """ + Inline admin class for PluginSetting + """ + + model = models.PluginSetting + + class PluginConfigAdmin(admin.ModelAdmin): """Custom admin with restricted id fields""" readonly_fields = ["key", "name", ] - list_display = ['active', '__str__', 'key', 'name', ] + list_display = ['name', 'key', '__str__', 'active', ] list_filter = ['active'] actions = [plugin_activate, plugin_deactivate, ] - + inlines = [PluginSettingInline,] admin.site.register(models.PluginConfig, PluginConfigAdmin) diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 93c6335497..b001798544 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -8,16 +8,17 @@ from __future__ import unicode_literals from django.utils.translation import gettext_lazy as _ from django.db import models +import common.models + from plugin import plugin_reg class PluginConfig(models.Model): - """ A PluginConfig object holds settings for plugins. - - It is used to designate a Part as 'subscribed' for a given User. + """ + A PluginConfig object holds settings for plugins. Attributes: - key: slug of the plugin - must be unique + key: slug of the plugin (this must be unique across all installed plugins!) name: PluginName of the plugin - serves for a manual double check if the right plugin is used active: Should the plugin be loaded? """ @@ -63,7 +64,10 @@ class PluginConfig(models.Model): # functions def __init__(self, *args, **kwargs): - """override to set original state of""" + """ + Override to set original state of the plugin-config instance + """ + super().__init__(*args, **kwargs) self.__org_active = self.active @@ -82,7 +86,9 @@ class PluginConfig(models.Model): } def save(self, force_insert=False, force_update=False, *args, **kwargs): - """extend save method to reload plugins if the 'active' status changes""" + """ + Extend save method to reload plugins if the 'active' status changes + """ reload = kwargs.pop('no_reload', False) # check if no_reload flag is set ret = super().save(force_insert, force_update, *args, **kwargs) @@ -95,3 +101,37 @@ class PluginConfig(models.Model): plugin_reg.reload_plugins() return ret + + +class PluginSetting(common.models.BaseInvenTreeSetting): + """ + This model represents settings for individual plugins + """ + + class Meta: + unique_together = [ + ('plugin', 'key'), + ] + + @classmethod + def get_filters(cls, key, **kwargs): + """ + Override filters method to ensure settings are filtered by plugin id + """ + + filters = super().get_filters(key, **kwargs) + + plugin = kwargs.get('plugin', None) + + if plugin: + filters['plugin'] = plugin + + return filters + + plugin = models.ForeignKey( + PluginConfig, + related_name='settings', + null=False, + verbose_name=_('Plugin'), + on_delete=models.CASCADE, + )