diff --git a/constance/__init__.py b/constance/__init__.py index ac30b720729b4a64970613383e5c4e054278231a..7ba1b9ddc23362417fa3c650d07f80d4f95aa56f 100644 --- a/constance/__init__.py +++ b/constance/__init__.py @@ -1,6 +1,7 @@ from django.utils.functional import LazyObject +from . import checks -__version__ = '2.2.0' +__version__ = '2.4.0' default_app_config = 'constance.apps.ConstanceConfig' diff --git a/constance/admin.py b/constance/admin.py index a2285cc04be7f71c276a3973abe8e65177b50a7f..51f68c6e57b94d00dacccd48e32be3f703777419 100644 --- a/constance/admin.py +++ b/constance/admin.py @@ -13,6 +13,7 @@ from django.contrib import admin, messages from django.contrib.admin import widgets from django.contrib.admin.options import csrf_protect_m from django.core.exceptions import PermissionDenied, ImproperlyConfigured +from django.core.files.storage import default_storage from django.forms import fields from django.http import HttpResponseRedirect from django.template.response import TemplateResponse @@ -23,6 +24,7 @@ from django.utils.module_loading import import_string from django.utils.translation import ugettext_lazy as _ from . import LazyConfig, settings +from .checks import get_inconsistent_fieldnames config = LazyConfig() @@ -137,11 +139,8 @@ class ConstanceForm(forms.Form): def save(self): for file_field in self.files: file = self.cleaned_data[file_field] - file_path = os.path.join(django_settings.MEDIA_ROOT, file.name) - with open(file_path, 'wb+') as destination: - for chunk in file.chunks(): - destination.write(chunk) - self.cleaned_data[file_field] = file.name + default_storage.save(file.name, file) + self.cleaned_data[file_field] = file.name for name in settings.CONFIG: if getattr(config, name) != self.cleaned_data[name]: @@ -165,11 +164,7 @@ class ConstanceForm(forms.Form): if not settings.CONFIG_FIELDSETS: return cleaned_data - field_name_list = [] - for fieldset_title, fields_list in settings.CONFIG_FIELDSETS.items(): - for field_name in fields_list: - field_name_list.append(field_name) - if field_name_list and set(set(settings.CONFIG.keys()) - set(field_name_list)): + if get_inconsistent_fieldnames(): raise forms.ValidationError(_('CONSTANCE_CONFIG_FIELDSETS is missing ' 'field(s) that exists in CONSTANCE_CONFIG.')) @@ -259,8 +254,12 @@ class ConstanceAdmin(admin.ModelAdmin): if settings.CONFIG_FIELDSETS: context['fieldsets'] = [] for fieldset_title, fields_list in settings.CONFIG_FIELDSETS.items(): - fields_exist = all(field in settings.CONFIG for field in fields_list) - assert fields_exist, "CONSTANCE_CONFIG_FIELDSETS contains field(s) that does not exist" + absent_fields = [field for field in fields_list + if field not in settings.CONFIG] + assert not any(absent_fields), ( + "CONSTANCE_CONFIG_FIELDSETS contains field(s) that does " + "not exist: %s" % ', '.join(absent_fields)) + config_values = [] for name in fields_list: diff --git a/constance/backends/database/migrations/0002_auto_20190129_2304.py b/constance/backends/database/migrations/0002_auto_20190129_2304.py new file mode 100644 index 0000000000000000000000000000000000000000..736798b102917e308c016e6af54328aeaf3ee891 --- /dev/null +++ b/constance/backends/database/migrations/0002_auto_20190129_2304.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.5 on 2019-01-30 04:04 + +from django.db import migrations +import picklefield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('database', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='constance', + name='value', + field=picklefield.fields.PickledObjectField(blank=True, editable=False, null=True), + ), + ] diff --git a/constance/backends/database/models.py b/constance/backends/database/models.py index e9964e837085f888464d8eeabb1ac389abe9e1ce..088fd798c67129e568ad8275e30a388c8c7a9c06 100644 --- a/constance/backends/database/models.py +++ b/constance/backends/database/models.py @@ -13,7 +13,7 @@ except ImportError: class Constance(models.Model): key = models.CharField(max_length=255, unique=True) - value = PickledObjectField() + value = PickledObjectField(null=True, blank=True) class Meta: verbose_name = _('constance') diff --git a/constance/base.py b/constance/base.py index e44ff75a82b5e221c747e190b0edb48b5c8bd404..72812ad500dd75313782710acc76e9619c916c58 100644 --- a/constance/base.py +++ b/constance/base.py @@ -7,7 +7,7 @@ class Config(object): """ def __init__(self): super(Config, self).__setattr__('_backend', - utils.import_module_attr(settings.BACKEND)()) + utils.import_module_attr(settings.BACKEND)()) def __getattr__(self, key): try: diff --git a/constance/changes.rst b/constance/changes.rst new file mode 100644 index 0000000000000000000000000000000000000000..87b256e9f33fdaa79f0944be2ef148ae50270f2f --- /dev/null +++ b/constance/changes.rst @@ -0,0 +1,305 @@ +Changelog +--------- + +v2.4.0 (2019/03/16) +~~~~~~~~~~~~~~~~~~~ + +* Show not existing fields in field_list + +* Drop Django<1.11 and 2.0, fix tests vs Django 2.2b + +* Fixed "Reset to default" button with constants whose name contains a space + +* Use default_storage to save file + +* Allow null & blank for PickleField + +* Removed Python 3.4 since is not longer supported + +v2.3.1 (2018/09/20) +~~~~~~~~~~~~~~~~~~~ + +* Fixes javascript typo. + +v2.3.0 (2018/09/13) +~~~~~~~~~~~~~~~~~~~ + +* Added zh_Hans translation. + +* Fixed TestAdmin.test_linebreaks() due to linebreaksbr() behavior change + on Django 2.1 + +* Improved chinese translation + +* Fix bug of can't change permission chang_config's name + +* Improve consistency of reset value handling for `date` + +* Drop support for Python 3.3 + +* Added official Django 2.0 support. + +* Added support for Django 2.1 + +v2.2.0 (2018/03/23) +~~~~~~~~~~~~~~~~~~~ + +* Fix ConstanceForm validation. + +* `CONSTANCE_DBS` setting for directing constance permissions/content_type + settings to certain DBs only. + +* Added config labels. + +* Updated italian translations. + +* Fix `CONSTANCE_CONFIG_FIELDSETS` mismatch issue. + +v2.1.0 (2018/02/07) +~~~~~~~~~~~~~~~~~~~ + +* Move inline JavaScript to constance.js. + +* Remove translation from the app name. + +* Added file uploads. + +* Update information on template context processors. + +* Allow running set while database is not created. + +* Moved inline css/javascripts out to their own files. + +* Add French translations. + +* Add testing for all supported Python and Django versions. + +* Preserve sorting from fieldset config. + +* Added datetime.timedelta support. + +* Added Estonian translations. + +* Account for server timezone for Date object. + +v2.0.0 (2017/02/17) +~~~~~~~~~~~~~~~~~~~ + +* **BACKWARD INCOMPATIBLE** Added the old value to the config_updated signal. + +* Added a `get_changelist_form` hook in the ModelAdmin. + +* Fix create_perm in apps.py to use database alias given by the post_migrate + signal. + +* Added tests for django 1.11. + +* Fix Reset to default to work with boolean/checkboxes. + +* Fix handling of MultiValueField's (eg SplitDateTimeField) on the command + line. + +v1.3.4 (2016/12/23) +~~~~~~~~~~~~~~~~~~~ + +* Fix config ordering issue + +* Added localize to check modified flag + +* Allow to rename Constance in Admin + +* Preserve line breaks in default value + +* Added functionality from django-constance-cli + +* Added "Reset to default" feature + +v1.3.3 (2016/09/17) +~~~~~~~~~~~~~~~~~~~ + +* Revert broken release + +v1.3.2 (2016/09/17) +~~~~~~~~~~~~~~~~~~~ + +* Fixes a bug where the signal was sent for fields without changes + +v1.3.1 (2016/09/15) +~~~~~~~~~~~~~~~~~~~ + +* Improved the signal path to avoid import errors + +* Improved the admin layout when using fieldsets + +v1.3 (2016/09/14) +~~~~~~~~~~~~~~~~~ + +* **BACKWARD INCOMPATIBLE** Dropped support for Django < 1.8). + +* Added ordering constance fields using OrderedDict + +* Added a signal when updating constance fields + +v1.2.1 (2016/09/1) +~~~~~~~~~~~~~~~~~~ + +* Added some fixes to small bugs + +* Fix cache when key changes + +* Upgrade django_redis connection string + +* Autofill cache key if key is missing + +* Added support for fieldsets + +v1.2 (2016/05/14) +~~~~~~~~~~~~~~~~~ + +* Custom Fields were added as a new feature + +* Added documentation on how to use Custom settings form + +* Introduced ``CONSTANCE_IGNORE_ADMIN_VERSION_CHECK`` + +* Improved documentation for ``CONSTANCE_ADDITIONAL_FIELDS`` + +v1.1.2 (2016/02/08) +~~~~~~~~~~~~~~~~~~~ + +* Moved to Jazzband organization (https://github.com/jazzband/django-constance) + +* Added Custom Fields + +* Added Django 1.9 support to tests + +* Fixes icons for Django 1.9 admin + +v1.1.1 (2015/10/01) +~~~~~~~~~~~~~~~~~~~ + +* Fixed a regression in the 1.1 release that prevented the rendering of the + admin view with constance values when using the context processor at the + same time. + +v1.1 (2015/09/24) +~~~~~~~~~~~~~~~~~ + +* **BACKWARD INCOMPATIBLE** Dropped support for Python 2.6 + The supported versions are 2.7, 3.3 (on Django < 1.9) and 3.4. + +* **BACKWARD INCOMPATIBLE** Dropped support for Django 1.4, 1.5 and 1.6 + The supported versions are 1.7, 1.8 and the upcoming 1.9 release + +* Added compatibility to Django 1.8 and 1.9. + +* Added Spanish and Chinese (``zh_CN``) translations. + +* Added :class:`override_config` decorator/context manager for easy + :doc:`testing <testing>`. + +* Added the ability to use linebreaks in config value help texts. + +* Various testing fixes. + +v1.0.1 (2015/01/07) +~~~~~~~~~~~~~~~~~~~ + +* Fixed issue with import time side effect on Django >= 1.7. + +v1.0 (2014/12/04) +~~~~~~~~~~~~~~~~~ + +* Added docs and set up Read The Docs project: + + https://django-constance.readthedocs.io/ + +* Set up Transifex project for easier translations: + + https://www.transifex.com/projects/p/django-constance + +* Added autofill feature for the database backend cache which is enabled + by default. + +* Added Django>=1.7 migrations and moved South migrations to own folder. + Please upgrade to South>=1.0 to use the new South migration location. + + For Django 1.7 users that means running the following to fake the migration:: + + django-admin.py migrate database --fake + +* Added consistency check when saving config values in the admin to prevent + accidentally overwriting other users' changes. + +* Fixed issue with South migration that would break on MySQL. + +* Fix compatibility with Django 1.6 and 1.7 and current master (to be 1.8). + +* Fixed clearing database cache en masse by applying prefix correctly. + +* Fixed a few translation related issues. + +* Switched to tox as test script. + +* Fixed a few minor cosmetic frontend issues + (e.g. padding in admin table header). + +* Deprecated a few old settings: + + ============================== =================================== + deprecated replacement + ============================== =================================== + ``CONSTANCE_CONNECTION_CLASS`` ``CONSTANCE_REDIS_CONNECTION_CLASS`` + ``CONSTANCE_CONNECTION`` ``CONSTANCE_REDIS_CONNECTION`` + ``CONSTANCE_PREFIX`` ``CONSTANCE_REDIS_PREFIX`` + ============================== =================================== + +* The undocumented feature to use an environment variable called + ``CONSTANCE_SETTINGS_MODULE`` to define which module to load + settings from has been removed. + +v0.6 (2013/04/12) +~~~~~~~~~~~~~~~~~ + +* Added Python 3 support. Supported versions: 2.6, 2.7, 3.2 and 3.3. + For Python 3.x the use of Django > 1.5.x is required. + +* Fixed a serious issue with ordering in the admin when using the database + backend. Thanks, Bouke Haarsma. + +* Switch to django-discover-runner as test runner to be able to run on + Python 3. + +* Fixed an issue with refering to static files in the admin interface + when using Django < 1.4. + +v0.5 (2013/03/02) +~~~~~~~~~~~~~~~~~ + +* Fixed compatibility with Django 1.5's swappable model backends. + +* Converted the ``key`` field of the database backend to use a ``CharField`` + with uniqueness instead of just ``TextField``. + + For South users we provide a migration for that change. First you + have to "fake" the initial migration we've also added to this release:: + + django-admin.py migrate database --fake 0001 + + After that you can run the rest of the migrations:: + + django-admin.py migrate database + +* Fixed compatibility with Django>1.4's way of refering to static files in + the admin. + +* Added ability to add custom authorization checks via the new + ``CONSTANCE_SUPERUSER_ONLY`` setting. + +* Added Polish translation. Thanks, Janusz Harkot. + +* Allow ``CONSTANCE_REDIS_CONNECTION`` being an URL instead of a dict. + +* Added ``CONSTANCE_DATABASE_PREFIX`` setting allow setting a key prefix. + +* Switched test runner to use django-nose. diff --git a/constance/checks.py b/constance/checks.py new file mode 100644 index 0000000000000000000000000000000000000000..de660297b46ce8ca9fe49b86631d47cca8b16082 --- /dev/null +++ b/constance/checks.py @@ -0,0 +1,42 @@ +from django.core import checks +from django.utils.translation import ugettext_lazy as _ + +from . import settings + + +@checks.register("constance") +def check_fieldsets(*args, **kwargs): + """ + A Django system check to make sure that, if defined, CONFIG_FIELDSETS accounts for + every entry in settings.CONFIG. + """ + if hasattr(settings, "CONFIG_FIELDSETS") and settings.CONFIG_FIELDSETS: + inconsistent_fieldnames = get_inconsistent_fieldnames() + if inconsistent_fieldnames: + return [ + checks.Warning( + _( + "CONSTANCE_CONFIG_FIELDSETS is missing " + "field(s) that exists in CONSTANCE_CONFIG." + ), + hint=", ".join(sorted(inconsistent_fieldnames)), + obj="settings.CONSTANCE_CONFIG", + id="constance.E001", + ) + ] + return [] + + +def get_inconsistent_fieldnames(): + """ + Returns a set of keys from settings.CONFIG that are not accounted for in + settings.CONFIG_FIELDSETS. + If there are no fieldnames in settings.CONFIG_FIELDSETS, returns an empty set. + """ + field_name_list = [] + for fieldset_title, fields_list in settings.CONFIG_FIELDSETS.items(): + for field_name in fields_list: + field_name_list.append(field_name) + if not field_name_list: + return {} + return set(set(settings.CONFIG.keys()) - set(field_name_list)) diff --git a/constance/static/admin/css/constance.css b/constance/static/admin/css/constance.css index 4d74660f4431e35de74f1c1b2b78198ef0496286..a00b0beea03a7caa18520c0e1ce7314563d53df2 100644 --- a/constance/static/admin/css/constance.css +++ b/constance/static/admin/css/constance.css @@ -13,3 +13,6 @@ .help { font-weight: normal !important; } +#results{ + overflow-x: auto; +} \ No newline at end of file diff --git a/constance/static/admin/js/constance.js b/constance/static/admin/js/constance.js index 690dfccaffe7953b0c58f1bae51b13270ce49889..bea4ba68c6950578f64e84a4582e86b09fd07647 100644 --- a/constance/static/admin/js/constance.js +++ b/constance/static/admin/js/constance.js @@ -6,14 +6,15 @@ $('#content-main').on('click', '.reset-link', function(e) { e.preventDefault(); - var field = $('#' + this.dataset.fieldId); + var field_selector = this.dataset.fieldId.replace(/ /g, "\\ ") + var field = $('#' + field_selector); var fieldType = this.dataset.fieldType; if (fieldType === 'checkbox') { field.prop('checked', this.dataset.default === 'true'); } else if (fieldType === 'date') { var defaultDate = new Date(this.dataset.default * 1000); - $('#' + this.dataset.fieldId).val(defaultDate.strftime(get_format('DATE_INPUT_FORMATS')[0]));} + $('#' + this.dataset.fieldId).val(defaultDate.strftime(get_format('DATE_INPUT_FORMATS')[0])); } else if (fieldType === 'datetime') { var defaultDate = new Date(this.dataset.default * 1000); $('#' + this.dataset.fieldId + '_0').val(defaultDate.strftime(get_format('DATE_INPUT_FORMATS')[0])); diff --git a/constance/templates/admin/constance/change_list.html b/constance/templates/admin/constance/change_list.html index bdee4d2ece5f864bb605f8867dcf1d8e5061dfef..6f5b937ad915cbc337c161ca0e8a560973a04c62 100644 --- a/constance/templates/admin/constance/change_list.html +++ b/constance/templates/admin/constance/change_list.html @@ -6,7 +6,7 @@ <link rel="stylesheet" type="text/css" href="{% static 'admin/css/changelists.css' %}" /> <link rel="stylesheet" type="text/css" href="{% static 'admin/css/forms.css' %}" /> {{ media.css }} - <link rel="stylesheet" type="text/css" href="{% static 'admin/css/../../../../venv/Lib/site-packages/constance/static/admin/css/constance.css' %}" /> + <link rel="stylesheet" type="text/css" href="{% static 'admin/css/constance.css' %}" /> {% endblock %} {% block extrahead %} @@ -14,7 +14,7 @@ <script type="text/javascript" src="{{ jsi18nurl|default:'../../jsi18n/' }}"></script> {{ block.super }} {{ media.js }} -<script type="text/javascript" src="{% static 'admin/js/../../../../venv/Lib/site-packages/constance/static/admin/js/constance.js' %}"></script> +<script type="text/javascript" src="{% static 'admin/js/constance.js' %}"></script> {% endblock %} {% block bodyclass %}change-list{% endblock %} @@ -48,12 +48,12 @@ <fieldset class="module"> <h2>{{ fieldset.title }}</h2> {% with config_values=fieldset.config_values %} - {% include "admin/constance/includes/../../../../venv/Lib/site-packages/constance/templates/admin/constance/includes/results_list.html" %} + {% include "admin/constance/includes/results_list.html" %} {% endwith %} </fieldset> {% endfor %} {% else %} - {% include "admin/constance/includes/../../../../venv/Lib/site-packages/constance/templates/admin/constance/includes/results_list.html" %} + {% include "admin/constance/includes/results_list.html" %} {% endif %} <p class="paginator"> diff --git a/constance/templates/admin/constance/includes/results_list.html b/constance/templates/admin/constance/includes/results_list.html index 295388be2a40268acb5a09dc96a25baf6e5fd2dc..b13c4ee8801b9a7834bc8b99db8d6c201a3f627d 100644 --- a/constance/templates/admin/constance/includes/results_list.html +++ b/constance/templates/admin/constance/includes/results_list.html @@ -1,49 +1,51 @@ {% load admin_static admin_list static i18n %} -<table> - <thead> - <tr> - <th><div class="text">{% trans "Name" %}</div></th> - <th><div class="text">{% trans "Default" %}</div></th> - <th><div class="text">{% trans "Value" %}</div></th> - <th><div class="text">{% trans "Is modified" %}</div></th> - </tr> - </thead> - {% for item in config_values %} - <tr class="{% cycle 'row1' 'row2' %}"> - <th> - {{ item.name }} <div class="help">{{ item.help_text|linebreaksbr }}</div> - </th> - <td> - {{ item.default|linebreaks }} - </td> - <td> - {{ item.form_field.errors }} - {% if item.is_file %}{% trans "Current file" %}: <a href="{% get_media_prefix as MEDIA_URL %}{{ MEDIA_URL }}{{ item.value }}" target="_blank">{{ item.value }}</a>{% endif %} - {{ item.form_field }} - <br> - <a href="#" class="reset-link" - data-field-id="{{ item.form_field.auto_id }}" - data-field-type="{% spaceless %} - {% if item.is_checkbox %}checkbox - {% elif item.is_date %}date - {% elif item.is_datetime %}datetime - {% endif %} - {% endspaceless %}" - data-default="{% spaceless %} - {% if item.is_checkbox %}{% if item.raw_default %} true {% else %} false {% endif %} - {% elif item.is_date %}{{ item.raw_default|date:"U" }} - {% elif item.is_datetime %}{{ item.raw_default|date:"U" }} - {% else %}{{ item.default }} +<div id="results"> + <table> + <thead> + <tr> + <th><div class="text">{% trans "Name" %}</div></th> + <th><div class="text">{% trans "Default" %}</div></th> + <th><div class="text">{% trans "Value" %}</div></th> + <th><div class="text">{% trans "Is modified" %}</div></th> + </tr> + </thead> + {% for item in config_values %} + <tr class="{% cycle 'row1' 'row2' %}"> + <th> + {{ item.name }} <div class="help">{{ item.help_text|linebreaksbr }}</div> + </th> + <td> + {{ item.default|linebreaks }} + </td> + <td> + {{ item.form_field.errors }} + {% if item.is_file %}{% trans "Current file" %}: <a href="{% get_media_prefix as MEDIA_URL %}{{ MEDIA_URL }}{{ item.value }}" target="_blank">{{ item.value }}</a>{% endif %} + {{ item.form_field }} + <br> + <a href="#" class="reset-link" + data-field-id="{{ item.form_field.auto_id }}" + data-field-type="{% spaceless %} + {% if item.is_checkbox %}checkbox + {% elif item.is_date %}date + {% elif item.is_datetime %}datetime + {% endif %} + {% endspaceless %}" + data-default="{% spaceless %} + {% if item.is_checkbox %}{% if item.raw_default %} true {% else %} false {% endif %} + {% elif item.is_date %}{{ item.raw_default|date:"U" }} + {% elif item.is_datetime %}{{ item.raw_default|date:"U" }} + {% else %}{{ item.default }} + {% endif %} + {% endspaceless %}">{% trans "Reset to default" %}</a> + </td> + <td> + {% if item.modified %} + <img src="{% static 'admin/img/icon-yes.'|add:icon_type %}" alt="{{ item.modified }}" /> + {% else %} + <img src="{% static 'admin/img/icon-no.'|add:icon_type %}" alt="{{ item.modified }}" /> {% endif %} - {% endspaceless %}">{% trans "Reset to default" %}</a> - </td> - <td> - {% if item.modified %} - <img src="{% static 'admin/img/icon-yes.'|add:icon_type %}" alt="{{ item.modified }}" /> - {% else %} - <img src="{% static 'admin/img/icon-no.'|add:icon_type %}" alt="{{ item.modified }}" /> - {% endif %} - </td> - </tr> - {% endfor %} -</table> + </td> + </tr> + {% endfor %} + </table> +</div>