Commit 66757fee authored by Claude Paroz's avatar Claude Paroz
Browse files

Fixed #23384 -- Allowed overriding part of a dictionary-type setting

This change is needed for upcoming changes where settings might be
grouped in a parent dictionary.
Thanks Tim Graham for the review.
parent 05a8cef4
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ import time # Needed for Windows

from django.conf import global_settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.datastructures import dict_merge
from django.utils.functional import LazyObject, empty
from django.utils import six

@@ -77,6 +78,10 @@ class BaseSettings(object):
        elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, six.string_types):
            raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set "
                "to a tuple, not a string.")
        elif (hasattr(self, name) and name.isupper() and
                isinstance(getattr(self, name), dict) and isinstance(value, dict)):
            # This allows defining only a partial dict to update a global setting
            value = dict_merge(getattr(self, name), value)
        object.__setattr__(self, name, value)


@@ -144,7 +149,7 @@ class UserSettingsHolder(BaseSettings):
        from the module specified in default_settings (if possible).
        """
        self.__dict__['_deleted'] = set()
        self.default_settings = default_settings
        self.__dict__['default_settings'] = default_settings

    def __getattr__(self, name):
        if name in self._deleted:
+1 −1
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ class MigrationLoader(object):

    @classmethod
    def migrations_module(cls, app_label):
        if app_label in settings.MIGRATION_MODULES:
        if settings.MIGRATION_MODULES.get(app_label):
            return settings.MIGRATION_MODULES[app_label]
        else:
            app_package_name = apps.get_app_config(app_label).name
+20 −0
Original line number Diff line number Diff line
@@ -244,6 +244,26 @@ class SortedDict(dict):
        self.keyOrder = []


def dict_merge(a, b):
    """
    Utility to recursively merge two dicts, taking care not to overwrite subkeys
    (which would happen with dict.update), but keeping existing key including
    those from subdictionaries (optionally opted-out if a `_clear_defaults` key
    is present).
    Thanks Ross McFarland (https://www.xormedia.com/recursively-merge-dictionaries-in-python/)
    """
    if b.get('_clear_defaults'):
        return copy.deepcopy(b)

    result = copy.deepcopy(a)
    for key, value in six.iteritems(b):
        if key in a and isinstance(result[key], dict):
            result[key] = dict_merge(result[key], value)
        else:
            result[key] = value
    return result


class OrderedSet(object):
    """
    A set which keeps the ordering of the inserted items.
+3 −0
Original line number Diff line number Diff line
@@ -530,6 +530,9 @@ Miscellaneous
  widget to allow more customization. The undocumented ``url_markup_template``
  attribute was removed in favor of ``template_with_initial``.

* When a dictionary setting is overridden in user settings, both dictionaries
  are merged by default. See :ref:`dictionary-settings`.

.. _deprecated-features-1.8:

Features deprecated in 1.8
+26 −0
Original line number Diff line number Diff line
@@ -110,6 +110,32 @@ between the current settings file and Django's default settings.

For more, see the :djadmin:`diffsettings` documentation.

.. _dictionary-settings:

Overriding dictionary settings
------------------------------

.. versionchanged:: 1.8

When defining a dictionary-type setting which has a non-empty value (see
:setting:`CACHES` for example), you do not have to redefine all its keys. You
can just define the keys differing from the default, and Django will simply
merge your setting value with the default value. For example, if you define
:setting:`CACHES` so::

    CACHES = {
        'special': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }

then ``CACHES['default']`` which is set by default in Django's global settings
will still be defined, as well as the new ``'special'`` cache backend.

If you want your setting to completely override the default value, you can add
a ``_clear_defaults`` key with a ``True`` value to the dictionary.

Using settings in Python code
=============================

Loading