summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2020-10-14 13:09:24 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-10-14 22:10:09 +0200
commit536213278c77884e9f985c18a8f9e1158f0d680a (patch)
treefa7991fd0970addfad18c2df3008caec05d5b174
parentae6b24093c7ccc730850d11bbe93e1481ebd2912 (diff)
[3.1.x] Refs #32096 -- Fixed ExclusionConstraint crash with JSONField key transforms in expressions.
Regression in 6789ded0a6ab797f0dcdfa6ad5d1cfa46e23abcd. Backport of ee0abac169c2dcc6818d583247903c2a8ef55f7c from master.
-rw-r--r--django/contrib/postgres/constraints.py9
-rw-r--r--docs/releases/3.1.3.txt6
-rw-r--r--tests/postgres_tests/migrations/0002_create_test_models.py1
-rw-r--r--tests/postgres_tests/models.py1
-rw-r--r--tests/postgres_tests/test_constraints.py17
5 files changed, 30 insertions, 4 deletions
diff --git a/django/contrib/postgres/constraints.py b/django/contrib/postgres/constraints.py
index aea1f9bb3d..038840990b 100644
--- a/django/contrib/postgres/constraints.py
+++ b/django/contrib/postgres/constraints.py
@@ -45,14 +45,15 @@ class ExclusionConstraint(BaseConstraint):
self.deferrable = deferrable
super().__init__(name=name)
- def _get_expression_sql(self, compiler, connection, query):
+ def _get_expression_sql(self, compiler, schema_editor, query):
expressions = []
for expression, operator in self.expressions:
if isinstance(expression, str):
expression = F(expression)
expression = expression.resolve_expression(query=query)
- sql, params = expression.as_sql(compiler, connection)
- expressions.append('%s WITH %s' % (sql % params, operator))
+ sql, params = compiler.compile(expression)
+ sql = sql % tuple(schema_editor.quote_value(p) for p in params)
+ expressions.append('%s WITH %s' % (sql, operator))
return expressions
def _get_condition_sql(self, compiler, schema_editor, query):
@@ -65,7 +66,7 @@ class ExclusionConstraint(BaseConstraint):
def constraint_sql(self, model, schema_editor):
query = Query(model, alias_cols=False)
compiler = query.get_compiler(connection=schema_editor.connection)
- expressions = self._get_expression_sql(compiler, schema_editor.connection, query)
+ expressions = self._get_expression_sql(compiler, schema_editor, query)
condition = self._get_condition_sql(compiler, schema_editor, query)
return self.template % {
'name': schema_editor.quote_name(self.name),
diff --git a/docs/releases/3.1.3.txt b/docs/releases/3.1.3.txt
index e6c13fff40..8c7b533b48 100644
--- a/docs/releases/3.1.3.txt
+++ b/docs/releases/3.1.3.txt
@@ -33,3 +33,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 4c2e04705d..35bde7ba06 100644
--- a/tests/postgres_tests/test_constraints.py
+++ b/tests/postgres_tests/test_constraints.py
@@ -3,6 +3,7 @@ from unittest import mock
from django.db import IntegrityError, connection, transaction
from django.db.models import CheckConstraint, Deferrable, F, Func, Q
+from django.db.models.fields.json import KeyTextTransform
from django.utils import timezone
from . import PostgreSQLTestCase
@@ -397,6 +398,22 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
editor.remove_constraint(RangesModel, constraint)
self.assertNotIn(constraint_name, self.get_constraints(RangesModel._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))