mirror of
https://github.com/django/django.git
synced 2025-01-19 06:43:15 +00:00
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.
This commit is contained in:
parent
ee6e93ec87
commit
7991111af1
@ -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}]'
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user