diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 11157763cb..37a6289d75 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -136,6 +136,19 @@ class BaseInvenTreeSetting(models.Model): return settings + def get_kwargs(self): + """ + Construct kwargs for doing class-based settings lookup, + depending on *which* class we are. + + This is necessary to abtract the settings object + from the implementing class (e.g plugins) + + Subclasses should override this function to ensure the kwargs are correctly set. + """ + + return {} + @classmethod def get_setting_definition(cls, key, **kwargs): """ @@ -319,11 +332,11 @@ class BaseInvenTreeSetting(models.Model): value = setting.value # Cast to boolean if necessary - if setting.is_bool(**kwargs): + if setting.is_bool(): value = InvenTree.helpers.str2bool(value) # Cast to integer if necessary - if setting.is_int(**kwargs): + if setting.is_int(): try: value = int(value) except (ValueError, TypeError): @@ -390,19 +403,19 @@ class BaseInvenTreeSetting(models.Model): @property def name(self): - return self.__class__.get_setting_name(self.key) + return self.__class__.get_setting_name(self.key, **self.get_kwargs()) @property def default_value(self): - return self.__class__.get_setting_default(self.key) + return self.__class__.get_setting_default(self.key, **self.get_kwargs()) @property def description(self): - return self.__class__.get_setting_description(self.key) + return self.__class__.get_setting_description(self.key, **self.get_kwargs()) @property def units(self): - return self.__class__.get_setting_units(self.key) + return self.__class__.get_setting_units(self.key, **self.get_kwargs()) def clean(self, **kwargs): """ @@ -512,12 +525,12 @@ class BaseInvenTreeSetting(models.Model): except self.DoesNotExist: pass - def choices(self, **kwargs): + def choices(self): """ Return the available choices for this setting (or None if no choices are defined) """ - return self.__class__.get_setting_choices(self.key, **kwargs) + return self.__class__.get_setting_choices(self.key, **self.get_kwargs()) def valid_options(self): """ @@ -531,14 +544,14 @@ class BaseInvenTreeSetting(models.Model): return [opt[0] for opt in choices] - def is_choice(self, **kwargs): + def is_choice(self): """ Check if this setting is a "choice" field """ - return self.__class__.get_setting_choices(self.key, **kwargs) is not None + return self.__class__.get_setting_choices(self.key, **self.get_kwargs()) is not None - def as_choice(self, **kwargs): + def as_choice(self): """ Render this setting as the "display" value of a choice field, e.g. if the choices are: @@ -547,7 +560,7 @@ class BaseInvenTreeSetting(models.Model): then display 'A4 paper' """ - choices = self.get_setting_choices(self.key, **kwargs) + choices = self.get_setting_choices(self.key, **self.get_kwargs()) if not choices: return self.value @@ -558,12 +571,28 @@ class BaseInvenTreeSetting(models.Model): return self.value - def is_bool(self, **kwargs): + def is_model(self): + """ + Check if this setting references a model instance in the database + """ + + return self.model_name() is not None + + def model_name(self): + """ + Return the model name associated with this setting + """ + + setting = self.get_setting_definition(self.key, **self.get_kwargs()) + + return setting.get('model', None) + + def is_bool(self): """ Check if this setting is required to be a boolean value """ - validator = self.__class__.get_setting_validator(self.key, **kwargs) + validator = self.__class__.get_setting_validator(self.key, **self.get_kwargs()) return self.__class__.validator_is_bool(validator) @@ -576,17 +605,20 @@ class BaseInvenTreeSetting(models.Model): return InvenTree.helpers.str2bool(self.value) - def setting_type(self, **kwargs): + def setting_type(self): """ Return the field type identifier for this setting object """ - if self.is_bool(**kwargs): + if self.is_bool(): return 'boolean' - elif self.is_int(**kwargs): + elif self.is_int(): return 'integer' + elif self.is_model(): + return 'model' + else: return 'string' @@ -603,12 +635,12 @@ class BaseInvenTreeSetting(models.Model): return False - def is_int(self, **kwargs): + def is_int(self,): """ Check if the setting is required to be an integer value: """ - validator = self.__class__.get_setting_validator(self.key, **kwargs) + validator = self.__class__.get_setting_validator(self.key, **self.get_kwargs()) return self.__class__.validator_is_int(validator) @@ -651,88 +683,7 @@ class BaseInvenTreeSetting(models.Model): @property def protected(self): - return self.__class__.is_protected(self.key) - - -class GenericReferencedSettingClass: - """ - This mixin can be used to add reference keys to static properties - - Sample: - ```python - class SampleSetting(GenericReferencedSettingClass, 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()) + return self.__class__.is_protected(self.key, **self.get_kwargs()) def settings_group_options(): @@ -1558,6 +1509,16 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): return self.__class__.get_setting(self.key, user=self.user) + def get_kwargs(self): + """ + Explicit kwargs required to uniquely identify a particular setting object, + in addition to the 'key' parameter + """ + + return { + 'user': self.user, + } + class PriceBreak(models.Model): """ diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index 9d637c3e39..27fc15bca5 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -28,6 +28,8 @@ class SettingsSerializer(InvenTreeModelSerializer): choices = serializers.SerializerMethodField() + model_name = serializers.CharField(read_only=True) + def get_choices(self, obj): """ Returns the choices available for a given item @@ -75,6 +77,7 @@ class GlobalSettingsSerializer(SettingsSerializer): 'description', 'type', 'choices', + 'model_name', ] @@ -96,6 +99,7 @@ class UserSettingsSerializer(SettingsSerializer): 'user', 'type', 'choices', + 'model_name', ] @@ -124,6 +128,7 @@ class GenericReferencedSettingSerializer(SettingsSerializer): 'description', 'type', 'choices', + 'model_name', ] # set Meta class diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 0624693abc..1620bed230 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -102,7 +102,7 @@ class PluginConfig(models.Model): return ret -class PluginSetting(common.models.GenericReferencedSettingClass, common.models.BaseInvenTreeSetting): +class PluginSetting(common.models.BaseInvenTreeSetting): """ This model represents settings for individual plugins """ @@ -112,7 +112,13 @@ class PluginSetting(common.models.GenericReferencedSettingClass, common.models.B ('plugin', 'key'), ] - REFERENCE_NAME = 'plugin' + plugin = models.ForeignKey( + PluginConfig, + related_name='settings', + null=False, + verbose_name=_('Plugin'), + on_delete=models.CASCADE, + ) @classmethod def get_setting_definition(cls, key, **kwargs): @@ -131,7 +137,7 @@ class PluginSetting(common.models.GenericReferencedSettingClass, common.models.B if 'settings' not in kwargs: - plugin = kwargs.pop('plugin', None) + plugin = kwargs.pop('plugin') if plugin: @@ -142,16 +148,18 @@ class PluginSetting(common.models.GenericReferencedSettingClass, common.models.B return super().get_setting_definition(key, **kwargs) - plugin = models.ForeignKey( - PluginConfig, - related_name='settings', - null=False, - verbose_name=_('Plugin'), - on_delete=models.CASCADE, - ) + def get_kwargs(self): + """ + Explicit kwargs required to uniquely identify a particular setting object, + in addition to the 'key' parameter + """ + + return { + 'plugin': self.plugin, + } -class NotificationUserSetting(common.models.GenericReferencedSettingClass, common.models.BaseInvenTreeSetting): +class NotificationUserSetting(common.models.BaseInvenTreeSetting): """ This model represents notification settings for a user """ @@ -161,8 +169,6 @@ class NotificationUserSetting(common.models.GenericReferencedSettingClass, commo ('method', 'user', 'key'), ] - REFERENCE_NAME = 'method' - @classmethod def get_setting_definition(cls, key, **kwargs): from common.notifications import storage @@ -171,6 +177,17 @@ class NotificationUserSetting(common.models.GenericReferencedSettingClass, commo return super().get_setting_definition(key, **kwargs) + def get_kwargs(self): + """ + Explicit kwargs required to uniquely identify a particular setting object, + in addition to the 'key' parameter + """ + + return { + 'method': self.method, + 'user': self.user, + } + method = models.CharField( max_length=255, verbose_name=_('Method'), diff --git a/InvenTree/plugin/samples/integration/sample.py b/InvenTree/plugin/samples/integration/sample.py index 2df3bc116a..af99727ed6 100644 --- a/InvenTree/plugin/samples/integration/sample.py +++ b/InvenTree/plugin/samples/integration/sample.py @@ -65,6 +65,11 @@ class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixi ], 'default': 'A', }, + 'SELECT_COMPANY': { + 'name': 'Company', + 'description': 'Select a company object from the database', + 'model': 'company.Company', + }, } NAVIGATION = [ diff --git a/InvenTree/templates/InvenTree/settings/setting.html b/InvenTree/templates/InvenTree/settings/setting.html index 0bc099f8a2..55c323faec 100644 --- a/InvenTree/templates/InvenTree/settings/setting.html +++ b/InvenTree/templates/InvenTree/settings/setting.html @@ -22,6 +22,9 @@ {{ setting.description }}