浏览代码

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

Hasan Ramezani 5 年之前
父节点
当前提交
5d674eac87

+ 8 - 1
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

+ 14 - 2
docs/ref/contrib/postgres/fields.txt

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

+ 7 - 0
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`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 16 - 0
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',

+ 10 - 0
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):

+ 61 - 1
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 = [