Skip to content
Browse files

Fixed #16969 -- Don't connect to named database when possible

Thanks Andreas Pelme for the report and initial patch, and
Aymeric Augustin, Shai Berger and Tim Graham for the reviews.
  • Loading branch information...
1 parent 1830f50 commit e953c78eeb81ee69dccd356145563fd6f9e4c7b6 @claudep claudep committed
37 django/db/backends/
@@ -6,6 +6,7 @@
from django.conf import settings
from django.db.utils import load_backend
from django.utils.encoding import force_bytes
+from django.utils.functional import cached_property
from django.utils.six.moves import input
from .utils import truncate_name
@@ -29,6 +30,24 @@ class BaseDatabaseCreation(object):
def __init__(self, connection):
self.connection = connection
+ @cached_property
+ def _nodb_connection(self):
+ """
+ Alternative connection to be used when there is no need to access
+ the main database, specifically for test db creation/deletion.
+ This also prevents the production database from being exposed to
+ potential child threads while (or after) the test database is destroyed.
+ Refs #10868, #17786, #16969.
+ """
+ settings_dict = self.connection.settings_dict.copy()
+ settings_dict['NAME'] = None
+ backend = load_backend(settings_dict['ENGINE'])
+ nodb_connection = backend.DatabaseWrapper(
+ settings_dict,
+ alias='__no_db__',
+ allow_thread_sharing=False)
+ return nodb_connection
def _digest(cls, *args):
@@ -386,7 +405,7 @@ def _create_test_db(self, verbosity, autoclobber):
qn = self.connection.ops.quote_name
# Create the test database and connect to it.
- cursor = self.connection.cursor()
+ cursor = self._nodb_connection.cursor()
"CREATE DATABASE %s %s" % (qn(test_database_name), suffix))
@@ -431,18 +450,7 @@ def destroy_test_db(self, old_database_name, verbosity=1):
print("Destroying test database for alias '%s'%s..." % (
self.connection.alias, test_db_repr))
- # Temporarily use a new connection and a copy of the settings dict.
- # This prevents the production database from being exposed to potential
- # child threads while (or after) the test database is destroyed.
- # Refs #10868 and #17786.
- settings_dict = self.connection.settings_dict.copy()
- settings_dict['NAME'] = old_database_name
- backend = load_backend(settings_dict['ENGINE'])
- new_connection = backend.DatabaseWrapper(
- settings_dict,
- alias='__destroy_test_db__',
- allow_thread_sharing=False)
- new_connection.creation._destroy_test_db(test_database_name, verbosity)
+ self._destroy_test_db(test_database_name, verbosity)
def _destroy_test_db(self, test_database_name, verbosity):
@@ -452,12 +460,11 @@ def _destroy_test_db(self, test_database_name, verbosity):
# ourselves. Connect to the previous database (not the test database)
# to do so, because it's not allowed to delete a database while being
# connected to it.
- cursor = self.connection.cursor()
+ cursor = self._nodb_connection.cursor()
# Wait to avoid "database is being accessed by other users" errors.
cursor.execute("DROP DATABASE %s"
% self.connection.ops.quote_name(test_database_name))
- self.connection.close()
def set_autocommit(self):
5 django/db/backends/postgresql_psycopg2/
@@ -101,13 +101,14 @@ def __init__(self, *args, **kwargs):
def get_connection_params(self):
settings_dict = self.settings_dict
- if not settings_dict['NAME']:
+ # None may be used to connect to the default 'postgres' db
+ if settings_dict['NAME'] == '':
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured(
"settings.DATABASES is improperly configured. "
"Please supply the NAME value.")
conn_params = {
- 'database': settings_dict['NAME'],
+ 'database': settings_dict['NAME'] or 'postgres',
if 'autocommit' in conn_params:
18 docs/internals/contributing/writing-code/unit-tests.txt
@@ -76,17 +76,21 @@ If you're using a backend that isn't SQLite, you will need to provide other
details for each database:
* The :setting:`USER` option needs to specify an existing user account
- for the database.
+ for the database. That user needs permission to execute ``CREATE DATABASE``
+ so that the test database can be created.
* The :setting:`PASSWORD` option needs to provide the password for
the :setting:`USER` that has been specified.
-* The :setting:`NAME` option must be the name of an existing database to
- which the given user has permission to connect. The unit tests will not
- touch this database; the test runner creates a new database whose name
- is :setting:`NAME` prefixed with ``test_``, and this test database is
- deleted when the tests are finished. This means your user account needs
- permission to execute ``CREATE DATABASE``.
+Test databases get their names by prepending ``test_`` to the value of the
+:setting:`NAME` settings for the databases defined in :setting:`DATABASES`.
+These test databases are deleted when the tests are finished.
+.. versionchanged:: 1.7
+ Before Django 1.7, the :setting:`NAME` setting was mandatory and had to
+ be the name of an existing database to which the given user had permission
+ to connect.
You will also need to ensure that your database uses UTF-8 as the default
character set. If your database server doesn't use UTF-8 as a default charset,

0 comments on commit e953c78

Please sign in to comment.
Something went wrong with that request. Please try again.