[2.2.x] 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 for the review. Regression in 6b048b364ca1e0e56a0d3815bf2be33ac9998355. Backport of 7991111af12056ec9a856f35935d273526338c1f from master
This commit is contained in:
parent
9dee8515d6
commit
1088a9777d
@ -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
|
||||
|
@ -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`).
|
||||
|
@ -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'
|
||||
|
@ -131,7 +131,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):
|
||||
|
@ -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),
|
||||
)
|
||||
|
@ -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}]'
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user