From cdf7f90f95022002464faede7f0a75fb11d10ecb Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 14 May 2015 19:27:31 +0200 Subject: [PATCH] [1.8.x] Fixed #24791 -- Added fallback when 'postgres' database isn't available Thanks Carl Meyer and Tim Graham for the reviews. Backport of 322605035 from master. --- django/db/backends/base/creation.py | 2 +- .../db/backends/postgresql_psycopg2/base.py | 26 +++++++++++++++++++ docs/releases/1.8.2.txt | 4 +++ tests/backends/tests.py | 26 +++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/django/db/backends/base/creation.py b/django/db/backends/base/creation.py index 1b05a1cb1a..2c803e5539 100644 --- a/django/db/backends/base/creation.py +++ b/django/db/backends/base/creation.py @@ -514,7 +514,7 @@ class BaseDatabaseCreation(object): # 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. - with self._nodb_connection.cursor() as cursor: + with self.connection._nodb_connection.cursor() as cursor: # Wait to avoid "database is being accessed by other users" errors. time.sleep(1) cursor.execute("DROP DATABASE %s" diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index d110b120d7..a76ca3c0df 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -4,10 +4,14 @@ PostgreSQL database backend for Django. Requires psycopg 2: http://initd.org/projects/psycopg2 """ +import warnings + from django.conf import settings from django.core.exceptions import ImproperlyConfigured +from django.db import DEFAULT_DB_ALIAS from django.db.backends.base.base import BaseDatabaseWrapper from django.db.backends.base.validation import BaseDatabaseValidation +from django.db.utils import DatabaseError as WrappedDatabaseError from django.utils.encoding import force_str from django.utils.functional import cached_property from django.utils.safestring import SafeBytes, SafeText @@ -230,6 +234,28 @@ class DatabaseWrapper(BaseDatabaseWrapper): else: return True + @cached_property + def _nodb_connection(self): + nodb_connection = super(DatabaseWrapper, self)._nodb_connection + try: + nodb_connection.ensure_connection() + except (DatabaseError, WrappedDatabaseError): + warnings.warn( + "Normally Django will use a connection to the 'postgres' database " + "to avoid running initialization queries against the production " + "database when it's not needed (for example, when running tests). " + "Django was unable to create a connection to the 'postgres' database " + "and will use the default database instead.", + RuntimeWarning + ) + settings_dict = self.settings_dict.copy() + settings_dict['NAME'] = settings.DATABASES[DEFAULT_DB_ALIAS]['NAME'] + nodb_connection = self.__class__( + self.settings_dict.copy(), + alias=self.alias, + allow_thread_sharing=False) + return nodb_connection + @cached_property def psycopg2_version(self): return PSYCOPG2_VERSION diff --git a/docs/releases/1.8.2.txt b/docs/releases/1.8.2.txt index 3682126aa1..896f4d0334 100644 --- a/docs/releases/1.8.2.txt +++ b/docs/releases/1.8.2.txt @@ -33,3 +33,7 @@ Bugfixes * Fixed session cookie deletion when using :setting:`SESSION_COOKIE_DOMAIN` (:ticket:`24799`). + +* On PostgreSQL, when no access is granted for the ``postgres`` database, + Django now falls back to the default database when it normally requires a + "no database" connection (:ticket:`24791`). diff --git a/tests/backends/tests.py b/tests/backends/tests.py index a624bf57b2..11136e3b09 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -162,6 +162,32 @@ class PostgreSQLTests(TestCase): self.assert_parses("PostgreSQL 9.4beta1", 90400) self.assert_parses("PostgreSQL 9.3.1 on i386-apple-darwin9.2.2, compiled by GCC i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5478)", 90301) + def test_nodb_connection(self): + """ + Test that the _nodb_connection property fallbacks to the default connection + database when access to the 'postgres' database is not granted. + """ + def mocked_connect(self): + if self.settings_dict['NAME'] is None: + raise DatabaseError() + return '' + + nodb_conn = connection._nodb_connection + self.assertIsNone(nodb_conn.settings_dict['NAME']) + + # Now assume the 'postgres' db isn't available + del connection._nodb_connection + with warnings.catch_warnings(record=True) as w: + with mock.patch('django.db.backends.base.base.BaseDatabaseWrapper.connect', + side_effect=mocked_connect, autospec=True): + nodb_conn = connection._nodb_connection + del connection._nodb_connection + self.assertIsNotNone(nodb_conn.settings_dict['NAME']) + self.assertEqual(nodb_conn.settings_dict['NAME'], settings.DATABASES[DEFAULT_DB_ALIAS]['NAME']) + # Check a RuntimeWarning nas been emitted + self.assertEqual(len(w), 1) + self.assertEqual(w[0].message.__class__, RuntimeWarning) + def test_version_detection(self): """Test PostgreSQL version detection"""