Simplify the various settings objects, to improve retrieval of 'parameters' from the base class

- Remove the GenericReferencedSettingsClass mixin
- Each subclass defines a very simple get_kwargs() method
- Now, at object level *and* class level we can perform lookup of settings and actually get proper data back
- Adds "model" option to setting (precursor of things to come)
This commit is contained in:
Oliver 2022-05-12 16:45:27 +10:00
parent 56f36d4b4b
commit e112d555d4
5 changed files with 105 additions and 114 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -22,6 +22,9 @@
{{ setting.description }}
</td>
<td>
{% if setting.model_name %}
<b>Model name: {{ setting.model_name }}</b>
{% endif %}
{% if setting.is_bool %}
<div class='form-check form-switch'>
<input class='form-check-input boolean-setting' fieldname='{{ setting.key.upper }}' pk='{{ setting.pk }}' setting='{{ setting.key.upper }}' id='setting-value-{{ setting.key.upper }}' type='checkbox' {% if setting.as_bool %}checked=''{% endif %} {% if plugin %}plugin='{{ plugin.slug }}'{% endif %}{% if user_setting %}user='{{request.user.id}}'{% endif %}{% if notification_setting %}notification='{{request.user.id}}'{% endif %}>