Browse Source

Fixed #30987 -- Added models.PositiveBigIntegerField.

Caio Ariede 5 years ago
parent
commit
555bebe774

+ 1 - 0
django/contrib/gis/utils/layermapping.py

@@ -77,6 +77,7 @@ class LayerMapping:
         models.UUIDField: OFTString,
         models.BigIntegerField: (OFTInteger, OFTReal, OFTString),
         models.SmallIntegerField: (OFTInteger, OFTReal, OFTString),
+        models.PositiveBigIntegerField: (OFTInteger, OFTReal, OFTString),
         models.PositiveIntegerField: (OFTInteger, OFTReal, OFTString),
         models.PositiveSmallIntegerField: (OFTInteger, OFTReal, OFTString),
     }

+ 1 - 0
django/db/backends/base/operations.py

@@ -24,6 +24,7 @@ class BaseDatabaseOperations:
         'SmallIntegerField': (-32768, 32767),
         'IntegerField': (-2147483648, 2147483647),
         'BigIntegerField': (-9223372036854775808, 9223372036854775807),
+        'PositiveBigIntegerField': (0, 9223372036854775807),
         'PositiveSmallIntegerField': (0, 32767),
         'PositiveIntegerField': (0, 2147483647),
         'SmallAutoField': (-32768, 32767),

+ 2 - 0
django/db/backends/mysql/base.py

@@ -120,6 +120,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         'GenericIPAddressField': 'char(39)',
         'NullBooleanField': 'bool',
         'OneToOneField': 'integer',
+        'PositiveBigIntegerField': 'bigint UNSIGNED',
         'PositiveIntegerField': 'integer UNSIGNED',
         'PositiveSmallIntegerField': 'smallint UNSIGNED',
         'SlugField': 'varchar(%(max_length)s)',
@@ -339,6 +340,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
     def data_type_check_constraints(self):
         if self.features.supports_column_check_constraints:
             return {
+                'PositiveBigIntegerField': '`%(column)s` >= 0',
                 'PositiveIntegerField': '`%(column)s` >= 0',
                 'PositiveSmallIntegerField': '`%(column)s` >= 0',
             }

+ 3 - 1
django/db/backends/mysql/introspection.py

@@ -47,7 +47,9 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
             elif field_type == 'SmallIntegerField':
                 return 'SmallAutoField'
         if description.is_unsigned:
-            if field_type == 'IntegerField':
+            if field_type == 'BigIntegerField':
+                return 'PositiveBigIntegerField'
+            elif field_type == 'IntegerField':
                 return 'PositiveIntegerField'
             elif field_type == 'SmallIntegerField':
                 return 'PositiveSmallIntegerField'

+ 2 - 0
django/db/backends/mysql/operations.py

@@ -15,6 +15,7 @@ class DatabaseOperations(BaseDatabaseOperations):
         **BaseDatabaseOperations.integer_field_ranges,
         'PositiveSmallIntegerField': (0, 65535),
         'PositiveIntegerField': (0, 4294967295),
+        'PositiveBigIntegerField': (0, 18446744073709551615),
     }
     cast_data_types = {
         'AutoField': 'signed integer',
@@ -26,6 +27,7 @@ class DatabaseOperations(BaseDatabaseOperations):
         'IntegerField': 'signed integer',
         'BigIntegerField': 'signed integer',
         'SmallIntegerField': 'signed integer',
+        'PositiveBigIntegerField': 'unsigned integer',
         'PositiveIntegerField': 'unsigned integer',
         'PositiveSmallIntegerField': 'unsigned integer',
     }

+ 2 - 0
django/db/backends/oracle/base.py

@@ -120,6 +120,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         'GenericIPAddressField': 'VARCHAR2(39)',
         'NullBooleanField': 'NUMBER(1)',
         'OneToOneField': 'NUMBER(11)',
+        'PositiveBigIntegerField': 'NUMBER(19)',
         'PositiveIntegerField': 'NUMBER(11)',
         'PositiveSmallIntegerField': 'NUMBER(11)',
         'SlugField': 'NVARCHAR2(%(max_length)s)',
@@ -133,6 +134,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
     data_type_check_constraints = {
         'BooleanField': '%(qn_column)s IN (0,1)',
         'NullBooleanField': '%(qn_column)s IN (0,1)',
+        'PositiveBigIntegerField': '%(qn_column)s >= 0',
         'PositiveIntegerField': '%(qn_column)s >= 0',
         'PositiveSmallIntegerField': '%(qn_column)s >= 0',
     }

+ 1 - 0
django/db/backends/oracle/operations.py

@@ -25,6 +25,7 @@ class DatabaseOperations(BaseDatabaseOperations):
         'SmallIntegerField': (-99999999999, 99999999999),
         'IntegerField': (-99999999999, 99999999999),
         'BigIntegerField': (-9999999999999999999, 9999999999999999999),
+        'PositiveBigIntegerField': (0, 9999999999999999999),
         'PositiveSmallIntegerField': (0, 99999999999),
         'PositiveIntegerField': (0, 99999999999),
         'SmallAutoField': (-99999, 99999),

+ 2 - 0
django/db/backends/oracle/utils.py

@@ -16,6 +16,7 @@ class InsertVar:
         'IntegerField': int,
         'BigIntegerField': int,
         'SmallIntegerField': int,
+        'PositiveBigIntegerField': int,
         'PositiveSmallIntegerField': int,
         'PositiveIntegerField': int,
         'FloatField': Database.NATIVE_FLOAT,
@@ -71,6 +72,7 @@ class BulkInsertMapper:
         'FloatField': NUMBER,
         'IntegerField': NUMBER,
         'NullBooleanField': NUMBER,
+        'PositiveBigIntegerField': NUMBER,
         'PositiveIntegerField': NUMBER,
         'PositiveSmallIntegerField': NUMBER,
         'SmallIntegerField': NUMBER,

+ 2 - 0
django/db/backends/postgresql/base.py

@@ -89,6 +89,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         'GenericIPAddressField': 'inet',
         'NullBooleanField': 'boolean',
         'OneToOneField': 'integer',
+        'PositiveBigIntegerField': 'bigint',
         'PositiveIntegerField': 'integer',
         'PositiveSmallIntegerField': 'smallint',
         'SlugField': 'varchar(%(max_length)s)',
@@ -99,6 +100,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         'UUIDField': 'uuid',
     }
     data_type_check_constraints = {
+        'PositiveBigIntegerField': '"%(column)s" >= 0',
         'PositiveIntegerField': '"%(column)s" >= 0',
         'PositiveSmallIntegerField': '"%(column)s" >= 0',
     }

+ 2 - 0
django/db/backends/sqlite3/base.py

@@ -102,6 +102,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         'GenericIPAddressField': 'char(39)',
         'NullBooleanField': 'bool',
         'OneToOneField': 'integer',
+        'PositiveBigIntegerField': 'bigint unsigned',
         'PositiveIntegerField': 'integer unsigned',
         'PositiveSmallIntegerField': 'smallint unsigned',
         'SlugField': 'varchar(%(max_length)s)',
@@ -112,6 +113,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         'UUIDField': 'char(32)',
     }
     data_type_check_constraints = {
+        'PositiveBigIntegerField': '"%(column)s" >= 0',
         'PositiveIntegerField': '"%(column)s" >= 0',
         'PositiveSmallIntegerField': '"%(column)s" >= 0',
     }

+ 1 - 0
django/db/backends/sqlite3/introspection.py

@@ -37,6 +37,7 @@ class FlexibleFieldLookupDict:
         'integer': 'IntegerField',
         'bigint': 'BigIntegerField',
         'integer unsigned': 'PositiveIntegerField',
+        'bigint unsigned': 'PositiveBigIntegerField',
         'decimal': 'DecimalField',
         'real': 'FloatField',
         'text': 'TextField',

+ 16 - 3
django/db/models/fields/__init__.py

@@ -33,9 +33,9 @@ __all__ = [
     'DateField', 'DateTimeField', 'DecimalField', 'DurationField',
     'EmailField', 'Empty', 'Field', 'FilePathField', 'FloatField',
     'GenericIPAddressField', 'IPAddressField', 'IntegerField', 'NOT_PROVIDED',
-    'NullBooleanField', 'PositiveIntegerField', 'PositiveSmallIntegerField',
-    'SlugField', 'SmallAutoField', 'SmallIntegerField', 'TextField',
-    'TimeField', 'URLField', 'UUIDField',
+    'NullBooleanField', 'PositiveBigIntegerField', 'PositiveIntegerField',
+    'PositiveSmallIntegerField', 'SlugField', 'SmallAutoField',
+    'SmallIntegerField', 'TextField', 'TimeField', 'URLField', 'UUIDField',
 ]
 
 
@@ -1955,6 +1955,19 @@ class PositiveIntegerRelDbTypeMixin:
             return IntegerField().db_type(connection=connection)
 
 
+class PositiveBigIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField):
+    description = _('Positive big integer')
+
+    def get_internal_type(self):
+        return 'PositiveBigIntegerField'
+
+    def formfield(self, **kwargs):
+        return super().formfield(**{
+            'min_value': 0,
+            **kwargs,
+        })
+
+
 class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField):
     description = _("Positive integer")
 

+ 11 - 0
docs/ref/models/fields.txt

@@ -1177,6 +1177,17 @@ values are stored as null.
 Like :class:`BooleanField` with ``null=True``. Use that instead of this field
 as it's likely to be deprecated in a future version of Django.
 
+``PositiveBigIntegerField``
+---------------------------
+
+.. class:: PositiveBigIntegerField(**options)
+
+.. versionadded:: 3.1
+
+Like a :class:`PositiveIntegerField`, but only allows values under a certain
+(database-dependent) point. Values from ``0`` to ``9223372036854775807`` are
+safe in all databases supported by Django.
+
 ``PositiveIntegerField``
 ------------------------
 

+ 5 - 0
docs/releases/3.1.txt

@@ -194,6 +194,11 @@ Models
   * ``TREE`` format on MySQL 8.0.16+,
   * ``analyze`` option on MySQL 8.0.18+ and MariaDB.
 
+* Added :class:`~django.db.models.PositiveBigIntegerField` which acts much like
+  a :class:`~django.db.models.PositiveIntegerField` except that it only allows
+  values under a certain (database-dependent) limit. Values from ``0`` to
+  ``9223372036854775807`` are safe in all databases supported by Django.
+
 Pagination
 ~~~~~~~~~~
 

+ 2 - 0
docs/topics/forms/modelforms.txt

@@ -106,6 +106,8 @@ Model field                         Form field
 
 :class:`NullBooleanField`           :class:`~django.forms.NullBooleanField`
 
+:class:`PositiveBigIntegerField`    :class:`~django.forms.IntegerField`
+
 :class:`PositiveIntegerField`       :class:`~django.forms.IntegerField`
 
 :class:`PositiveSmallIntegerField`  :class:`~django.forms.IntegerField`

+ 1 - 0
tests/bulk_create/models.py

@@ -76,6 +76,7 @@ class NullableFields(models.Model):
     integer_field = models.IntegerField(null=True, default=2)
     null_boolean_field = models.BooleanField(null=True, default=False)
     null_boolean_field_old = models.NullBooleanField(null=True, default=False)
+    positive_big_integer_field = models.PositiveBigIntegerField(null=True, default=2 ** 63 - 1)
     positive_integer_field = models.PositiveIntegerField(null=True, default=3)
     positive_small_integer_field = models.PositiveSmallIntegerField(null=True, default=4)
     small_integer_field = models.SmallIntegerField(null=True, default=5)

+ 1 - 0
tests/db_functions/comparison/test_cast.py

@@ -59,6 +59,7 @@ class CastTests(TestCase):
             models.IntegerField,
             models.BigIntegerField,
             models.SmallIntegerField,
+            models.PositiveBigIntegerField,
             models.PositiveIntegerField,
             models.PositiveSmallIntegerField,
         ):

+ 1 - 0
tests/expressions_case/models.py

@@ -29,6 +29,7 @@ class CaseTestModel(models.Model):
     null_boolean_old = models.NullBooleanField()
     positive_integer = models.PositiveIntegerField(null=True)
     positive_small_integer = models.PositiveSmallIntegerField(null=True)
+    positive_big_integer = models.PositiveSmallIntegerField(null=True)
     slug = models.SlugField(default='')
     small_integer = models.SmallIntegerField(null=True)
     text = models.TextField(default='')

+ 13 - 0
tests/expressions_case/tests.py

@@ -833,6 +833,19 @@ class CaseExpressionTests(TestCase):
             transform=attrgetter('integer', 'null_boolean_old')
         )
 
+    def test_update_positive_big_integer(self):
+        CaseTestModel.objects.update(
+            positive_big_integer=Case(
+                When(integer=1, then=1),
+                When(integer=2, then=2),
+            ),
+        )
+        self.assertQuerysetEqual(
+            CaseTestModel.objects.all().order_by('pk'),
+            [(1, 1), (2, 2), (3, None), (2, 2), (3, None), (3, None), (4, None)],
+            transform=attrgetter('integer', 'positive_big_integer')
+        )
+
     def test_update_positive_integer(self):
         CaseTestModel.objects.update(
             positive_integer=Case(

+ 7 - 0
tests/field_deconstruction/tests.py

@@ -453,6 +453,13 @@ class FieldDeconstructionTests(SimpleTestCase):
         self.assertEqual(args, [])
         self.assertEqual(kwargs, {})
 
+    def test_positive_big_integer_field(self):
+        field = models.PositiveBigIntegerField()
+        name, path, args, kwargs = field.deconstruct()
+        self.assertEqual(path, 'django.db.models.PositiveBigIntegerField')
+        self.assertEqual(args, [])
+        self.assertEqual(kwargs, {})
+
     def test_slug_field(self):
         field = models.SlugField()
         name, path, args, kwargs = field.deconstruct()

+ 1 - 0
tests/inspectdb/models.py

@@ -57,6 +57,7 @@ class ColumnTypes(models.Model):
     float_field = models.FloatField()
     int_field = models.IntegerField()
     gen_ip_address_field = models.GenericIPAddressField(protocol="ipv4")
+    pos_big_int_field = models.PositiveBigIntegerField()
     pos_int_field = models.PositiveIntegerField()
     pos_small_int_field = models.PositiveSmallIntegerField()
     slug_field = models.SlugField()

+ 8 - 0
tests/inspectdb/tests.py

@@ -118,11 +118,19 @@ class InspectDBTestCase(TestCase):
             assertFieldType('pos_int_field', "models.IntegerField()")
 
         if connection.features.can_introspect_positive_integer_field:
+            if connection.features.can_introspect_big_integer_field:
+                assertFieldType('pos_big_int_field', 'models.PositiveBigIntegerField()')
+            else:
+                assertFieldType('pos_big_int_field', 'models.PositiveIntegerField()')
             if connection.features.can_introspect_small_integer_field:
                 assertFieldType('pos_small_int_field', "models.PositiveSmallIntegerField()")
             else:
                 assertFieldType('pos_small_int_field', "models.PositiveIntegerField()")
         else:
+            if connection.features.can_introspect_big_integer_field:
+                assertFieldType('pos_big_int_field', 'models.BigIntegerField()')
+            else:
+                assertFieldType('pos_big_int_field', 'models.IntegerField()')
             if connection.features.can_introspect_small_integer_field:
                 assertFieldType('pos_small_int_field', "models.SmallIntegerField()")
             else:

+ 1 - 0
tests/invalid_models_tests/test_ordinary_fields.py

@@ -689,6 +689,7 @@ class IntegerFieldTests(SimpleTestCase):
             biginteger = models.BigIntegerField(max_length=2)
             smallinteger = models.SmallIntegerField(max_length=2)
             positiveinteger = models.PositiveIntegerField(max_length=2)
+            positivebiginteger = models.PositiveBigIntegerField(max_length=2)
             positivesmallinteger = models.PositiveSmallIntegerField(max_length=2)
 
         for field in Model._meta.get_fields():

+ 4 - 0
tests/model_fields/models.py

@@ -117,6 +117,10 @@ class BigIntegerModel(models.Model):
     null_value = models.BigIntegerField(null=True, blank=True)
 
 
+class PositiveBigIntegerModel(models.Model):
+    value = models.PositiveBigIntegerField()
+
+
 class PositiveSmallIntegerModel(models.Model):
     value = models.PositiveSmallIntegerField()
 

+ 7 - 2
tests/model_fields/test_integerfield.py

@@ -6,8 +6,8 @@ from django.db import IntegrityError, connection, models
 from django.test import SimpleTestCase, TestCase
 
 from .models import (
-    BigIntegerModel, IntegerModel, PositiveIntegerModel,
-    PositiveSmallIntegerModel, SmallIntegerModel,
+    BigIntegerModel, IntegerModel, PositiveBigIntegerModel,
+    PositiveIntegerModel, PositiveSmallIntegerModel, SmallIntegerModel,
 )
 
 
@@ -182,6 +182,11 @@ class PositiveIntegerFieldTests(IntegerFieldTests):
             p.save()
 
 
+class PositiveBigIntegerFieldTests(IntegerFieldTests):
+    model = PositiveBigIntegerModel
+    documented_range = (0, 9223372036854775807)
+
+
 class ValidationTests(SimpleTestCase):
 
     class Choices(models.IntegerChoices):

+ 7 - 3
tests/model_fields/test_promises.py

@@ -4,9 +4,9 @@ from decimal import Decimal
 from django.db.models.fields import (
     AutoField, BinaryField, BooleanField, CharField, DateField, DateTimeField,
     DecimalField, EmailField, FilePathField, FloatField, GenericIPAddressField,
-    IntegerField, IPAddressField, NullBooleanField, PositiveIntegerField,
-    PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField,
-    TimeField, URLField,
+    IntegerField, IPAddressField, NullBooleanField, PositiveBigIntegerField,
+    PositiveIntegerField, PositiveSmallIntegerField, SlugField,
+    SmallIntegerField, TextField, TimeField, URLField,
 )
 from django.db.models.fields.files import FileField, ImageField
 from django.test import SimpleTestCase
@@ -97,6 +97,10 @@ class PromiseTest(SimpleTestCase):
         lazy_func = lazy(lambda: 1, int)
         self.assertIsInstance(PositiveSmallIntegerField().get_prep_value(lazy_func()), int)
 
+    def test_PositiveBigIntegerField(self):
+        lazy_func = lazy(lambda: 1, int)
+        self.assertIsInstance(PositiveBigIntegerField().get_prep_value(lazy_func()), int)
+
     def test_SlugField(self):
         lazy_func = lazy(lambda: 'slug', str)
         self.assertIsInstance(SlugField().get_prep_value(lazy_func()), str)

+ 4 - 0
tests/serializers/models/data.py

@@ -72,6 +72,10 @@ class NullBooleanData(models.Model):
     data = models.NullBooleanField(null=True)
 
 
+class PositiveBigIntegerData(models.Model):
+    data = models.PositiveBigIntegerField(null=True)
+
+
 class PositiveIntegerData(models.Model):
     data = models.PositiveIntegerField(null=True)
 

+ 4 - 2
tests/serializers/test_data.py

@@ -23,8 +23,8 @@ from .models import (
     GenericData, GenericIPAddressData, GenericIPAddressPKData,
     InheritAbstractModel, InheritBaseModel, IntegerData, IntegerPKData,
     Intermediate, LengthModel, M2MData, M2MIntermediateData, M2MSelfData,
-    ModifyingSaveData, NullBooleanData, O2OData, PositiveIntegerData,
-    PositiveIntegerPKData, PositiveSmallIntegerData,
+    ModifyingSaveData, NullBooleanData, O2OData, PositiveBigIntegerData,
+    PositiveIntegerData, PositiveIntegerPKData, PositiveSmallIntegerData,
     PositiveSmallIntegerPKData, SlugData, SlugPKData, SmallData, SmallPKData,
     Tag, TextData, TimeData, UniqueAnchor, UUIDData,
 )
@@ -241,6 +241,8 @@ test_data = [
     (data_obj, 100, NullBooleanData, True),
     (data_obj, 101, NullBooleanData, False),
     (data_obj, 102, NullBooleanData, None),
+    (data_obj, 110, PositiveBigIntegerData, 9223372036854775807),
+    (data_obj, 111, PositiveBigIntegerData, None),
     (data_obj, 120, PositiveIntegerData, 123456789),
     (data_obj, 121, PositiveIntegerData, None),
     (data_obj, 130, PositiveSmallIntegerData, 12),