summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2019-07-10 10:33:36 +0200
committerGitHub <noreply@github.com>2019-07-10 10:33:36 +0200
commit7991111af12056ec9a856f35935d273526338c1f (patch)
tree4067b5171a44f6d559c6d44cda34338c75b73ecc
parentee6e93ec8727d0f5ed33190a3c354867669ed72f (diff)
Fixed #30621 -- Fixed crash of __contains lookup for Date/DateTimeRangeField when the right hand side is the same type.
Thanks Tilman Koschnick for the report and initial patch. Thanks Carlton Gibson the review. Regression in 6b048b364ca1e0e56a0d3815bf2be33ac9998355.
-rw-r--r--django/contrib/postgres/fields/ranges.py7
-rw-r--r--docs/releases/2.2.4.txt6
-rw-r--r--tests/postgres_tests/migrations/0002_create_test_models.py2
-rw-r--r--tests/postgres_tests/models.py2
-rw-r--r--tests/postgres_tests/test_constraints.py52
-rw-r--r--tests/postgres_tests/test_ranges.py10
6 files changed, 76 insertions, 3 deletions
diff --git a/django/contrib/postgres/fields/ranges.py b/django/contrib/postgres/fields/ranges.py
index 74ba4eb230..0e8a347d5f 100644
--- a/django/contrib/postgres/fields/ranges.py
+++ b/django/contrib/postgres/fields/ranges.py
@@ -170,7 +170,12 @@ class DateTimeRangeContains(models.Lookup):
params = lhs_params + rhs_params
# Cast the rhs if needed.
cast_sql = ''
- if isinstance(self.rhs, models.Expression) and self.rhs._output_field_or_none:
+ if (
+ isinstance(self.rhs, models.Expression) and
+ self.rhs._output_field_or_none and
+ # Skip cast if rhs has a matching range type.
+ not isinstance(self.rhs._output_field_or_none, self.lhs.output_field.__class__)
+ ):
cast_internal_type = self.lhs.output_field.base_field.get_internal_type()
cast_sql = '::{}'.format(connection.data_types.get(cast_internal_type))
return '%s @> %s%s' % (lhs, rhs, cast_sql), params
diff --git a/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt
index a1a849680d..0ad92f4ab1 100644
--- a/docs/releases/2.2.4.txt
+++ b/docs/releases/2.2.4.txt
@@ -12,3 +12,9 @@ Bugfixes
* Fixed a regression in Django 2.2 when ordering a ``QuerySet.union()``,
``intersection()``, or ``difference()`` by a field type present more than
once results in the wrong ordering being used (:ticket:`30628`).
+
+* Fixed a migration crash on PostgreSQL when adding a check constraint
+ with a ``contains`` lookup on
+ :class:`~django.contrib.postgres.fields.DateRangeField` or
+ :class:`~django.contrib.postgres.fields.DateTimeRangeField`, if the right
+ hand side of an expression is the same type (:ticket:`30621`).
diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py
index dc941de139..b9f9cee6bf 100644
--- a/tests/postgres_tests/migrations/0002_create_test_models.py
+++ b/tests/postgres_tests/migrations/0002_create_test_models.py
@@ -211,7 +211,9 @@ class Migration(migrations.Migration):
('bigints', BigIntegerRangeField(null=True, blank=True)),
('decimals', DecimalRangeField(null=True, blank=True)),
('timestamps', DateTimeRangeField(null=True, blank=True)),
+ ('timestamps_inner', DateTimeRangeField(null=True, blank=True)),
('dates', DateRangeField(null=True, blank=True)),
+ ('dates_inner', DateRangeField(null=True, blank=True)),
],
options={
'required_db_vendor': 'postgresql'
diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py
index 2bb6e6fcdf..3d170a9a1a 100644
--- a/tests/postgres_tests/models.py
+++ b/tests/postgres_tests/models.py
@@ -135,7 +135,9 @@ class RangesModel(PostgreSQLModel):
bigints = BigIntegerRangeField(blank=True, null=True)
decimals = DecimalRangeField(blank=True, null=True)
timestamps = DateTimeRangeField(blank=True, null=True)
+ timestamps_inner = DateTimeRangeField(blank=True, null=True)
dates = DateRangeField(blank=True, null=True)
+ dates_inner = DateRangeField(blank=True, null=True)
class RangeLookupsModel(PostgreSQLModel):
diff --git a/tests/postgres_tests/test_constraints.py b/tests/postgres_tests/test_constraints.py
index 0e09a1c546..2fc6ee5322 100644
--- a/tests/postgres_tests/test_constraints.py
+++ b/tests/postgres_tests/test_constraints.py
@@ -1,5 +1,7 @@
+import datetime
+
from django.db import connection, transaction
-from django.db.models import Q
+from django.db.models import F, Q
from django.db.models.constraints import CheckConstraint
from django.db.utils import IntegrityError
@@ -33,3 +35,51 @@ class SchemaTests(PostgreSQLTestCase):
with self.assertRaises(IntegrityError), transaction.atomic():
RangesModel.objects.create(ints=(20, 50))
RangesModel.objects.create(ints=(10, 30))
+
+ def test_check_constraint_daterange_contains(self):
+ constraint_name = 'dates_contains'
+ self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
+ constraint = CheckConstraint(
+ check=Q(dates__contains=F('dates_inner')),
+ name=constraint_name,
+ )
+ with connection.schema_editor() as editor:
+ editor.add_constraint(RangesModel, constraint)
+ with connection.cursor() as cursor:
+ constraints = connection.introspection.get_constraints(cursor, RangesModel._meta.db_table)
+ self.assertIn(constraint_name, constraints)
+ date_1 = datetime.date(2016, 1, 1)
+ date_2 = datetime.date(2016, 1, 4)
+ with self.assertRaises(IntegrityError), transaction.atomic():
+ RangesModel.objects.create(
+ dates=(date_1, date_2),
+ dates_inner=(date_1, date_2.replace(day=5)),
+ )
+ RangesModel.objects.create(
+ dates=(date_1, date_2),
+ dates_inner=(date_1, date_2),
+ )
+
+ def test_check_constraint_datetimerange_contains(self):
+ constraint_name = 'timestamps_contains'
+ self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
+ constraint = CheckConstraint(
+ check=Q(timestamps__contains=F('timestamps_inner')),
+ name=constraint_name,
+ )
+ with connection.schema_editor() as editor:
+ editor.add_constraint(RangesModel, constraint)
+ with connection.cursor() as cursor:
+ constraints = connection.introspection.get_constraints(cursor, RangesModel._meta.db_table)
+ self.assertIn(constraint_name, constraints)
+ datetime_1 = datetime.datetime(2016, 1, 1)
+ datetime_2 = datetime.datetime(2016, 1, 2, 12)
+ with self.assertRaises(IntegrityError), transaction.atomic():
+ RangesModel.objects.create(
+ timestamps=(datetime_1, datetime_2),
+ timestamps_inner=(datetime_1, datetime_2.replace(hour=13)),
+ )
+ RangesModel.objects.create(
+ timestamps=(datetime_1, datetime_2),
+ timestamps_inner=(datetime_1, datetime_2),
+ )
diff --git a/tests/postgres_tests/test_ranges.py b/tests/postgres_tests/test_ranges.py
index ae834b6ff0..89f32ee77c 100644
--- a/tests/postgres_tests/test_ranges.py
+++ b/tests/postgres_tests/test_ranges.py
@@ -115,11 +115,15 @@ class TestRangeContainsLookup(PostgreSQLTestCase):
]
cls.obj = RangesModel.objects.create(
dates=(cls.dates[0], cls.dates[3]),
+ dates_inner=(cls.dates[1], cls.dates[2]),
timestamps=(cls.timestamps[0], cls.timestamps[3]),
+ timestamps_inner=(cls.timestamps[1], cls.timestamps[2]),
)
cls.aware_obj = RangesModel.objects.create(
dates=(cls.dates[0], cls.dates[3]),
+ dates_inner=(cls.dates[1], cls.dates[2]),
timestamps=(cls.aware_timestamps[0], cls.aware_timestamps[3]),
+ timestamps_inner=(cls.timestamps[1], cls.timestamps[2]),
)
# Objects that don't match any queries.
for i in range(3, 4):
@@ -140,6 +144,7 @@ class TestRangeContainsLookup(PostgreSQLTestCase):
(self.aware_timestamps[1], self.aware_timestamps[2]),
Value(self.dates[0], output_field=DateTimeField()),
Func(F('dates'), function='lower', output_field=DateTimeField()),
+ F('timestamps_inner'),
)
for filter_arg in filter_args:
with self.subTest(filter_arg=filter_arg):
@@ -154,6 +159,7 @@ class TestRangeContainsLookup(PostgreSQLTestCase):
(self.dates[1], self.dates[2]),
Value(self.dates[0], output_field=DateField()),
Func(F('timestamps'), function='lower', output_field=DateField()),
+ F('dates_inner'),
)
for filter_arg in filter_args:
with self.subTest(filter_arg=filter_arg):
@@ -361,7 +367,9 @@ class TestSerialization(PostgreSQLSimpleTestCase):
'\\"bounds\\": \\"[)\\"}", "decimals": "{\\"empty\\": true}", '
'"bigints": null, "timestamps": "{\\"upper\\": \\"2014-02-02T12:12:12+00:00\\", '
'\\"lower\\": \\"2014-01-01T00:00:00+00:00\\", \\"bounds\\": \\"[)\\"}", '
- '"dates": "{\\"upper\\": \\"2014-02-02\\", \\"lower\\": \\"2014-01-01\\", \\"bounds\\": \\"[)\\"}" }, '
+ '"timestamps_inner": null, '
+ '"dates": "{\\"upper\\": \\"2014-02-02\\", \\"lower\\": \\"2014-01-01\\", \\"bounds\\": \\"[)\\"}", '
+ '"dates_inner": null }, '
'"model": "postgres_tests.rangesmodel", "pk": null}]'
)