2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 12:06:44 +00:00

abstracting Settings model

This commit is contained in:
Matthias 2021-07-20 01:34:35 +02:00
parent af68ea23c3
commit ec53099872

View File

@ -11,6 +11,7 @@ import decimal
import math import math
from django.db import models, transaction from django.db import models, transaction
from django.contrib.auth.models import User
from django.db.utils import IntegrityError, OperationalError from django.db.utils import IntegrityError, OperationalError
from django.conf import settings from django.conf import settings
@ -28,7 +29,394 @@ import InvenTree.helpers
import InvenTree.fields import InvenTree.fields
class InvenTreeSetting(models.Model): class BaseInvenTreeSetting(models.Model):
"""
An base InvenTreeSetting object is a key:value pair used for storing
single values (e.g. one-off settings values).
"""
GLOBAL_SETTINGS = {}
class Meta:
abstract = True
@classmethod
def get_setting_name(cls, key):
"""
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 ''
@classmethod
def get_setting_description(cls, key):
"""
Return the description for 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('description', '')
else:
return ''
@classmethod
def get_setting_units(cls, key):
"""
Return the units for 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('units', '')
else:
return ''
@classmethod
def get_setting_validator(cls, key):
"""
Return the validator for a particular setting.
If it does not exist, return None
"""
key = str(key).strip().upper()
if key in cls.GLOBAL_SETTINGS:
setting = cls.GLOBAL_SETTINGS[key]
return setting.get('validator', None)
else:
return None
@classmethod
def get_setting_default(cls, key):
"""
Return the default value for 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('default', '')
else:
return ''
@classmethod
def get_setting_choices(cls, key):
"""
Return the validator choices available for a particular setting.
"""
key = str(key).strip().upper()
if key in cls.GLOBAL_SETTINGS:
setting = cls.GLOBAL_SETTINGS[key]
choices = setting.get('choices', None)
else:
choices = None
"""
TODO:
if type(choices) is function:
# Evaluate the function (we expect it will return a list of tuples...)
return choices()
"""
return choices
@classmethod
def get_setting_object(cls, key):
"""
Return an InvenTreeSetting object matching the given key.
- Key is case-insensitive
- Returns None if no match is made
"""
key = str(key).strip().upper()
try:
setting = cls.objects.filter(key__iexact=key).first()
except (ValueError, cls.DoesNotExist):
setting = None
except (IntegrityError, OperationalError):
setting = None
# Setting does not exist! (Try to create it)
if not setting:
setting = cls(key=key, value=cls.get_setting_default(key))
try:
# Wrap this statement in "atomic", so it can be rolled back if it fails
with transaction.atomic():
setting.save()
except (IntegrityError, OperationalError):
# It might be the case that the database isn't created yet
pass
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):
"""
Get the value of a particular setting.
If it does not exist, return the backup value (default = None)
"""
# If no backup value is specified, atttempt to retrieve a "default" value
if backup_value is None:
backup_value = cls.get_setting_default(key)
setting = cls.get_setting_object(key)
if setting:
value = setting.value
# If the particular setting is defined as a boolean, cast the value to a boolean
if setting.is_bool():
value = InvenTree.helpers.str2bool(value)
if setting.is_int():
try:
value = int(value)
except (ValueError, TypeError):
value = backup_value
else:
value = backup_value
return value
@classmethod
def set_setting(cls, key, value, user, create=True):
"""
Set the value of a particular setting.
If it does not exist, option to create it.
Args:
key: settings key
value: New value
user: User object (must be staff member to update a core setting)
create: If True, create a new setting if the specified key does not exist.
"""
if user is not None and not user.is_staff:
return
try:
setting = cls.objects.get(key__iexact=key)
except cls.DoesNotExist:
if create:
setting = cls(key=key)
else:
return
# Enforce standard boolean representation
if setting.is_bool():
value = InvenTree.helpers.str2bool(value)
setting.value = str(value)
setting.save()
key = models.CharField(max_length=50, blank=False, unique=True, help_text=_('Settings key (must be unique - case insensitive'))
value = models.CharField(max_length=200, blank=True, unique=False, help_text=_('Settings value'))
@property
def name(self):
return self.__class__.get_setting_name(self.key)
@property
def default_value(self):
return self.__class__.get_setting_default(self.key)
@property
def description(self):
return self.__class__.get_setting_description(self.key)
@property
def units(self):
return self.__class__.get_setting_units(self.key)
def clean(self):
"""
If a validator (or multiple validators) are defined for a particular setting key,
run them against the 'value' field.
"""
super().clean()
validator = self.__class__.get_setting_validator(self.key)
if self.is_bool():
self.value = InvenTree.helpers.str2bool(self.value)
if self.is_int():
try:
self.value = int(self.value)
except (ValueError):
raise ValidationError(_('Must be an integer value'))
if validator is not None:
self.run_validator(validator)
def run_validator(self, validator):
"""
Run a validator against the 'value' field for this InvenTreeSetting object.
"""
if validator is None:
return
value = self.value
# Boolean validator
if self.is_bool():
# Value must "look like" a boolean value
if InvenTree.helpers.is_bool(value):
# Coerce into either "True" or "False"
value = InvenTree.helpers.str2bool(value)
else:
raise ValidationError({
'value': _('Value must be a boolean value')
})
# Integer validator
if self.is_int():
try:
# Coerce into an integer value
value = int(value)
except (ValueError, TypeError):
raise ValidationError({
'value': _('Value must be an integer value'),
})
# If a list of validators is supplied, iterate through each one
if type(validator) in [list, tuple]:
for v in validator:
self.run_validator(v)
if callable(validator):
# We can accept function validators with a single argument
validator(self.value)
def validate_unique(self, exclude=None):
""" Ensure that the key:value pair is unique.
In addition to the base validators, this ensures that the 'key'
is unique, using a case-insensitive comparison.
"""
super().validate_unique(exclude)
try:
setting = self.__class__.objects.exclude(id=self.id).filter(key__iexact=self.key)
if setting.exists():
raise ValidationError({'key': _('Key string must be unique')})
except self.DoesNotExist:
pass
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)
def is_bool(self):
"""
Check if this setting is required to be a boolean value
"""
validator = self.__class__.get_setting_validator(self.key)
if validator == bool:
return True
if type(validator) in [list, tuple]:
for v in validator:
if v == bool:
return True
def as_bool(self):
"""
Return the value of this setting converted to a boolean value.
Warning: Only use on values where is_bool evaluates to true!
"""
return InvenTree.helpers.str2bool(self.value)
def is_int(self):
"""
Check if the setting is required to be an integer value:
"""
validator = self.__class__.get_setting_validator(self.key)
if validator == int:
return True
if type(validator) in [list, tuple]:
for v in validator:
if v == int:
return True
return False
def as_int(self):
"""
Return the value of this setting converted to a boolean value.
If an error occurs, return the default value
"""
try:
value = int(self.value)
except (ValueError, TypeError):
value = self.default_value()
return value
class InvenTreeSetting(BaseInvenTreeSetting):
""" """
An InvenTreeSetting object is a key:value pair used for storing An InvenTreeSetting object is a key:value pair used for storing
single values (e.g. one-off settings values). single values (e.g. one-off settings values).
@ -357,380 +745,6 @@ class InvenTreeSetting(models.Model):
verbose_name = "InvenTree Setting" verbose_name = "InvenTree Setting"
verbose_name_plural = "InvenTree Settings" verbose_name_plural = "InvenTree Settings"
@classmethod
def get_setting_name(cls, key):
"""
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 ''
@classmethod
def get_setting_description(cls, key):
"""
Return the description for 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('description', '')
else:
return ''
@classmethod
def get_setting_units(cls, key):
"""
Return the units for 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('units', '')
else:
return ''
@classmethod
def get_setting_validator(cls, key):
"""
Return the validator for a particular setting.
If it does not exist, return None
"""
key = str(key).strip().upper()
if key in cls.GLOBAL_SETTINGS:
setting = cls.GLOBAL_SETTINGS[key]
return setting.get('validator', None)
else:
return None
@classmethod
def get_setting_default(cls, key):
"""
Return the default value for 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('default', '')
else:
return ''
@classmethod
def get_setting_choices(cls, key):
"""
Return the validator choices available for a particular setting.
"""
key = str(key).strip().upper()
if key in cls.GLOBAL_SETTINGS:
setting = cls.GLOBAL_SETTINGS[key]
choices = setting.get('choices', None)
else:
choices = None
"""
TODO:
if type(choices) is function:
# Evaluate the function (we expect it will return a list of tuples...)
return choices()
"""
return choices
@classmethod
def get_setting_object(cls, key):
"""
Return an InvenTreeSetting object matching the given key.
- Key is case-insensitive
- Returns None if no match is made
"""
key = str(key).strip().upper()
try:
setting = InvenTreeSetting.objects.filter(key__iexact=key).first()
except (ValueError, InvenTreeSetting.DoesNotExist):
setting = None
except (IntegrityError, OperationalError):
setting = None
# Setting does not exist! (Try to create it)
if not setting:
setting = InvenTreeSetting(key=key, value=InvenTreeSetting.get_setting_default(key))
try:
# Wrap this statement in "atomic", so it can be rolled back if it fails
with transaction.atomic():
setting.save()
except (IntegrityError, OperationalError):
# It might be the case that the database isn't created yet
pass
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 = InvenTreeSetting.get_setting_object(cls)
if setting:
return setting.pk
else:
return None
@classmethod
def get_setting(cls, key, backup_value=None):
"""
Get the value of a particular setting.
If it does not exist, return the backup value (default = None)
"""
# If no backup value is specified, atttempt to retrieve a "default" value
if backup_value is None:
backup_value = cls.get_setting_default(key)
setting = InvenTreeSetting.get_setting_object(key)
if setting:
value = setting.value
# If the particular setting is defined as a boolean, cast the value to a boolean
if setting.is_bool():
value = InvenTree.helpers.str2bool(value)
if setting.is_int():
try:
value = int(value)
except (ValueError, TypeError):
value = backup_value
else:
value = backup_value
return value
@classmethod
def set_setting(cls, key, value, user, create=True):
"""
Set the value of a particular setting.
If it does not exist, option to create it.
Args:
key: settings key
value: New value
user: User object (must be staff member to update a core setting)
create: If True, create a new setting if the specified key does not exist.
"""
if user is not None and not user.is_staff:
return
try:
setting = InvenTreeSetting.objects.get(key__iexact=key)
except InvenTreeSetting.DoesNotExist:
if create:
setting = InvenTreeSetting(key=key)
else:
return
# Enforce standard boolean representation
if setting.is_bool():
value = InvenTree.helpers.str2bool(value)
setting.value = str(value)
setting.save()
key = models.CharField(max_length=50, blank=False, unique=True, help_text=_('Settings key (must be unique - case insensitive'))
value = models.CharField(max_length=200, blank=True, unique=False, help_text=_('Settings value'))
@property
def name(self):
return InvenTreeSetting.get_setting_name(self.key)
@property
def default_value(self):
return InvenTreeSetting.get_setting_default(self.key)
@property
def description(self):
return InvenTreeSetting.get_setting_description(self.key)
@property
def units(self):
return InvenTreeSetting.get_setting_units(self.key)
def clean(self):
"""
If a validator (or multiple validators) are defined for a particular setting key,
run them against the 'value' field.
"""
super().clean()
validator = InvenTreeSetting.get_setting_validator(self.key)
if self.is_bool():
self.value = InvenTree.helpers.str2bool(self.value)
if self.is_int():
try:
self.value = int(self.value)
except (ValueError):
raise ValidationError(_('Must be an integer value'))
if validator is not None:
self.run_validator(validator)
def run_validator(self, validator):
"""
Run a validator against the 'value' field for this InvenTreeSetting object.
"""
if validator is None:
return
value = self.value
# Boolean validator
if self.is_bool():
# Value must "look like" a boolean value
if InvenTree.helpers.is_bool(value):
# Coerce into either "True" or "False"
value = InvenTree.helpers.str2bool(value)
else:
raise ValidationError({
'value': _('Value must be a boolean value')
})
# Integer validator
if self.is_int():
try:
# Coerce into an integer value
value = int(value)
except (ValueError, TypeError):
raise ValidationError({
'value': _('Value must be an integer value'),
})
# If a list of validators is supplied, iterate through each one
if type(validator) in [list, tuple]:
for v in validator:
self.run_validator(v)
if callable(validator):
# We can accept function validators with a single argument
validator(self.value)
def validate_unique(self, exclude=None):
""" Ensure that the key:value pair is unique.
In addition to the base validators, this ensures that the 'key'
is unique, using a case-insensitive comparison.
"""
super().validate_unique(exclude)
try:
setting = InvenTreeSetting.objects.exclude(id=self.id).filter(key__iexact=self.key)
if setting.exists():
raise ValidationError({'key': _('Key string must be unique')})
except InvenTreeSetting.DoesNotExist:
pass
def choices(self):
"""
Return the available choices for this setting (or None if no choices are defined)
"""
return InvenTreeSetting.get_setting_choices(self.key)
def is_bool(self):
"""
Check if this setting is required to be a boolean value
"""
validator = InvenTreeSetting.get_setting_validator(self.key)
if validator == bool:
return True
if type(validator) in [list, tuple]:
for v in validator:
if v == bool:
return True
def as_bool(self):
"""
Return the value of this setting converted to a boolean value.
Warning: Only use on values where is_bool evaluates to true!
"""
return InvenTree.helpers.str2bool(self.value)
def is_int(self):
"""
Check if the setting is required to be an integer value:
"""
validator = InvenTreeSetting.get_setting_validator(self.key)
if validator == int:
return True
if type(validator) in [list, tuple]:
for v in validator:
if v == int:
return True
return False
def as_int(self):
"""
Return the value of this setting converted to a boolean value.
If an error occurs, return the default value
"""
try:
value = int(self.value)
except (ValueError, TypeError):
value = self.default_value()
return value
class PriceBreak(models.Model): class PriceBreak(models.Model):
""" """