[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 6b048b364c.
Backport of 7991111af1 from master
This commit is contained in:
Mariusz Felisiak 2019-07-10 10:33:36 +02:00
parent 9dee8515d6
commit 1088a9777d
6 changed files with 76 additions and 3 deletions

View file

@ -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'

View file

@ -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):

View file

@ -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),
)

View file

@ -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}]'
)