Commit 9b7d512d authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

[1.7.x] Fixed #24318 -- Set the transaction isolation level with psycopg >= 2.4.2.

Backport of 76356d96 from master
parent c16f9c2d
Loading
Loading
Loading
Loading
+23 −5
Original line number Diff line number Diff line
@@ -93,10 +93,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
    def __init__(self, *args, **kwargs):
        super(DatabaseWrapper, self).__init__(*args, **kwargs)

        opts = self.settings_dict["OPTIONS"]
        RC = psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED
        self.isolation_level = opts.get('isolation_level', RC)

        self.features = DatabaseFeatures(self)
        self.ops = DatabaseOperations(self)
        self.client = DatabaseClient(self)
@@ -131,7 +127,29 @@ class DatabaseWrapper(BaseDatabaseWrapper):
        return conn_params

    def get_new_connection(self, conn_params):
        return Database.connect(**conn_params)
        connection = Database.connect(**conn_params)

        # self.isolation_level must be set:
        # - after connecting to the database in order to obtain the database's
        #   default when no value is explicitly specified in options.
        # - before calling _set_autocommit() because if autocommit is on, that
        #   will set connection.isolation_level to ISOLATION_LEVEL_AUTOCOMMIT;
        #   and if autocommit is off, on psycopg2 < 2.4.2, _set_autocommit()
        #   needs self.isolation_level.
        options = self.settings_dict['OPTIONS']
        try:
            self.isolation_level = options['isolation_level']
        except KeyError:
            self.isolation_level = connection.isolation_level
        else:
            # Set the isolation level to the value from OPTIONS. This isn't
            # needed on psycopg2 < 2.4.2 because it happens as a side-effect
            # of _set_autocommit(False).
            if (self.isolation_level != connection.isolation_level and
                    self.psycopg2_version >= (2, 4, 2)):
                connection.set_session(isolation_level=self.isolation_level)

        return connection

    def init_connection_state(self):
        settings_dict = self.settings_dict
+4 −0
Original line number Diff line number Diff line
@@ -19,3 +19,7 @@ Bugfixes

* Fixed crash in ``contrib.sites`` migrations when a default database isn't
  used (:ticket:`24332`).

* Added the ability to set the isolation level on PostgreSQL with psycopg2 ≥
  2.4.2 (:ticket:`24318`). It was advertised as a new feature in Django 1.6
  but it didn't work in practice.
+28 −0
Original line number Diff line number Diff line
@@ -233,6 +233,34 @@ class PostgreSQLTests(TestCase):
        finally:
            new_connection.close()

    def test_connect_isolation_level(self):
        """
        Regression test for #18130 and #24318.
        """
        from psycopg2.extensions import (
            ISOLATION_LEVEL_READ_COMMITTED as read_committed,
            ISOLATION_LEVEL_SERIALIZABLE as serializable,
        )

        # Since this is a django.test.TestCase, a transaction is in progress
        # and the isolation level isn't reported as 0. This test assumes that
        # PostgreSQL is configured with the default isolation level.

        # Check the level on the psycopg2 connection, not the Django wrapper.
        self.assertEqual(connection.connection.isolation_level, read_committed)

        databases = copy.deepcopy(settings.DATABASES)
        databases[DEFAULT_DB_ALIAS]['OPTIONS']['isolation_level'] = serializable
        new_connections = ConnectionHandler(databases)
        new_connection = new_connections[DEFAULT_DB_ALIAS]
        try:
            # Start a transaction so the isolation level isn't reported as 0.
            new_connection.set_autocommit(False)
            # Check the level on the psycopg2 connection, not the Django wrapper.
            self.assertEqual(new_connection.connection.isolation_level, serializable)
        finally:
            new_connection.close()

    def _select(self, val):
        with connection.cursor() as cursor:
            cursor.execute("SELECT %s", (val,))