Fixed #31039 -- Added support for contained_by lookup with AutoFields, SmallIntegerField, and DecimalField.
This commit is contained in:
parent
664521c56a
commit
5d674eac87
|
@ -199,9 +199,11 @@ DateTimeRangeField.register_lookup(DateTimeRangeContains)
|
||||||
class RangeContainedBy(lookups.PostgresSimpleLookup):
|
class RangeContainedBy(lookups.PostgresSimpleLookup):
|
||||||
lookup_name = 'contained_by'
|
lookup_name = 'contained_by'
|
||||||
type_mapping = {
|
type_mapping = {
|
||||||
|
'smallint': 'int4range',
|
||||||
'integer': 'int4range',
|
'integer': 'int4range',
|
||||||
'bigint': 'int8range',
|
'bigint': 'int8range',
|
||||||
'double precision': 'numrange',
|
'double precision': 'numrange',
|
||||||
|
'numeric': 'numrange',
|
||||||
'date': 'daterange',
|
'date': 'daterange',
|
||||||
'timestamp with time zone': 'tstzrange',
|
'timestamp with time zone': 'tstzrange',
|
||||||
}
|
}
|
||||||
|
@ -209,13 +211,17 @@ class RangeContainedBy(lookups.PostgresSimpleLookup):
|
||||||
|
|
||||||
def process_rhs(self, compiler, connection):
|
def process_rhs(self, compiler, connection):
|
||||||
rhs, rhs_params = super().process_rhs(compiler, connection)
|
rhs, rhs_params = super().process_rhs(compiler, connection)
|
||||||
cast_type = self.type_mapping[self.lhs.output_field.db_type(connection)]
|
# Ignore precision for DecimalFields.
|
||||||
|
db_type = self.lhs.output_field.cast_db_type(connection).split('(')[0]
|
||||||
|
cast_type = self.type_mapping[db_type]
|
||||||
return '%s::%s' % (rhs, cast_type), rhs_params
|
return '%s::%s' % (rhs, cast_type), rhs_params
|
||||||
|
|
||||||
def process_lhs(self, compiler, connection):
|
def process_lhs(self, compiler, connection):
|
||||||
lhs, lhs_params = super().process_lhs(compiler, connection)
|
lhs, lhs_params = super().process_lhs(compiler, connection)
|
||||||
if isinstance(self.lhs.output_field, models.FloatField):
|
if isinstance(self.lhs.output_field, models.FloatField):
|
||||||
lhs = '%s::numeric' % lhs
|
lhs = '%s::numeric' % lhs
|
||||||
|
elif isinstance(self.lhs.output_field, models.SmallIntegerField):
|
||||||
|
lhs = '%s::integer' % lhs
|
||||||
return lhs, lhs_params
|
return lhs, lhs_params
|
||||||
|
|
||||||
def get_prep_lookup(self):
|
def get_prep_lookup(self):
|
||||||
|
@ -226,6 +232,7 @@ models.DateField.register_lookup(RangeContainedBy)
|
||||||
models.DateTimeField.register_lookup(RangeContainedBy)
|
models.DateTimeField.register_lookup(RangeContainedBy)
|
||||||
models.IntegerField.register_lookup(RangeContainedBy)
|
models.IntegerField.register_lookup(RangeContainedBy)
|
||||||
models.FloatField.register_lookup(RangeContainedBy)
|
models.FloatField.register_lookup(RangeContainedBy)
|
||||||
|
models.DecimalField.register_lookup(RangeContainedBy)
|
||||||
|
|
||||||
|
|
||||||
@RangeField.register_lookup
|
@RangeField.register_lookup
|
||||||
|
|
|
@ -738,10 +738,14 @@ operators ``@>``, ``<@``, and ``&&`` respectively.
|
||||||
<QuerySet [<Event: Soft play>]>
|
<QuerySet [<Event: Soft play>]>
|
||||||
|
|
||||||
The ``contained_by`` lookup is also available on the non-range field types:
|
The ``contained_by`` lookup is also available on the non-range field types:
|
||||||
|
:class:`~django.db.models.SmallAutoField`,
|
||||||
|
:class:`~django.db.models.AutoField`, :class:`~django.db.models.BigAutoField`,
|
||||||
|
:class:`~django.db.models.SmallIntegerField`,
|
||||||
:class:`~django.db.models.IntegerField`,
|
:class:`~django.db.models.IntegerField`,
|
||||||
:class:`~django.db.models.BigIntegerField`,
|
:class:`~django.db.models.BigIntegerField`,
|
||||||
:class:`~django.db.models.FloatField`, :class:`~django.db.models.DateField`,
|
:class:`~django.db.models.DecimalField`, :class:`~django.db.models.FloatField`,
|
||||||
and :class:`~django.db.models.DateTimeField`. For example::
|
:class:`~django.db.models.DateField`, and
|
||||||
|
:class:`~django.db.models.DateTimeField`. For example::
|
||||||
|
|
||||||
>>> from psycopg2.extras import DateTimeTZRange
|
>>> from psycopg2.extras import DateTimeTZRange
|
||||||
>>> Event.objects.filter(start__contained_by=DateTimeTZRange(
|
>>> Event.objects.filter(start__contained_by=DateTimeTZRange(
|
||||||
|
@ -750,6 +754,14 @@ and :class:`~django.db.models.DateTimeField`. For example::
|
||||||
... )
|
... )
|
||||||
<QuerySet [<Event: Soft play>]>
|
<QuerySet [<Event: Soft play>]>
|
||||||
|
|
||||||
|
.. versionchanged:: 3.1
|
||||||
|
|
||||||
|
Support for :class:`~django.db.models.SmallAutoField`,
|
||||||
|
:class:`~django.db.models.AutoField`,
|
||||||
|
:class:`~django.db.models.BigAutoField`,
|
||||||
|
:class:`~django.db.models.SmallIntegerField`, and
|
||||||
|
:class:`~django.db.models.DecimalField` was added.
|
||||||
|
|
||||||
.. fieldlookup:: rangefield.overlap
|
.. fieldlookup:: rangefield.overlap
|
||||||
|
|
||||||
``overlap``
|
``overlap``
|
||||||
|
|
|
@ -90,6 +90,13 @@ Minor features
|
||||||
:lookup:`rangefield.upper_inc`, and :lookup:`rangefield.upper_inf` allows
|
:lookup:`rangefield.upper_inc`, and :lookup:`rangefield.upper_inf` allows
|
||||||
querying :class:`~django.contrib.postgres.fields.RangeField` by a bound type.
|
querying :class:`~django.contrib.postgres.fields.RangeField` by a bound type.
|
||||||
|
|
||||||
|
* :lookup:`rangefield.contained_by` now supports
|
||||||
|
:class:`~django.db.models.SmallAutoField`,
|
||||||
|
:class:`~django.db.models.AutoField`,
|
||||||
|
:class:`~django.db.models.BigAutoField`,
|
||||||
|
:class:`~django.db.models.SmallIntegerField`, and
|
||||||
|
:class:`~django.db.models.DecimalField`.
|
||||||
|
|
||||||
:mod:`django.contrib.redirects`
|
:mod:`django.contrib.redirects`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,20 @@ class Migration(migrations.Migration):
|
||||||
options=None,
|
options=None,
|
||||||
bases=None,
|
bases=None,
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SmallAutoFieldModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.SmallAutoField(verbose_name='ID', serialize=False, primary_key=True)),
|
||||||
|
],
|
||||||
|
options=None,
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BigAutoFieldModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(verbose_name='ID', serialize=False, primary_key=True)),
|
||||||
|
],
|
||||||
|
options=None,
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Scene',
|
name='Scene',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -237,6 +251,8 @@ class Migration(migrations.Migration):
|
||||||
('float', models.FloatField(blank=True, null=True)),
|
('float', models.FloatField(blank=True, null=True)),
|
||||||
('timestamp', models.DateTimeField(blank=True, null=True)),
|
('timestamp', models.DateTimeField(blank=True, null=True)),
|
||||||
('date', models.DateField(blank=True, null=True)),
|
('date', models.DateField(blank=True, null=True)),
|
||||||
|
('small_integer', models.SmallIntegerField(blank=True, null=True)),
|
||||||
|
('decimal_field', models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'required_db_vendor': 'postgresql',
|
'required_db_vendor': 'postgresql',
|
||||||
|
|
|
@ -93,6 +93,14 @@ class TextFieldModel(models.Model):
|
||||||
return self.field
|
return self.field
|
||||||
|
|
||||||
|
|
||||||
|
class SmallAutoFieldModel(models.Model):
|
||||||
|
id = models.SmallAutoField(primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class BigAutoFieldModel(models.Model):
|
||||||
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
# Scene/Character/Line models are used to test full text search. They're
|
# Scene/Character/Line models are used to test full text search. They're
|
||||||
# populated with content from Monty Python and the Holy Grail.
|
# populated with content from Monty Python and the Holy Grail.
|
||||||
class Scene(models.Model):
|
class Scene(models.Model):
|
||||||
|
@ -148,6 +156,8 @@ class RangeLookupsModel(PostgreSQLModel):
|
||||||
float = models.FloatField(blank=True, null=True)
|
float = models.FloatField(blank=True, null=True)
|
||||||
timestamp = models.DateTimeField(blank=True, null=True)
|
timestamp = models.DateTimeField(blank=True, null=True)
|
||||||
date = models.DateField(blank=True, null=True)
|
date = models.DateField(blank=True, null=True)
|
||||||
|
small_integer = models.SmallIntegerField(blank=True, null=True)
|
||||||
|
decimal_field = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)
|
||||||
|
|
||||||
|
|
||||||
class JSONModel(PostgreSQLModel):
|
class JSONModel(PostgreSQLModel):
|
||||||
|
|
|
@ -11,7 +11,10 @@ from django.test.utils import isolate_apps
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase
|
from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase
|
||||||
from .models import PostgreSQLModel, RangeLookupsModel, RangesModel
|
from .models import (
|
||||||
|
BigAutoFieldModel, PostgreSQLModel, RangeLookupsModel, RangesModel,
|
||||||
|
SmallAutoFieldModel,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange
|
from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange
|
||||||
|
@ -354,6 +357,17 @@ class TestQueryingWithRanges(PostgreSQLTestCase):
|
||||||
[objs[0]],
|
[objs[0]],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_small_integer_field_contained_by(self):
|
||||||
|
objs = [
|
||||||
|
RangeLookupsModel.objects.create(small_integer=8),
|
||||||
|
RangeLookupsModel.objects.create(small_integer=4),
|
||||||
|
RangeLookupsModel.objects.create(small_integer=-1),
|
||||||
|
]
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
RangeLookupsModel.objects.filter(small_integer__contained_by=NumericRange(4, 6)),
|
||||||
|
[objs[1]],
|
||||||
|
)
|
||||||
|
|
||||||
def test_integer_range(self):
|
def test_integer_range(self):
|
||||||
objs = [
|
objs = [
|
||||||
RangeLookupsModel.objects.create(integer=5),
|
RangeLookupsModel.objects.create(integer=5),
|
||||||
|
@ -376,6 +390,19 @@ class TestQueryingWithRanges(PostgreSQLTestCase):
|
||||||
[objs[0]]
|
[objs[0]]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_decimal_field_contained_by(self):
|
||||||
|
objs = [
|
||||||
|
RangeLookupsModel.objects.create(decimal_field=Decimal('1.33')),
|
||||||
|
RangeLookupsModel.objects.create(decimal_field=Decimal('2.88')),
|
||||||
|
RangeLookupsModel.objects.create(decimal_field=Decimal('99.17')),
|
||||||
|
]
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
RangeLookupsModel.objects.filter(
|
||||||
|
decimal_field__contained_by=NumericRange(Decimal('1.89'), Decimal('7.91')),
|
||||||
|
),
|
||||||
|
[objs[1]],
|
||||||
|
)
|
||||||
|
|
||||||
def test_float_range(self):
|
def test_float_range(self):
|
||||||
objs = [
|
objs = [
|
||||||
RangeLookupsModel.objects.create(float=5),
|
RangeLookupsModel.objects.create(float=5),
|
||||||
|
@ -387,6 +414,39 @@ class TestQueryingWithRanges(PostgreSQLTestCase):
|
||||||
[objs[0]]
|
[objs[0]]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_small_auto_field_contained_by(self):
|
||||||
|
objs = SmallAutoFieldModel.objects.bulk_create([
|
||||||
|
SmallAutoFieldModel() for i in range(1, 5)
|
||||||
|
])
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
SmallAutoFieldModel.objects.filter(
|
||||||
|
id__contained_by=NumericRange(objs[1].pk, objs[3].pk),
|
||||||
|
),
|
||||||
|
objs[1:3],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_auto_field_contained_by(self):
|
||||||
|
objs = RangeLookupsModel.objects.bulk_create([
|
||||||
|
RangeLookupsModel() for i in range(1, 5)
|
||||||
|
])
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
RangeLookupsModel.objects.filter(
|
||||||
|
id__contained_by=NumericRange(objs[1].pk, objs[3].pk),
|
||||||
|
),
|
||||||
|
objs[1:3],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_big_auto_field_contained_by(self):
|
||||||
|
objs = BigAutoFieldModel.objects.bulk_create([
|
||||||
|
BigAutoFieldModel() for i in range(1, 5)
|
||||||
|
])
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
BigAutoFieldModel.objects.filter(
|
||||||
|
id__contained_by=NumericRange(objs[1].pk, objs[3].pk),
|
||||||
|
),
|
||||||
|
objs[1:3],
|
||||||
|
)
|
||||||
|
|
||||||
def test_f_ranges(self):
|
def test_f_ranges(self):
|
||||||
parent = RangesModel.objects.create(decimals=NumericRange(0, 10))
|
parent = RangesModel.objects.create(decimals=NumericRange(0, 10))
|
||||||
objs = [
|
objs = [
|
||||||
|
|
Loading…
Reference in New Issue