django-orchestra/orchestra/contrib/settings/__init__.py

114 lines
4.2 KiB
Python

import re
from collections import OrderedDict
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.functional import Promise
from orchestra.core import validators
from orchestra.utils.python import import_class, format_exception
default_app_config = 'orchestra.contrib.settings.apps.SettingsConfig'
class Setting(object):
"""
Keeps track of the defined settings and provides extra batteries like value validation.
"""
conf_settings = settings
settings = OrderedDict()
def __str__(self):
return self.name
def __repr__(self):
value = str(self.value)
value = ("'%s'" if isinstance(value, str) else '%s') % value
return '<%s: %s>' % (self.name, value)
def __new__(cls, name, default, help_text="", choices=None, editable=True, serializable=True,
multiple=False, validators=[], types=[], call_init=False):
if call_init:
return super(Setting, cls).__new__(cls)
cls.settings[name] = cls(name, default, help_text=help_text, choices=choices, editable=editable,
serializable=serializable, multiple=multiple, validators=validators, types=types, call_init=True)
return cls.get_value(name, default)
def __init__(self, *args, **kwargs):
self.name, self.default = args
for name, value in kwargs.items():
setattr(self, name, value)
self.value = self.get_value(self.name, self.default)
self.settings[name] = self
@classmethod
def validate_choices(cls, value):
if not isinstance(value, (list, tuple)):
raise ValidationError("%s is not a valid choices." % str(value))
for choice in value:
if not isinstance(choice, (list, tuple)) or len(choice) != 2:
raise ValidationError("%s is not a valid choice." % str(choice))
value, verbose = choice
if not isinstance(verbose, (str, Promise)):
raise ValidationError("%s is not a valid verbose name." % value)
@classmethod
def validate_import_class(cls, value):
try:
import_class(value)
except Exception as exc:
raise ValidationError(format_exception(exc))
@classmethod
def validate_model_label(cls, value):
from django.apps import apps
try:
apps.get_model(*value.split('.'))
except Exception as exc:
raise ValidationError(format_exception(exc))
@classmethod
def string_format_validator(cls, names, modulo=True):
def validate_string_format(value, names=names, modulo=modulo):
errors = []
regex = r'%\(([^\)]+)\)' if modulo else r'{([^}]+)}'
for n in re.findall(regex, value):
if n not in names:
errors.append(
ValidationError('%s is not a valid format name.' % n)
)
if errors:
raise ValidationError(errors)
return validate_string_format
def validate_value(self, value):
if value:
validators.all_valid(value, self.validators)
valid_types = list(self.types)
if self.choices:
choices = self.choices
if callable(choices):
choices = choices()
choices = [n for n,v in choices]
values = value
if not isinstance(values, (list, tuple)):
values = [value]
for cvalue in values:
if cvalue not in choices:
raise ValidationError("'%s' not in '%s'" % (value, ', '.join(choices)))
if isinstance(self.default, (list, tuple)):
valid_types.extend([list, tuple])
valid_types.append(type(self.default))
if not isinstance(value, tuple(valid_types)):
raise ValidationError("%s is not a valid type (%s)." %
(type(value).__name__, ', '.join(t.__name__ for t in valid_types))
)
def validate(self):
self.validate_value(self.value)
@classmethod
def get_value(cls, name, default):
return getattr(cls.conf_settings, name, default)