diff options
| author | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2020-10-14 13:09:24 +0200 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2020-10-14 22:10:09 +0200 |
| commit | 536213278c77884e9f985c18a8f9e1158f0d680a (patch) | |
| tree | fa7991fd0970addfad18c2df3008caec05d5b174 | |
| parent | ae6b24093c7ccc730850d11bbe93e1481ebd2912 (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.py | 9 | ||||
| -rw-r--r-- | docs/releases/3.1.3.txt | 6 | ||||
| -rw-r--r-- | tests/postgres_tests/migrations/0002_create_test_models.py | 1 | ||||
| -rw-r--r-- | tests/postgres_tests/models.py | 1 | ||||
| -rw-r--r-- | tests/postgres_tests/test_constraints.py | 17 |
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)) |
