From ee0abac169c2dcc6818d583247903c2a8ef55f7c Mon Sep 17 00:00:00 2001
From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Date: Wed, 14 Oct 2020 13:09:24 +0200
Subject: [PATCH] Refs #32096 -- Fixed ExclusionConstraint crash with JSONField
 key transforms in expressions.

Regression in 6789ded0a6ab797f0dcdfa6ad5d1cfa46e23abcd.
---
 django/contrib/postgres/constraints.py          |  2 +-
 docs/releases/3.1.3.txt                         |  6 ++++++
 .../migrations/0002_create_test_models.py       |  1 +
 tests/postgres_tests/models.py                  |  1 +
 tests/postgres_tests/test_constraints.py        | 17 +++++++++++++++++
 5 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/django/contrib/postgres/constraints.py b/django/contrib/postgres/constraints.py
index 12c185a777..a844b899b7 100644
--- a/django/contrib/postgres/constraints.py
+++ b/django/contrib/postgres/constraints.py
@@ -72,7 +72,7 @@ class ExclusionConstraint(BaseConstraint):
             if isinstance(expression, str):
                 expression = F(expression)
             expression = expression.resolve_expression(query=query)
-            sql, params = expression.as_sql(compiler, schema_editor.connection)
+            sql, params = compiler.compile(expression)
             try:
                 opclass = self.opclasses[idx]
                 if opclass:
diff --git a/docs/releases/3.1.3.txt b/docs/releases/3.1.3.txt
index f1a9aaf42d..38fb204397 100644
--- a/docs/releases/3.1.3.txt
+++ b/docs/releases/3.1.3.txt
@@ -34,3 +34,9 @@ Bugfixes
 * Fixed a regression in Django 3.1 that caused a crash of
   :class:`~django.db.models.ExpressionWrapper` with key transforms for
   :class:`~django.db.models.JSONField` (:ticket:`32096`).
+
+* Fixed a regression in Django 3.1 that caused a migrations crash on PostgreSQL
+  when adding an
+  :class:`~django.contrib.postgres.constraints.ExclusionConstraint` with key
+  transforms for :class:`~django.db.models.JSONField` in ``expressions``
+  (:ticket:`32096`).
diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py
index e334057d22..cd8b20ae01 100644
--- a/tests/postgres_tests/migrations/0002_create_test_models.py
+++ b/tests/postgres_tests/migrations/0002_create_test_models.py
@@ -303,6 +303,7 @@ class Migration(migrations.Migration):
                 ('start', models.DateTimeField()),
                 ('end', models.DateTimeField()),
                 ('cancelled', models.BooleanField(default=False)),
+                ('requirements', models.JSONField(blank=True, null=True)),
             ],
             options={
                 'required_db_vendor': 'postgresql',
diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py
index a5bfc72fe7..56c81064f3 100644
--- a/tests/postgres_tests/models.py
+++ b/tests/postgres_tests/models.py
@@ -191,3 +191,4 @@ class HotelReservation(PostgreSQLModel):
     start = models.DateTimeField()
     end = models.DateTimeField()
     cancelled = models.BooleanField(default=False)
+    requirements = models.JSONField(blank=True, null=True)
diff --git a/tests/postgres_tests/test_constraints.py b/tests/postgres_tests/test_constraints.py
index 89463650e8..8621d7a052 100644
--- a/tests/postgres_tests/test_constraints.py
+++ b/tests/postgres_tests/test_constraints.py
@@ -7,6 +7,7 @@ from django.db import (
 from django.db.models import (
     CheckConstraint, Deferrable, F, Func, Q, UniqueConstraint,
 )
+from django.db.models.fields.json import KeyTextTransform
 from django.db.models.functions import Left
 from django.test import skipUnlessDBFeature
 from django.utils import timezone
@@ -620,6 +621,22 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
             editor.add_constraint(Scene, constraint)
         self.assertIn(constraint_name, self.get_constraints(Scene._meta.db_table))
 
+    def test_expressions_with_key_transform(self):
+        constraint_name = 'exclude_overlapping_reservations_smoking'
+        constraint = ExclusionConstraint(
+            name=constraint_name,
+            expressions=[
+                (F('datespan'), RangeOperators.OVERLAPS),
+                (KeyTextTransform('smoking', 'requirements'), RangeOperators.EQUAL),
+            ],
+        )
+        with connection.schema_editor() as editor:
+            editor.add_constraint(HotelReservation, constraint)
+        self.assertIn(
+            constraint_name,
+            self.get_constraints(HotelReservation._meta.db_table),
+        )
+
     def test_range_adjacent_initially_deferred(self):
         constraint_name = 'ints_adjacent_deferred'
         self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))