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)