Commit 32260503 authored by Claude Paroz's avatar Claude Paroz
Browse files

Fixed #24791 -- Added fallback when 'postgres' database isn't available

Thanks Carl Meyer and Tim Graham for the reviews.
parent 2dee853e
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -217,7 +217,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"
+26 −0
Original line number Diff line number Diff line
@@ -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
+4 −0
Original line number Diff line number Diff line
@@ -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`).
+26 −0
Original line number Diff line number Diff line
@@ -161,6 +161,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"""