From a1f8d32ac71e39adc1459448586b7bdba85b150d Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Sun, 4 Oct 2015 19:57:00 +0000 Subject: [PATCH] Improved settings.parser API --- TODO.md | 5 ---- orchestra/admin/utils.py | 8 +++++-- .../project_template/project_name/settings.py | 6 ++++- orchestra/contrib/settings/README.md | 18 ++++++++++++++ orchestra/contrib/settings/__init__.py | 24 +++++++++++++++---- orchestra/contrib/settings/admin.py | 9 ++++++- orchestra/contrib/settings/forms.py | 6 ++--- orchestra/contrib/settings/parser.py | 15 +++++++++++- orchestra/management/commands/setupcelery.py | 12 +++------- 9 files changed, 76 insertions(+), 27 deletions(-) create mode 100644 orchestra/contrib/settings/README.md diff --git a/TODO.md b/TODO.md index f576defb..83418b15 100644 --- a/TODO.md +++ b/TODO.md @@ -237,7 +237,6 @@ https://code.djangoproject.com/ticket/24576 # TASK_BEAT_BACKEND = ('cron', 'celerybeat', 'uwsgi') # Ship orchestra production-ready (no DEBUG etc) -# Settings.parser.changes: if setting.value == default. remove # reload generic admin view ?redirect=http... # inspecting django db connection for asserting db readines? or performing a query * wake up django mailer on send_mail @@ -379,8 +378,6 @@ Case # Mailer: mark as sent # Mailer: download attachments -# Deprecate orchestra start/stop/restart services management commands? - # 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 -# validate saas setting allow_custom_url check that websites have a related declared directive # 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 -# tzinfo=datetime.timezone.utc diff --git a/orchestra/admin/utils.py b/orchestra/admin/utils.py index 2dc3e48a..7b23fa03 100644 --- a/orchestra/admin/utils.py +++ b/orchestra/admin/utils.py @@ -149,8 +149,12 @@ def admin_date(*args, **kwargs): natural = humanize.naturaldatetime(date) else: natural = humanize.naturaldate(date) - local = timezone.localtime(date).strftime("%Y-%m-%d %H:%M:%S %Z") - return '{1}'.format(local, escape(natural)) + if hasattr(date, 'hour'): + date = timezone.localtime(date) + date = date.strftime("%Y-%m-%d %H:%M:%S %Z") + else: + date = date.strftime("%Y-%m-%d") + return '{1}'.format(date, escape(natural)) def get_object_from_url(modeladmin, request): diff --git a/orchestra/conf/project_template/project_name/settings.py b/orchestra/conf/project_template/project_name/settings.py index 65885bc6..0c5c9e6f 100644 --- a/orchestra/conf/project_template/project_name/settings.py +++ b/orchestra/conf/project_template/project_name/settings.py @@ -127,7 +127,11 @@ DATABASES = { LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' + +try: + TIME_ZONE = open('/etc/timezone', 'r').read().strip() +except IOError: + TIME_ZONE = 'UTC' USE_I18N = True diff --git a/orchestra/contrib/settings/README.md b/orchestra/contrib/settings/README.md new file mode 100644 index 00000000..4de7305b --- /dev/null +++ b/orchestra/contrib/settings/README.md @@ -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 "", line 1, in + 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()}) +... +``` + diff --git a/orchestra/contrib/settings/__init__.py b/orchestra/contrib/settings/__init__.py index 75dc91cd..0393f872 100644 --- a/orchestra/contrib/settings/__init__.py +++ b/orchestra/contrib/settings/__init__.py @@ -82,18 +82,32 @@ class Setting(object): raise ValidationError(errors) return validate_string_format - def validate(self): - if self.value: - validators.all_valid(self.value, self.validators) + 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(self.value, tuple(valid_types)): + if not isinstance(value, tuple(valid_types)): 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 def get_value(cls, name, default): return getattr(cls.conf_settings, name, default) diff --git a/orchestra/contrib/settings/admin.py b/orchestra/contrib/settings/admin.py index a145d58b..8761ae04 100644 --- a/orchestra/contrib/settings/admin.py +++ b/orchestra/contrib/settings/admin.py @@ -53,10 +53,15 @@ class SettingView(generic.edit.FormView): setting = settings[data['name']] if not isinstance(data['value'], parser.NotSupported) and setting.editable: 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']: changes[setting.name] = parser.Remove() else: - changes[setting.name] = parser.serialize(data['value']) + changes[setting.name] = data['value'] if changes: # Display 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 context = self.get_context_data(form=form) context['diff'] = diff + if not diff: + messages.warning(self.request, _("Changes detected but no diff %s.") % changes) return self.render_to_response(context) n = len(changes) # Save changes diff --git a/orchestra/contrib/settings/forms.py b/orchestra/contrib/settings/forms.py index 5cd65516..2b339584 100644 --- a/orchestra/contrib/settings/forms.py +++ b/orchestra/contrib/settings/forms.py @@ -113,9 +113,9 @@ class SettingForm(ReadOnlyFormMixin, forms.Form): except Exception as exc: raise ValidationError(format_exception(exc)) self.setting.validate_value(value) - if not isinstance(value, self.setting_type): - if self.setting_type in (tuple, list) and isinstance(value, (tuple, list)): - value = self.setting_type(value) +# if not isinstance(value, self.setting_type): +# if self.setting_type in (tuple, list) and isinstance(value, (tuple, list)): +# value = self.setting_type(value) return value diff --git a/orchestra/contrib/settings/parser.py b/orchestra/contrib/settings/parser.py index f38a60e2..4a06fe72 100644 --- a/orchestra/contrib/settings/parser.py +++ b/orchestra/contrib/settings/parser.py @@ -9,6 +9,8 @@ from django.utils.functional import Promise from orchestra.utils.paths import get_project_dir +from . import Setting + class Remove(object): """ used to signal a setting remove """ @@ -92,7 +94,6 @@ def serialize(obj, init=True): def _format_setting(name, value): if isinstance(value, Remove): return "" - value = eval(value, get_eval_context()) try: value = json.dumps(value, indent=4) except TypeError: @@ -100,8 +101,20 @@ def _format_setting(name, 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()): """ returns settings_file content with applied changes """ + validate_changes(changes) updates = _find_updates(changes, settings_file) content = [] _changes = copy.copy(changes) diff --git a/orchestra/management/commands/setupcelery.py b/orchestra/management/commands/setupcelery.py index 63878f15..c343d6e8 100644 --- a/orchestra/management/commands/setupcelery.py +++ b/orchestra/management/commands/setupcelery.py @@ -110,32 +110,26 @@ class Command(BaseCommand): if Setting.settings['TASKS_BACKEND'].value != 'celery': changes['TASKS_BACKEND'] = 'celery' 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', 'celeryevcam', 'celeryd', 'celerybeat', ('uwsgi', 'nginx'), ) - ) 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', 'celerybeat', 'uwsgi', ) - ) 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'), 'celerybeat', 'celeryd', 'celeryevcam', 'postgresql' ) - ) if changes: settings_parser.apply(changes)