From 5bd63663a9754ef783aa21402f534fe6ed45ef57 Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Thu, 17 Dec 2009 15:10:38 +0000 Subject: [PATCH] =?UTF-8?q?Fixed=20#399:=20Added=20big=20integer=20field.?= =?UTF-8?q?=20Thanks=20to=20Tom=C3=A1=C5=A1=20Kope=C4=8Dek=20for=20persist?= =?UTF-8?q?ently=20maintaining=20a=20patch=20for=20this.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: http://code.djangoproject.com/svn/django/trunk@11887 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/contrib/admin/options.py | 17 +++++----- django/db/backends/mysql/creation.py | 1 + django/db/backends/mysql/introspection.py | 2 +- django/db/backends/oracle/creation.py | 1 + django/db/backends/oracle/introspection.py | 5 ++- django/db/backends/postgresql/creation.py | 1 + .../db/backends/postgresql/introspection.py | 1 + django/db/backends/sqlite3/creation.py | 1 + django/db/backends/sqlite3/introspection.py | 1 + django/db/models/fields/__init__.py | 13 +++++++ docs/ref/models/fields.txt | 12 +++++++ docs/topics/forms/modelforms.txt | 8 +++++ tests/modeltests/model_forms/models.py | 34 +++++++++++++++---- tests/regressiontests/introspection/models.py | 1 + tests/regressiontests/introspection/tests.py | 2 +- tests/regressiontests/model_fields/models.py | 3 ++ tests/regressiontests/model_fields/tests.py | 31 ++++++++++++++++- .../serializers_regress/models.py | 3 ++ .../serializers_regress/tests.py | 5 +++ 20 files changed, 125 insertions(+), 18 deletions(-) diff --git a/AUTHORS b/AUTHORS index bf7ee55533..c8d91f77c7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -246,6 +246,7 @@ answer newbie questions, and generally made Django that much better: Cameron Knight (ckknight) Nena Kojadin Igor Kolar + Tomáš Kopeček Gasper Koren Martin Kosír Arthur Koziel diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 2d04bbbc4b..c9d4f33884 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -42,14 +42,15 @@ FORMFIELD_FOR_DBFIELD_DEFAULTS = { 'form_class': forms.SplitDateTimeField, 'widget': widgets.AdminSplitDateTime }, - models.DateField: {'widget': widgets.AdminDateWidget}, - models.TimeField: {'widget': widgets.AdminTimeWidget}, - models.TextField: {'widget': widgets.AdminTextareaWidget}, - models.URLField: {'widget': widgets.AdminURLFieldWidget}, - models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget}, - models.CharField: {'widget': widgets.AdminTextInputWidget}, - models.ImageField: {'widget': widgets.AdminFileWidget}, - models.FileField: {'widget': widgets.AdminFileWidget}, + models.DateField: {'widget': widgets.AdminDateWidget}, + models.TimeField: {'widget': widgets.AdminTimeWidget}, + models.TextField: {'widget': widgets.AdminTextareaWidget}, + models.URLField: {'widget': widgets.AdminURLFieldWidget}, + models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget}, + models.BigIntegerField: {'widget': widgets.AdminIntegerFieldWidget}, + models.CharField: {'widget': widgets.AdminTextInputWidget}, + models.ImageField: {'widget': widgets.AdminFileWidget}, + models.FileField: {'widget': widgets.AdminFileWidget}, } diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py index 0611e01643..063ba4c712 100644 --- a/django/db/backends/mysql/creation.py +++ b/django/db/backends/mysql/creation.py @@ -18,6 +18,7 @@ class DatabaseCreation(BaseDatabaseCreation): 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', + 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index d8d59b73a1..9e1518b06e 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -17,7 +17,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): FIELD_TYPE.FLOAT: 'FloatField', FIELD_TYPE.INT24: 'IntegerField', FIELD_TYPE.LONG: 'IntegerField', - FIELD_TYPE.LONGLONG: 'IntegerField', + FIELD_TYPE.LONGLONG: 'BigIntegerField', FIELD_TYPE.SHORT: 'IntegerField', FIELD_TYPE.STRING: 'CharField', FIELD_TYPE.TIMESTAMP: 'DateTimeField', diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index 43d8760fca..86d84ca5a6 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -27,6 +27,7 @@ class DatabaseCreation(BaseDatabaseCreation): 'FilePathField': 'NVARCHAR2(%(max_length)s)', 'FloatField': 'DOUBLE PRECISION', 'IntegerField': 'NUMBER(11)', + 'BigIntegerField': 'NUMBER(19)', 'IPAddressField': 'VARCHAR2(15)', 'NullBooleanField': 'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))', 'OneToOneField': 'NUMBER(11)', diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py index 0b4f61a360..0dd0304302 100644 --- a/django/db/backends/oracle/introspection.py +++ b/django/db/backends/oracle/introspection.py @@ -29,7 +29,10 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): def get_field_type(self, data_type, description): # If it's a NUMBER with scale == 0, consider it an IntegerField if data_type == cx_Oracle.NUMBER and description[5] == 0: - return 'IntegerField' + if description[4] > 11: + return 'BigIntegerField' + else: + return 'IntegerField' else: return super(DatabaseIntrospection, self).get_field_type( data_type, description) diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py index 8f329feca4..0940c7eb71 100644 --- a/django/db/backends/postgresql/creation.py +++ b/django/db/backends/postgresql/creation.py @@ -18,6 +18,7 @@ class DatabaseCreation(BaseDatabaseCreation): 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', + 'BigIntegerField': 'bigint', 'IPAddressField': 'inet', 'NullBooleanField': 'boolean', 'OneToOneField': 'integer', diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py index a6cafcd949..534bf41d46 100644 --- a/django/db/backends/postgresql/introspection.py +++ b/django/db/backends/postgresql/introspection.py @@ -4,6 +4,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): # Maps type codes to Django Field types. data_types_reverse = { 16: 'BooleanField', + 20: 'BigIntegerField', 21: 'SmallIntegerField', 23: 'IntegerField', 25: 'TextField', diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index e9e0a94634..fb27d22cf3 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -19,6 +19,7 @@ class DatabaseCreation(BaseDatabaseCreation): 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'real', 'IntegerField': 'integer', + 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py index b58e6a7faf..ce64908873 100644 --- a/django/db/backends/sqlite3/introspection.py +++ b/django/db/backends/sqlite3/introspection.py @@ -16,6 +16,7 @@ class FlexibleFieldLookupDict: 'smallinteger': 'SmallIntegerField', 'int': 'IntegerField', 'integer': 'IntegerField', + 'bigint': 'BigIntegerField', 'integer unsigned': 'PositiveIntegerField', 'decimal': 'DecimalField', 'real': 'FloatField', diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index b5fd30e47c..ea10b801f6 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -732,6 +732,19 @@ class IntegerField(Field): defaults.update(kwargs) return super(IntegerField, self).formfield(**defaults) +class BigIntegerField(IntegerField): + empty_strings_allowed = False + description = ugettext_lazy("Big (8 byte) integer") + MAX_BIGINT = 9223372036854775807 + def get_internal_type(self): + return "BigIntegerField" + + def formfield(self, **kwargs): + defaults = {'min_value': -BigIntegerField.MAX_BIGINT - 1, + 'max_value': BigIntegerField.MAX_BIGINT} + defaults.update(kwargs) + return super(BigIntegerField, self).formfield(**defaults) + class IPAddressField(Field): empty_strings_allowed = False description = ugettext_lazy("IP address") diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 0cb5be4b92..cd8cdc7a01 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -299,6 +299,18 @@ according to available IDs. You usually won't need to use this directly; a primary key field will automatically be added to your model if you don't specify otherwise. See :ref:`automatic-primary-key-fields`. +``BigIntegerField`` +------------------- + +.. versionadded:: 1.2 + +.. class:: BigIntegerField([**options]) + +A 64 bit integer, much like an :class:`IntegerField` except that it is +guaranteed to fit numbers from -9223372036854775808 to 9223372036854775807. The +admin represents this as an ```` (a single-line input). + + ``BooleanField`` ---------------- diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index c5aa9c8a77..024479508a 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -46,6 +46,10 @@ the full list of conversions: =============================== ======================================== ``AutoField`` Not represented in the form + ``BigIntegerField`` ``IntegerField`` with ``min_value`` set + to -9223372036854775808 and ``max_value`` + set to 9223372036854775807. + ``BooleanField`` ``BooleanField`` ``CharField`` ``CharField`` with ``max_length`` set to @@ -108,6 +112,10 @@ the full list of conversions: The ``FloatField`` form field and ``DecimalField`` model and form fields are new in Django 1.0. +.. versionadded:: 1.2 + The ``BigIntegerField`` is new in Django 1.2. + + As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field types are special cases: diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 0fd24c18ad..c94e6d3e95 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -13,12 +13,6 @@ import tempfile from django.db import models from django.core.files.storage import FileSystemStorage -# Python 2.3 doesn't have sorted() -try: - sorted -except NameError: - from django.utils.itercompat import sorted - temp_storage_dir = tempfile.mkdtemp() temp_storage = FileSystemStorage(temp_storage_dir) @@ -201,6 +195,12 @@ class Post(models.Model): def __unicode__(self): return self.name +class BigInt(models.Model): + biggie = models.BigIntegerField() + + def __unicode__(self): + return unicode(self.biggie) + __test__ = {'API_TESTS': """ >>> from django import forms >>> from django.forms.models import ModelForm, model_to_dict @@ -1145,6 +1145,28 @@ True # Delete the current file since this is not done by Django. >>> instance.file.delete() >>> instance.delete() + +# BigIntegerField ################################################################ +>>> class BigIntForm(forms.ModelForm): +... class Meta: +... model = BigInt +... +>>> bif = BigIntForm({'biggie': '-9223372036854775808'}) +>>> bif.is_valid() +True +>>> bif = BigIntForm({'biggie': '-9223372036854775809'}) +>>> bif.is_valid() +False +>>> bif.errors +{'biggie': [u'Ensure this value is greater than or equal to -9223372036854775808.']} +>>> bif = BigIntForm({'biggie': '9223372036854775807'}) +>>> bif.is_valid() +True +>>> bif = BigIntForm({'biggie': '9223372036854775808'}) +>>> bif.is_valid() +False +>>> bif.errors +{'biggie': [u'Ensure this value is less than or equal to 9223372036854775807.']} """} if test_images: diff --git a/tests/regressiontests/introspection/models.py b/tests/regressiontests/introspection/models.py index 3227067bd8..ef485e3a3c 100644 --- a/tests/regressiontests/introspection/models.py +++ b/tests/regressiontests/introspection/models.py @@ -4,6 +4,7 @@ class Reporter(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) email = models.EmailField() + facebook_user_id = models.BigIntegerField() def __unicode__(self): return u"%s %s" % (self.first_name, self.last_name) diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py index 1454e1e3e5..de6381fc29 100644 --- a/tests/regressiontests/introspection/tests.py +++ b/tests/regressiontests/introspection/tests.py @@ -77,7 +77,7 @@ class IntrospectionTests(TestCase): cursor = connection.cursor() desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table) self.assertEqual([datatype(r[1], r) for r in desc], - ['IntegerField', 'CharField', 'CharField', 'CharField']) + ['IntegerField', 'CharField', 'CharField', 'CharField', 'BigIntegerField']) # Regression test for #9991 - 'real' types in postgres if settings.DATABASE_ENGINE.startswith('postgresql'): diff --git a/tests/regressiontests/model_fields/models.py b/tests/regressiontests/model_fields/models.py index 81e6c22f09..ab841ce409 100644 --- a/tests/regressiontests/model_fields/models.py +++ b/tests/regressiontests/model_fields/models.py @@ -51,6 +51,9 @@ class BigD(models.Model): class BigS(models.Model): s = models.SlugField(max_length=255) +class BigInt(models.Model): + value = models.BigIntegerField() + null_value = models.BigIntegerField(null = True, blank = True) ############################################################################### # ImageField diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index 7a6fee5a2a..b8d0f7ef0e 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -6,7 +6,7 @@ from django import forms from django.db import models from django.core.exceptions import ValidationError -from models import Foo, Bar, Whiz, BigD, BigS, Image +from models import Foo, Bar, Whiz, BigD, BigS, Image, BigInt try: from decimal import Decimal @@ -144,3 +144,32 @@ class SlugFieldTests(django.test.TestCase): bs = BigS.objects.create(s = 'slug'*50) bs = BigS.objects.get(pk=bs.pk) self.assertEqual(bs.s, 'slug'*50) + +class BigIntegerFieldTests(django.test.TestCase): + def test_limits(self): + # Ensure that values that are right at the limits can be saved + # and then retrieved without corruption. + maxval = 9223372036854775807 + minval = -maxval - 1 + BigInt.objects.create(value=maxval) + qs = BigInt.objects.filter(value__gte=maxval) + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].value, maxval) + BigInt.objects.create(value=minval) + qs = BigInt.objects.filter(value__lte=minval) + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].value, minval) + + def test_types(self): + b = BigInt(value = 0) + self.assertTrue(isinstance(b.value, (int, long))) + b.save() + self.assertTrue(isinstance(b.value, (int, long))) + b = BigInt.objects.all()[0] + self.assertTrue(isinstance(b.value, (int, long))) + + def test_coercing(self): + BigInt.objects.create(value ='10') + b = BigInt.objects.get(value = '10') + self.assertEqual(b.value, 10) + diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py index 313ed8fc3a..ccd850326d 100644 --- a/tests/regressiontests/serializers_regress/models.py +++ b/tests/regressiontests/serializers_regress/models.py @@ -43,6 +43,9 @@ class FloatData(models.Model): class IntegerData(models.Model): data = models.IntegerField(null=True) +class BigIntegerData(models.Model): + data = models.BigIntegerField(null=True) + # class ImageData(models.Model): # data = models.ImageField(null=True) diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py index de7ddcc9f7..f0781d78b1 100644 --- a/tests/regressiontests/serializers_regress/tests.py +++ b/tests/regressiontests/serializers_regress/tests.py @@ -321,6 +321,11 @@ The end."""), (inherited_obj, 900, InheritAbstractModel, {'child_data':37,'parent_data':42}), (inherited_obj, 910, ExplicitInheritBaseModel, {'child_data':37,'parent_data':42}), (inherited_obj, 920, InheritBaseModel, {'child_data':37,'parent_data':42}), + + (data_obj, 1000, BigIntegerData, 9223372036854775807), + (data_obj, 1001, BigIntegerData, -9223372036854775808), + (data_obj, 1002, BigIntegerData, 0), + (data_obj, 1003, BigIntegerData, None), ] # Because Oracle treats the empty string as NULL, Oracle is expected to fail