Improved settings.parser API
This commit is contained in:
parent
c8d694299b
commit
a1f8d32ac7
5
TODO.md
5
TODO.md
|
@ -237,7 +237,6 @@ https://code.djangoproject.com/ticket/24576
|
||||||
# TASK_BEAT_BACKEND = ('cron', 'celerybeat', 'uwsgi')
|
# TASK_BEAT_BACKEND = ('cron', 'celerybeat', 'uwsgi')
|
||||||
# Ship orchestra production-ready (no DEBUG etc)
|
# Ship orchestra production-ready (no DEBUG etc)
|
||||||
|
|
||||||
# Settings.parser.changes: if setting.value == default. remove
|
|
||||||
# reload generic admin view ?redirect=http...
|
# reload generic admin view ?redirect=http...
|
||||||
# inspecting django db connection for asserting db readines? or performing a query
|
# inspecting django db connection for asserting db readines? or performing a query
|
||||||
* wake up django mailer on send_mail
|
* wake up django mailer on send_mail
|
||||||
|
@ -379,8 +378,6 @@ Case
|
||||||
# Mailer: mark as sent
|
# Mailer: mark as sent
|
||||||
# Mailer: download attachments
|
# Mailer: download attachments
|
||||||
|
|
||||||
# Deprecate orchestra start/stop/restart services management commands?
|
|
||||||
|
|
||||||
# Enable/disable ignore period orders list filter
|
# Enable/disable ignore period orders list filter
|
||||||
|
|
||||||
|
|
||||||
|
@ -414,8 +411,6 @@ http://makandracards.com/makandra/24933-chrome-34+-firefox-38+-ie11+-ignore-auto
|
||||||
|
|
||||||
mkhomedir_helper or create ssh homes with bash.rc and such
|
mkhomedir_helper or create ssh homes with bash.rc and such
|
||||||
|
|
||||||
# validate saas setting allow_custom_url check that websites have a related declared directive
|
|
||||||
# warnings if some plugins are disabled, like make routes red
|
# warnings if some plugins are disabled, like make routes red
|
||||||
|
|
||||||
# replace show emails by https://docs.python.org/3/library/email.contentmanager.html#module-email.contentmanager
|
# replace show emails by https://docs.python.org/3/library/email.contentmanager.html#module-email.contentmanager
|
||||||
# tzinfo=datetime.timezone.utc
|
|
||||||
|
|
|
@ -149,8 +149,12 @@ def admin_date(*args, **kwargs):
|
||||||
natural = humanize.naturaldatetime(date)
|
natural = humanize.naturaldatetime(date)
|
||||||
else:
|
else:
|
||||||
natural = humanize.naturaldate(date)
|
natural = humanize.naturaldate(date)
|
||||||
local = timezone.localtime(date).strftime("%Y-%m-%d %H:%M:%S %Z")
|
if hasattr(date, 'hour'):
|
||||||
return '<span title="{0}">{1}</span>'.format(local, escape(natural))
|
date = timezone.localtime(date)
|
||||||
|
date = date.strftime("%Y-%m-%d %H:%M:%S %Z")
|
||||||
|
else:
|
||||||
|
date = date.strftime("%Y-%m-%d")
|
||||||
|
return '<span title="{0}">{1}</span>'.format(date, escape(natural))
|
||||||
|
|
||||||
|
|
||||||
def get_object_from_url(modeladmin, request):
|
def get_object_from_url(modeladmin, request):
|
||||||
|
|
|
@ -127,7 +127,11 @@ DATABASES = {
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
|
||||||
|
try:
|
||||||
|
TIME_ZONE = open('/etc/timezone', 'r').read().strip()
|
||||||
|
except IOError:
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
|
18
orchestra/contrib/settings/README.md
Normal file
18
orchestra/contrib/settings/README.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
```python
|
||||||
|
>>> from orchestra.contrib.settings import Setting, parser
|
||||||
|
>>> Setting.settings['TASKS_BACKEND'].value
|
||||||
|
'thread'
|
||||||
|
>>> Setting.settings['TASKS_BACKEND'].default
|
||||||
|
'thread'
|
||||||
|
>>> Setting.settings['TASKS_BACKEND'].validate_value('rata')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<console>", line 1, in <module>
|
||||||
|
File "/home/orchestra/django-orchestra/orchestra/contrib/settings/__init__.py", line 99, in validate_value
|
||||||
|
raise ValidationError("'%s' not in '%s'" % (value, ', '.join(choices)))
|
||||||
|
django.core.exceptions.ValidationError: ["'rata' not in 'thread, process, celery'"]
|
||||||
|
>>> parser.apply({'TASKS_BACKEND': 'process'})
|
||||||
|
...
|
||||||
|
>>> parser.apply({'TASKS_BACKEND': parser.Remove()})
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
|
@ -82,18 +82,32 @@ class Setting(object):
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
return validate_string_format
|
return validate_string_format
|
||||||
|
|
||||||
def validate(self):
|
def validate_value(self, value):
|
||||||
if self.value:
|
if value:
|
||||||
validators.all_valid(self.value, self.validators)
|
validators.all_valid(value, self.validators)
|
||||||
valid_types = list(self.types)
|
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)):
|
if isinstance(self.default, (list, tuple)):
|
||||||
valid_types.extend([list, tuple])
|
valid_types.extend([list, tuple])
|
||||||
valid_types.append(type(self.default))
|
valid_types.append(type(self.default))
|
||||||
if not isinstance(self.value, tuple(valid_types)):
|
if not isinstance(value, tuple(valid_types)):
|
||||||
raise ValidationError("%s is not a valid type (%s)." %
|
raise ValidationError("%s is not a valid type (%s)." %
|
||||||
(type(self.value).__name__, ', '.join(t.__name__ for t in valid_types))
|
(type(value).__name__, ', '.join(t.__name__ for t in valid_types))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
self.validate_value(self.value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_value(cls, name, default):
|
def get_value(cls, name, default):
|
||||||
return getattr(cls.conf_settings, name, default)
|
return getattr(cls.conf_settings, name, default)
|
||||||
|
|
|
@ -53,10 +53,15 @@ class SettingView(generic.edit.FormView):
|
||||||
setting = settings[data['name']]
|
setting = settings[data['name']]
|
||||||
if not isinstance(data['value'], parser.NotSupported) and setting.editable:
|
if not isinstance(data['value'], parser.NotSupported) and setting.editable:
|
||||||
if setting.value != data['value']:
|
if setting.value != data['value']:
|
||||||
|
# Ignore differences between lists and tuples
|
||||||
|
if (type(setting.value) != type(data['value']) and
|
||||||
|
isinstance(data['value'], list) and
|
||||||
|
tuple(data['value']) == setting.value):
|
||||||
|
continue
|
||||||
if setting.default == data['value']:
|
if setting.default == data['value']:
|
||||||
changes[setting.name] = parser.Remove()
|
changes[setting.name] = parser.Remove()
|
||||||
else:
|
else:
|
||||||
changes[setting.name] = parser.serialize(data['value'])
|
changes[setting.name] = data['value']
|
||||||
if changes:
|
if changes:
|
||||||
# Display confirmation
|
# Display confirmation
|
||||||
if not self.request.POST.get('confirmation'):
|
if not self.request.POST.get('confirmation'):
|
||||||
|
@ -66,6 +71,8 @@ class SettingView(generic.edit.FormView):
|
||||||
diff = sys.run(cmd, valid_codes=(1, 0)).stdout
|
diff = sys.run(cmd, valid_codes=(1, 0)).stdout
|
||||||
context = self.get_context_data(form=form)
|
context = self.get_context_data(form=form)
|
||||||
context['diff'] = diff
|
context['diff'] = diff
|
||||||
|
if not diff:
|
||||||
|
messages.warning(self.request, _("Changes detected but no diff %s.") % changes)
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
n = len(changes)
|
n = len(changes)
|
||||||
# Save changes
|
# Save changes
|
||||||
|
|
|
@ -113,9 +113,9 @@ class SettingForm(ReadOnlyFormMixin, forms.Form):
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise ValidationError(format_exception(exc))
|
raise ValidationError(format_exception(exc))
|
||||||
self.setting.validate_value(value)
|
self.setting.validate_value(value)
|
||||||
if not isinstance(value, self.setting_type):
|
# if not isinstance(value, self.setting_type):
|
||||||
if self.setting_type in (tuple, list) and isinstance(value, (tuple, list)):
|
# if self.setting_type in (tuple, list) and isinstance(value, (tuple, list)):
|
||||||
value = self.setting_type(value)
|
# value = self.setting_type(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ from django.utils.functional import Promise
|
||||||
|
|
||||||
from orchestra.utils.paths import get_project_dir
|
from orchestra.utils.paths import get_project_dir
|
||||||
|
|
||||||
|
from . import Setting
|
||||||
|
|
||||||
|
|
||||||
class Remove(object):
|
class Remove(object):
|
||||||
""" used to signal a setting remove """
|
""" used to signal a setting remove """
|
||||||
|
@ -92,7 +94,6 @@ def serialize(obj, init=True):
|
||||||
def _format_setting(name, value):
|
def _format_setting(name, value):
|
||||||
if isinstance(value, Remove):
|
if isinstance(value, Remove):
|
||||||
return ""
|
return ""
|
||||||
value = eval(value, get_eval_context())
|
|
||||||
try:
|
try:
|
||||||
value = json.dumps(value, indent=4)
|
value = json.dumps(value, indent=4)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -100,8 +101,20 @@ def _format_setting(name, value):
|
||||||
return "{name} = {value}".format(name=name, value=value)
|
return "{name} = {value}".format(name=name, value=value)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_changes(changes):
|
||||||
|
for name, value in changes.items():
|
||||||
|
if not isinstance(value, Remove):
|
||||||
|
try:
|
||||||
|
setting = Setting.settings[name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
setting.validate_value(value)
|
||||||
|
|
||||||
|
|
||||||
def apply(changes, settings_file=get_settings_file()):
|
def apply(changes, settings_file=get_settings_file()):
|
||||||
""" returns settings_file content with applied changes """
|
""" returns settings_file content with applied changes """
|
||||||
|
validate_changes(changes)
|
||||||
updates = _find_updates(changes, settings_file)
|
updates = _find_updates(changes, settings_file)
|
||||||
content = []
|
content = []
|
||||||
_changes = copy.copy(changes)
|
_changes = copy.copy(changes)
|
||||||
|
|
|
@ -110,32 +110,26 @@ class Command(BaseCommand):
|
||||||
if Setting.settings['TASKS_BACKEND'].value != 'celery':
|
if Setting.settings['TASKS_BACKEND'].value != 'celery':
|
||||||
changes['TASKS_BACKEND'] = 'celery'
|
changes['TASKS_BACKEND'] = 'celery'
|
||||||
if Setting.settings['ORCHESTRA_START_SERVICES'].value == Setting.settings['ORCHESTRA_START_SERVICES'].default:
|
if Setting.settings['ORCHESTRA_START_SERVICES'].value == Setting.settings['ORCHESTRA_START_SERVICES'].default:
|
||||||
changes['ORCHESTRA_START_SERVICES'] = settings_parser.serialize(
|
changes['ORCHESTRA_START_SERVICES'] = (
|
||||||
(
|
|
||||||
'postgresql',
|
'postgresql',
|
||||||
'celeryevcam',
|
'celeryevcam',
|
||||||
'celeryd',
|
'celeryd',
|
||||||
'celerybeat',
|
'celerybeat',
|
||||||
('uwsgi', 'nginx'),
|
('uwsgi', 'nginx'),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
if Setting.settings['ORCHESTRA_RESTART_SERVICES'].value == Setting.settings['ORCHESTRA_RESTART_SERVICES'].default:
|
if Setting.settings['ORCHESTRA_RESTART_SERVICES'].value == Setting.settings['ORCHESTRA_RESTART_SERVICES'].default:
|
||||||
changes['ORCHESTRA_RESTART_SERVICES'] = settings_parser.serialize(
|
changes['ORCHESTRA_RESTART_SERVICES'] = (
|
||||||
(
|
|
||||||
'celeryd',
|
'celeryd',
|
||||||
'celerybeat',
|
'celerybeat',
|
||||||
'uwsgi',
|
'uwsgi',
|
||||||
)
|
)
|
||||||
)
|
|
||||||
if Setting.settings['ORCHESTRA_STOP_SERVICES'].value == Setting.settings['ORCHESTRA_STOP_SERVICES'].default:
|
if Setting.settings['ORCHESTRA_STOP_SERVICES'].value == Setting.settings['ORCHESTRA_STOP_SERVICES'].default:
|
||||||
changes['ORCHESTRA_STOP_SERVICES'] = settings_parser.serialize(
|
changes['ORCHESTRA_STOP_SERVICES'] = (
|
||||||
(
|
|
||||||
('uwsgi', 'nginx'),
|
('uwsgi', 'nginx'),
|
||||||
'celerybeat',
|
'celerybeat',
|
||||||
'celeryd',
|
'celeryd',
|
||||||
'celeryevcam',
|
'celeryevcam',
|
||||||
'postgresql'
|
'postgresql'
|
||||||
)
|
)
|
||||||
)
|
|
||||||
if changes:
|
if changes:
|
||||||
settings_parser.apply(changes)
|
settings_parser.apply(changes)
|
||||||
|
|
Loading…
Reference in a new issue