1
0
mirror of https://github.com/django/django.git synced 2025-03-24 16:20:46 +00:00

Fixed -- 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.
This commit is contained in:
Mariusz Felisiak 2019-07-10 10:33:36 +02:00 committed by GitHub
parent ee6e93ec87
commit 7991111af1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 3 deletions
django/contrib/postgres/fields
docs/releases
tests/postgres_tests

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

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

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