From e51662e7bc2a946e6656e4954c902451a04eaf0e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 11 Jun 2014 15:26:17 -0400 Subject: [PATCH] [1.7.x] Fixed #22811 -- Allowed setting both the old and new TEST database settings. An ImproperlyConfigured exception will be raised they mismatch. Backport of 1c58cabad7 and a2cd0e12c9 from master --- django/db/utils.py | 26 ++++++--- docs/ref/settings.txt | 2 + docs/releases/1.7.txt | 5 +- tests/backends/tests.py | 125 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 149 insertions(+), 9 deletions(-) diff --git a/django/db/utils.py b/django/db/utils.py index 7f192564255..40471bfbbbb 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -187,6 +187,7 @@ class ConnectionHandler(object): 'USER_CREATE': 'CREATE_USER', 'PASSWD': 'PASSWORD', } + TEST_SETTING_RENAMES_REVERSE = {v: k for k, v in TEST_SETTING_RENAMES.items()} def prepare_test_settings(self, alias): """ @@ -197,18 +198,29 @@ class ConnectionHandler(object): except KeyError: raise ConnectionDoesNotExist("The connection %s doesn't exist" % alias) + test_dict_set = 'TEST' in conn test_settings = conn.setdefault('TEST', {}) + old_test_settings = {} for key, value in six.iteritems(conn): if key.startswith('TEST_'): new_key = key[5:] new_key = self.TEST_SETTING_RENAMES.get(new_key, new_key) - if new_key in test_settings: - raise ImproperlyConfigured("Connection %s has both %s and TEST[%s] specified." % - (alias, key, new_key)) - warnings.warn("In Django 1.9 the %s connection setting will be moved " - "to a %s entry in the TEST setting" % (key, new_key), - RemovedInDjango19Warning, stacklevel=2) - test_settings[new_key] = value + old_test_settings[new_key] = value + + if old_test_settings: + if test_dict_set: + if test_settings != old_test_settings: + raise ImproperlyConfigured( + "Connection '%s' has mismatched TEST and TEST_* " + "database settings." % alias) + else: + test_settings.update(old_test_settings) + for key, _ in six.iteritems(old_test_settings): + warnings.warn("In Django 1.9 the %s connection setting will be moved " + "to a %s entry in the TEST setting" % + (self.TEST_SETTING_RENAMES_REVERSE.get(key, key), key), + RemovedInDjango19Warning, stacklevel=2) + for key in list(conn.keys()): if key.startswith('TEST_'): del conn[key] diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 01d052d413c..ac231941417 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -614,6 +614,8 @@ TEST All :setting:`TEST ` sub-entries used to be independent entries in the database settings dictionary, with a ``TEST_`` prefix. + For backwards compatibility with older versions of Django, you can define + both versions of the settings as long as they match. Further, ``TEST_CREATE``, ``TEST_USER_CREATE`` and ``TEST_PASSWD`` were changed to ``CREATE_DB``, ``CREATE_USER`` and ``PASSWORD`` respectively. diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index e8f25a45e15..f970d408192 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -1546,9 +1546,12 @@ will be removed in Django 1.8. Reorganization of database test settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + All database settings with a ``TEST_`` prefix have been deprecated in favor of entries in a :setting:`TEST ` dictionary in the database -settings. The old settings will be supported until Django 1.9. +settings. The old settings will be supported until Django 1.9. For backwards +compatibility with older versions of Django, you can define both versions of +the settings as long as they match. FastCGI support ~~~~~~~~~~~~~~~ diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 90ae8a98c0e..930a9f7ff7b 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -10,6 +10,7 @@ import threading import unittest from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from django.core.management.color import no_style from django.db import (connection, connections, DEFAULT_DB_ALIAS, DatabaseError, IntegrityError, transaction) @@ -24,7 +25,7 @@ from django.db.models.sql.constants import CURSOR from django.db.utils import ConnectionHandler from django.test import (TestCase, TransactionTestCase, override_settings, skipUnlessDBFeature, skipIfDBFeature) -from django.test.utils import str_prefix +from django.test.utils import str_prefix, IgnoreAllDeprecationWarningsMixin from django.utils import six from django.utils.six.moves import xrange @@ -1015,3 +1016,125 @@ class BackendUtilTests(TestCase): '0.1') equal('0.1234567890', 12, 0, '0') + + +class DBTestSettingsRenamedTests(IgnoreAllDeprecationWarningsMixin, TestCase): + + mismatch_msg = ("Connection 'test-deprecation' has mismatched TEST " + "and TEST_* database settings.") + + @classmethod + def setUpClass(cls): + # Silence "UserWarning: Overriding setting DATABASES can lead to + # unexpected behavior." + cls.warning_classes.append(UserWarning) + + def setUp(self): + super(DBTestSettingsRenamedTests, self).setUp() + self.handler = ConnectionHandler() + self.db_settings = {'default': {}} + + def test_mismatched_database_test_settings_1(self): + # if the TEST setting is used, all TEST_* keys must appear in it. + self.db_settings.update({ + 'test-deprecation': { + 'TEST': {}, + 'TEST_NAME': 'foo', + } + }) + with override_settings(DATABASES=self.db_settings): + with self.assertRaisesMessage(ImproperlyConfigured, self.mismatch_msg): + self.handler.prepare_test_settings('test-deprecation') + + def test_mismatched_database_test_settings_2(self): + # if the TEST setting is used, all TEST_* keys must match. + self.db_settings.update({ + 'test-deprecation': { + 'TEST': {'NAME': 'foo'}, + 'TEST_NAME': 'bar', + }, + }) + with override_settings(DATABASES=self.db_settings): + with self.assertRaisesMessage(ImproperlyConfigured, self.mismatch_msg): + self.handler.prepare_test_settings('test-deprecation') + + def test_mismatched_database_test_settings_3(self): + # Verifies the mapping of an aliased key. + self.db_settings.update({ + 'test-deprecation': { + 'TEST': {'CREATE_DB': 'foo'}, + 'TEST_CREATE': 'bar', + }, + }) + with override_settings(DATABASES=self.db_settings): + with self.assertRaisesMessage(ImproperlyConfigured, self.mismatch_msg): + self.handler.prepare_test_settings('test-deprecation') + + def test_mismatched_database_test_settings_4(self): + # Verifies the mapping of an aliased key when the aliased key is missing. + self.db_settings.update({ + 'test-deprecation': { + 'TEST': {}, + 'TEST_CREATE': 'bar', + }, + }) + with override_settings(DATABASES=self.db_settings): + with self.assertRaisesMessage(ImproperlyConfigured, self.mismatch_msg): + self.handler.prepare_test_settings('test-deprecation') + + def test_mismatched_settings_old_none(self): + self.db_settings.update({ + 'test-deprecation': { + 'TEST': {'CREATE_DB': None}, + 'TEST_CREATE': '', + }, + }) + with override_settings(DATABASES=self.db_settings): + with self.assertRaisesMessage(ImproperlyConfigured, self.mismatch_msg): + self.handler.prepare_test_settings('test-deprecation') + + def test_mismatched_settings_new_none(self): + self.db_settings.update({ + 'test-deprecation': { + 'TEST': {}, + 'TEST_CREATE': None, + }, + }) + with override_settings(DATABASES=self.db_settings): + with self.assertRaisesMessage(ImproperlyConfigured, self.mismatch_msg): + self.handler.prepare_test_settings('test-deprecation') + + def test_matched_test_settings(self): + # should be able to define new settings and the old, if they match + self.db_settings.update({ + 'test-deprecation': { + 'TEST': {'NAME': 'foo'}, + 'TEST_NAME': 'foo', + }, + }) + with override_settings(DATABASES=self.db_settings): + self.handler.prepare_test_settings('test-deprecation') + + def test_new_settings_only(self): + # should be able to define new settings without the old + self.db_settings.update({ + 'test-deprecation': { + 'TEST': {'NAME': 'foo'}, + }, + }) + with override_settings(DATABASES=self.db_settings): + self.handler.prepare_test_settings('test-deprecation') + + def test_old_settings_only(self): + # should be able to define old settings without the new + self.db_settings.update({ + 'test-deprecation': { + 'TEST_NAME': 'foo', + }, + }) + with override_settings(DATABASES=self.db_settings): + self.handler.prepare_test_settings('test-deprecation') + + def test_empty_settings(self): + with override_settings(DATABASES=self.db_settings): + self.handler.prepare_test_settings('default')