From 5d674eac871a306405b0fbbaeb17bbeba9c68bf3 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Thu, 5 Dec 2019 09:54:27 +0100 Subject: [PATCH] Fixed #31039 -- Added support for contained_by lookup with AutoFields, SmallIntegerField, and DecimalField. --- django/contrib/postgres/fields/ranges.py | 9 ++- docs/ref/contrib/postgres/fields.txt | 16 ++++- docs/releases/3.1.txt | 7 +++ .../migrations/0002_create_test_models.py | 16 +++++ tests/postgres_tests/models.py | 10 +++ tests/postgres_tests/test_ranges.py | 62 ++++++++++++++++++- 6 files changed, 116 insertions(+), 4 deletions(-) diff --git a/django/contrib/postgres/fields/ranges.py b/django/contrib/postgres/fields/ranges.py index b0b9275622..9a89fda02d 100644 --- a/django/contrib/postgres/fields/ranges.py +++ b/django/contrib/postgres/fields/ranges.py @@ -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 diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index cd35a33ee6..e8fcef6215 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -738,10 +738,14 @@ operators ``@>``, ``<@``, and ``&&`` respectively. ]> 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:: ... ) ]> +.. 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`` diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 631978d0ac..ec107bf29b 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -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` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py index 0e36cd1256..12d94e348a 100644 --- a/tests/postgres_tests/migrations/0002_create_test_models.py +++ b/tests/postgres_tests/migrations/0002_create_test_models.py @@ -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', diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py index 2cf47b88c7..8528c59da1 100644 --- a/tests/postgres_tests/models.py +++ b/tests/postgres_tests/models.py @@ -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): diff --git a/tests/postgres_tests/test_ranges.py b/tests/postgres_tests/test_ranges.py index 789ff3d546..7257d66505 100644 --- a/tests/postgres_tests/test_ranges.py +++ b/tests/postgres_tests/test_ranges.py @@ -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 = [