From cd7efa20338cb6f3ede4780e00590c0a6dd48ca2 Mon Sep 17 00:00:00 2001
From: Simon Charette <charette.s@gmail.com>
Date: Sat, 17 Dec 2016 15:06:47 -0500
Subject: [PATCH] Fixed #25492 -- Checked deferred foreign key constraints
 before dropping them.

This allows running foreign key data and schema altering operations in the
same migration on PostgreSQL.

Thanks Tim for review.
---
 django/db/backends/postgresql/schema.py |  4 ++++
 tests/schema/tests.py                   | 17 +++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/django/db/backends/postgresql/schema.py b/django/db/backends/postgresql/schema.py
index 1afb85d468..bffa8bd45b 100644
--- a/django/db/backends/postgresql/schema.py
+++ b/django/db/backends/postgresql/schema.py
@@ -15,6 +15,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
     sql_create_varchar_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s varchar_pattern_ops)%(extra)s"
     sql_create_text_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s text_pattern_ops)%(extra)s"
 
+    # Setting the constraint to IMMEDIATE runs any deferred checks to allow
+    # dropping it in the same transaction.
+    sql_delete_fk = "SET CONSTRAINTS %(name)s IMMEDIATE; ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
+
     def quote_value(self, value):
         return psycopg2.extensions.adapt(value)
 
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index e8cb1996cb..cf96e4163e 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -532,6 +532,23 @@ class SchemaTests(TransactionTestCase):
         with connection.schema_editor() as editor:
             editor.alter_field(Note, old_field, new_field, strict=True)
 
+    @skipUnlessDBFeature('can_defer_constraint_checks', 'can_rollback_ddl')
+    def test_alter_fk_checks_deferred_constraints(self):
+        """
+        #25492 - Altering a foreign key's structure and data in the same
+        transaction.
+        """
+        with connection.schema_editor() as editor:
+            editor.create_model(Node)
+        old_field = Node._meta.get_field('parent')
+        new_field = ForeignKey(Node, CASCADE)
+        new_field.set_attributes_from_name('parent')
+        parent = Node.objects.create()
+        with connection.schema_editor() as editor:
+            # Update the parent FK to create a deferred constraint check.
+            Node.objects.update(parent=parent)
+            editor.alter_field(Node, old_field, new_field, strict=True)
+
     def test_alter_text_field_to_date_field(self):
         """
         #25002 - Test conversion of text field to date field.