From 12245b0f71e1c3387e213c23cfcc51024d8d64fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Mon, 8 Jun 2015 16:13:40 +0200 Subject: [PATCH 1/3] allow to override field config_type and set custom additional fields --- constance/admin.py | 28 +++++++++++++++++++++++----- constance/base.py | 4 +++- constance/settings.py | 2 ++ docs/index.rst | 27 +++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/constance/admin.py b/constance/admin.py index 6e68ee4..cbae4fc 100644 --- a/constance/admin.py +++ b/constance/admin.py @@ -15,6 +15,7 @@ from django.template.context import RequestContext from django.utils import six from django.utils.formats import localize from django.utils.translation import ugettext_lazy as _ +import django try: from django.utils.encoding import smart_bytes @@ -51,6 +52,17 @@ FIELDS = { float: (fields.FloatField, {'widget': NUMERIC_WIDGET}), } +def parse_additional_fields(fields): + for key in fields: + field = fields[key] + field[0] = eval(field[0]) + if 'widget' in field[1]: + field[1]['widget'] = eval(field[1]['widget']) + return fields + + +FIELDS.update(parse_additional_fields(settings.ADDITIONAL_FIELDS)) + if not six.PY3: FIELDS.update({ long: INTEGER_LIKE, @@ -65,8 +77,13 @@ class ConstanceForm(forms.Form): super(ConstanceForm, self).__init__(*args, initial=initial, **kwargs) version_hash = hashlib.md5() - for name, (default, help_text) in settings.CONFIG.items(): - config_type = type(default) + for name, options in settings.CONFIG.items(): + default, help_text = options[0], options[1] + if len(options) == 3: + config_type = options[2] + else: + config_type = type(default) + if config_type not in FIELDS: raise ImproperlyConfigured(_("Constance doesn't support " "config values of the type " @@ -111,8 +128,8 @@ class ConstanceAdmin(admin.ModelAdmin): # First load a mapping between config name and default value if not self.has_change_permission(request, None): raise PermissionDenied - default_initial = ((name, default) - for name, (default, help_text) in settings.CONFIG.items()) + default_initial = ((name, options[0]) + for name, options in settings.CONFIG.items()) # Then update the mapping with actually values from the backend initial = dict(default_initial, **dict(config._backend.mget(settings.CONFIG.keys()))) @@ -136,7 +153,8 @@ class ConstanceAdmin(admin.ModelAdmin): 'form': form, 'media': self.media + form.media, } - for name, (default, help_text) in settings.CONFIG.items(): + for name, options in settings.CONFIG.items(): + default, help_text = options[0], options[1] # First try to load the value from the actual backend value = initial.get(name) # Then if the returned value is None, get the default diff --git a/constance/base.py b/constance/base.py index 99afe62..72812ad 100644 --- a/constance/base.py +++ b/constance/base.py @@ -11,7 +11,9 @@ class Config(object): def __getattr__(self, key): try: - default, help_text = settings.CONFIG[key] + if not len(settings.CONFIG[key]) in (2, 3): + raise AttributeError(key) + default = settings.CONFIG[key][0] except KeyError: raise AttributeError(key) result = self._backend.get(key) diff --git a/constance/settings.py b/constance/settings.py index 6f1350d..fd5edd4 100644 --- a/constance/settings.py +++ b/constance/settings.py @@ -5,6 +5,8 @@ BACKEND = getattr(settings, 'CONSTANCE_BACKEND', CONFIG = getattr(settings, 'CONSTANCE_CONFIG', {}) +ADDITIONAL_FIELDS = getattr(settings, 'CONSTANCE_ADDITIONAL_FIELDS', {}) + DATABASE_CACHE_BACKEND = getattr(settings, 'CONSTANCE_DATABASE_CACHE_BACKEND', None) diff --git a/docs/index.rst b/docs/index.rst index 2980166..feea83a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -53,6 +53,33 @@ admin will show. See the :ref:`Backends ` section how to setup the backend and finish the configuration. +Custom fields +------------- + +You can set the field type by the third value in the `CONSTANCE_CONFIG` +tuple. The value can be string or one of the supported types: + +.. code-block:: python + 'THE_ANSWER': (42, 'Answer to the Ultimate Question of Life, ' + 'The Universe, and Everything', str), + +If you can add your custom field types, you can use the +`CONSTANCE_ADDITIONAL_FIELDS` variable. Note that you must +use later evaluated strings instead of direct classes: + +.. code-block:: python + CONSTANCE_ADDITIONAL_FIELDS = { + 'yes_no_null_select': ['django.forms.fields.ChoiceField', + { + 'widget': 'django.forms.Select', + 'choices': (("-----", None), ("yes", "Yes"), ("no", "No")) + }], + } + + CONSTANCE_CONFIG = { + 'MY_SELECT_KEY': ('yes', 'select yes or no', 'yes_no_null_select'), + } + Usage ----- From 0e447ee3e20fa8bbccc0c257124ff22e37544d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Sun, 14 Jun 2015 17:25:56 +0200 Subject: [PATCH 2/3] add tests for custom additional fields --- tests/settings.py | 9 +++++++++ tests/storage.py | 3 +++ 2 files changed, 12 insertions(+) diff --git a/tests/settings.py b/tests/settings.py index 04a428a..d7678e2 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -51,6 +51,14 @@ long_value = 123456 if not six.PY3: long_value = long(long_value) +CONSTANCE_ADDITIONAL_FIELDS = { + 'yes_no_null_select': ['django.forms.fields.ChoiceField', + { + 'widget': 'django.forms.Select', + 'choices': (("-----", None), ("yes", "Yes"), ("no", "No")) + }], + } + CONSTANCE_CONFIG = { 'INT_VALUE': (1, 'some int'), 'LONG_VALUE': (long_value, 'some looong int'), @@ -63,6 +71,7 @@ CONSTANCE_CONFIG = { 'FLOAT_VALUE': (3.1415926536, 'PI'), 'DATE_VALUE': (date(2010, 12, 24), 'Merry Chrismas'), 'TIME_VALUE': (time(23, 59, 59), 'And happy New Year'), + 'CHOICE_VALUE': ('yes', 'select yes or no', 'yes_no_null_select'), } DEBUG = True diff --git a/tests/storage.py b/tests/storage.py index a78b02a..338daae 100644 --- a/tests/storage.py +++ b/tests/storage.py @@ -27,6 +27,7 @@ class StorageTestsMixin(object): self.assertEqual(self.config.FLOAT_VALUE, 3.1415926536) self.assertEqual(self.config.DATE_VALUE, date(2010, 12, 24)) self.assertEqual(self.config.TIME_VALUE, time(23, 59, 59)) + self.assertEqual(self.config.CHOICE_VALUE, 'yes') # set values self.config.INT_VALUE = 100 @@ -39,6 +40,7 @@ class StorageTestsMixin(object): self.config.FLOAT_VALUE = 2.718281845905 self.config.DATE_VALUE = date(2001, 12, 20) self.config.TIME_VALUE = time(1, 59, 0) + self.config.CHOICE_VALUE = 'no' # read again self.assertEqual(self.config.INT_VALUE, 100) @@ -51,6 +53,7 @@ class StorageTestsMixin(object): self.assertEqual(self.config.FLOAT_VALUE, 2.718281845905) self.assertEqual(self.config.DATE_VALUE, date(2001, 12, 20)) self.assertEqual(self.config.TIME_VALUE, time(1, 59, 0)) + self.assertEqual(self.config.CHOICE_VALUE, 'no') def test_nonexistent(self): try: From 201ba0376280157a2e211ad172eca647552b10d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrno=20Ader?= Date: Mon, 2 Nov 2015 16:52:55 +0200 Subject: [PATCH 3/3] Safer custom fields --- constance/admin.py | 21 +++++++++++++------ .../admin/constance/change_list.html | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/constance/admin.py b/constance/admin.py index 32bc15a..37c3cd3 100644 --- a/constance/admin.py +++ b/constance/admin.py @@ -15,6 +15,7 @@ from django.template.response import TemplateResponse from django.utils import six from django.utils.encoding import smart_bytes from django.utils.formats import localize +from django.utils.module_loading import import_string from django.utils.translation import ugettext_lazy as _ import django @@ -43,13 +44,21 @@ FIELDS = { float: (fields.FloatField, {'widget': NUMERIC_WIDGET}), } + def parse_additional_fields(fields): - for key in fields: - field = fields[key] - field[0] = eval(field[0]) - if 'widget' in field[1]: - field[1]['widget'] = eval(field[1]['widget']) - return fields + for key in fields: + field = fields[key] + + field[0] = import_string(field[0]) + + if 'widget' in field[1]: + klass = import_string(field[1]['widget']) + field[1]['widget'] = klass(**(field[1].get('widget_kwargs', {}) or {})) + + if 'widget_kwargs' in field[1]: + del field[1]['widget_kwargs'] + + return fields FIELDS.update(parse_additional_fields(settings.ADDITIONAL_FIELDS)) diff --git a/constance/templates/admin/constance/change_list.html b/constance/templates/admin/constance/change_list.html index 746e263..71c7da1 100644 --- a/constance/templates/admin/constance/change_list.html +++ b/constance/templates/admin/constance/change_list.html @@ -36,7 +36,7 @@ {% block bodyclass %}change-list{% endblock %} {% block content %} -
+
{% csrf_token %} {% if form.errors %}