Fixed #31039 -- Added support for contained_by lookup with AutoFields, SmallIntegerField, and DecimalField.

This commit is contained in:
Hasan Ramezani 2019-12-05 09:54:27 +01:00 committed by Mariusz Felisiak
parent 664521c56a
commit 5d674eac87
6 changed files with 116 additions and 4 deletions

View File

@ -199,9 +199,11 @@ DateTimeRangeField.register_lookup(DateTimeRangeContains)
class RangeContainedBy(lookups.PostgresSimpleLookup):
lookup_name = 'contained_by'
type_mapping = {
'smallint': 'int4range',
'integer': 'int4range',
'bigint': 'int8range',
'double precision': 'numrange',
'numeric': 'numrange',
'date': 'daterange',
'timestamp with time zone': 'tstzrange',
}
@ -209,13 +211,17 @@ class RangeContainedBy(lookups.PostgresSimpleLookup):
def process_rhs(self, 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
def process_lhs(self, compiler, connection):
lhs, lhs_params = super().process_lhs(compiler, connection)
if isinstance(self.lhs.output_field, models.FloatField):
lhs = '%s::numeric' % lhs
elif isinstance(self.lhs.output_field, models.SmallIntegerField):
lhs = '%s::integer' % lhs
return lhs, lhs_params
def get_prep_lookup(self):
@ -226,6 +232,7 @@ models.DateField.register_lookup(RangeContainedBy)
models.DateTimeField.register_lookup(RangeContainedBy)
models.IntegerField.register_lookup(RangeContainedBy)
models.FloatField.register_lookup(RangeContainedBy)
models.DecimalField.register_lookup(RangeContainedBy)
@RangeField.register_lookup

View File

@ -738,10 +738,14 @@ operators ``@>``, ``<@``, and ``&&`` respectively.
<QuerySet [<Event: Soft play>]>
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.BigIntegerField`,
:class:`~django.db.models.FloatField`, :class:`~django.db.models.DateField`,
and :class:`~django.db.models.DateTimeField`. For example::
:class:`~django.db.models.DecimalField`, :class:`~django.db.models.FloatField`,
:class:`~django.db.models.DateField`, and
:class:`~django.db.models.DateTimeField`. For example::
>>> from psycopg2.extras import DateTimeTZRange
>>> Event.objects.filter(start__contained_by=DateTimeTZRange(
@ -750,6 +754,14 @@ and :class:`~django.db.models.DateTimeField`. For example::
... )
<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
``overlap``

View File

@ -90,6 +90,13 @@ Minor features
:lookup:`rangefield.upper_inc`, and :lookup:`rangefield.upper_inf` allows
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`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -124,6 +124,20 @@ class Migration(migrations.Migration):
options=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(
name='Scene',
fields=[
@ -237,6 +251,8 @@ class Migration(migrations.Migration):
('float', models.FloatField(blank=True, null=True)),
('timestamp', models.DateTimeField(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={
'required_db_vendor': 'postgresql',

View File

@ -93,6 +93,14 @@ class TextFieldModel(models.Model):
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
# populated with content from Monty Python and the Holy Grail.
class Scene(models.Model):
@ -148,6 +156,8 @@ class RangeLookupsModel(PostgreSQLModel):
float = models.FloatField(blank=True, null=True)
timestamp = models.DateTimeField(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):

View File

@ -11,7 +11,10 @@ from django.test.utils import isolate_apps
from django.utils import timezone
from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase
from .models import PostgreSQLModel, RangeLookupsModel, RangesModel
from .models import (
BigAutoFieldModel, PostgreSQLModel, RangeLookupsModel, RangesModel,
SmallAutoFieldModel,
)
try:
from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange
@ -354,6 +357,17 @@ class TestQueryingWithRanges(PostgreSQLTestCase):
[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):
objs = [
RangeLookupsModel.objects.create(integer=5),
@ -376,6 +390,19 @@ class TestQueryingWithRanges(PostgreSQLTestCase):
[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):
objs = [
RangeLookupsModel.objects.create(float=5),
@ -387,6 +414,39 @@ class TestQueryingWithRanges(PostgreSQLTestCase):
[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):
parent = RangesModel.objects.create(decimals=NumericRange(0, 10))
objs = [