summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Sosnovskiy <alecs.box@gmail.com>2015-07-02 11:43:15 +0300
committerTim Graham <timograham@gmail.com>2015-12-25 20:01:31 -0500
commit2a7ce34600d0f879e93c9a5e02215948ed3bb6ac (patch)
treed1a4770772b452513e1d64c090dc15ae287afe70
parenta1d0c60fa05cbad2e5a25ec37e0afaf1b84c9302 (diff)
Fixed #14286 -- Added models.BigAutoField.
-rw-r--r--django/contrib/gis/utils/layermapping.py4
-rw-r--r--django/db/backends/base/schema.py2
-rw-r--r--django/db/backends/mysql/base.py1
-rw-r--r--django/db/backends/mysql/introspection.py8
-rw-r--r--django/db/backends/oracle/base.py1
-rw-r--r--django/db/backends/postgresql/base.py1
-rw-r--r--django/db/backends/postgresql/introspection.py7
-rw-r--r--django/db/backends/postgresql/schema.py5
-rw-r--r--django/db/backends/sqlite3/base.py2
-rw-r--r--django/db/models/fields/__init__.py26
-rw-r--r--docs/ref/models/fields.txt12
-rw-r--r--docs/releases/1.10.txt4
-rw-r--r--docs/topics/forms/modelforms.txt2
-rw-r--r--tests/introspection/models.py18
-rw-r--r--tests/introspection/tests.py8
-rw-r--r--tests/many_to_many/models.py10
-rw-r--r--tests/many_to_one/models.py18
-rw-r--r--tests/many_to_one/tests.py13
-rw-r--r--tests/migrations/test_operations.py138
19 files changed, 260 insertions, 20 deletions
diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py
index 665d2e3cc6..22d7dd2579 100644
--- a/django/contrib/gis/utils/layermapping.py
+++ b/django/contrib/gis/utils/layermapping.py
@@ -15,7 +15,8 @@ from django.contrib.gis.gdal import (
SpatialReference,
)
from django.contrib.gis.gdal.field import (
- OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime,
+ OFTDate, OFTDateTime, OFTInteger, OFTInteger64, OFTReal, OFTString,
+ OFTTime,
)
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
from django.db import connections, models, router, transaction
@@ -60,6 +61,7 @@ class LayerMapping(object):
# counterparts.
FIELD_TYPES = {
models.AutoField: OFTInteger,
+ models.BigAutoField: OFTInteger64,
models.IntegerField: (OFTInteger, OFTReal, OFTString),
models.FloatField: (OFTInteger, OFTReal),
models.DateField: OFTDate,
diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index 2aa486c91e..191478a6a3 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -261,7 +261,7 @@ class BaseDatabaseSchemaEditor(object):
definition,
))
# Autoincrement SQL (for backends with post table definition variant)
- if field.get_internal_type() == "AutoField":
+ if field.get_internal_type() in ("AutoField", "BigAutoField"):
autoinc_sql = self.connection.ops.autoinc_sql(model._meta.db_table, field.column)
if autoinc_sql:
self.deferred_sql.extend(autoinc_sql)
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index 6fbf15e42e..f674232ab3 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -153,6 +153,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# If a column type is set to None, it won't be included in the output.
_data_types = {
'AutoField': 'integer AUTO_INCREMENT',
+ 'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
index 0a22357cf9..3d0f6f3689 100644
--- a/django/db/backends/mysql/introspection.py
+++ b/django/db/backends/mysql/introspection.py
@@ -38,8 +38,12 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
def get_field_type(self, data_type, description):
field_type = super(DatabaseIntrospection, self).get_field_type(data_type, description)
- if field_type == 'IntegerField' and 'auto_increment' in description.extra:
- return 'AutoField'
+ if 'auto_increment' in description.extra:
+ if field_type == 'IntegerField':
+ return 'AutoField'
+ elif field_type == 'BigIntegerField':
+ return 'BigAutoField'
+
return field_type
def get_table_list(self, cursor):
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index d286ad0fff..ef93300737 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -92,6 +92,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# output (the "qn_" prefix is stripped before the lookup is performed.
data_types = {
'AutoField': 'NUMBER(11)',
+ 'BigAutoField': 'NUMBER(19)',
'BinaryField': 'BLOB',
'BooleanField': 'NUMBER(1)',
'CharField': 'NVARCHAR2(%(max_length)s)',
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index 383601e477..7822c66327 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -72,6 +72,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# If a column type is set to None, it won't be included in the output.
data_types = {
'AutoField': 'serial',
+ 'BigAutoField': 'bigserial',
'BinaryField': 'bytea',
'BooleanField': 'boolean',
'CharField': 'varchar(%(max_length)s)',
diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
index 9b3e9074b2..90b7090464 100644
--- a/django/db/backends/postgresql/introspection.py
+++ b/django/db/backends/postgresql/introspection.py
@@ -46,8 +46,11 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
def get_field_type(self, data_type, description):
field_type = super(DatabaseIntrospection, self).get_field_type(data_type, description)
- if field_type == 'IntegerField' and description.default and 'nextval' in description.default:
- return 'AutoField'
+ if description.default and 'nextval' in description.default:
+ if field_type == 'IntegerField':
+ return 'AutoField'
+ elif field_type == 'BigIntegerField':
+ return 'BigAutoField'
return field_type
def get_table_list(self, cursor):
diff --git a/django/db/backends/postgresql/schema.py b/django/db/backends/postgresql/schema.py
index cc31aadf0a..42d01f002d 100644
--- a/django/db/backends/postgresql/schema.py
+++ b/django/db/backends/postgresql/schema.py
@@ -54,14 +54,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
"""
Makes ALTER TYPE with SERIAL make sense.
"""
- if new_type.lower() == "serial":
+ if new_type.lower() in ("serial", "bigserial"):
column = new_field.column
sequence_name = "%s_%s_seq" % (table, column)
+ col_type = "integer" if new_type.lower() == "serial" else "bigint"
return (
(
self.sql_alter_column_type % {
"column": self.quote_name(column),
- "type": "integer",
+ "type": col_type,
},
[],
),
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index da9a21cbe9..55b97e8bd2 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -93,6 +93,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# schema inspection is more useful.
data_types = {
'AutoField': 'integer',
+ 'BigAutoField': 'integer',
'BinaryField': 'BLOB',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
@@ -120,6 +121,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
}
data_types_suffix = {
'AutoField': 'AUTOINCREMENT',
+ 'BigAutoField': 'AUTOINCREMENT',
}
# SQLite requires LIKE statements to include an ESCAPE clause if the value
# being escaped has a percent or underscore in it.
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index d16c58d29b..7a2dfdbd7d 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -42,14 +42,14 @@ from django.utils.translation import ugettext_lazy as _
# Avoid "TypeError: Item in ``from list'' not a string" -- unicode_literals
# makes these strings unicode
__all__ = [str(x) for x in (
- 'AutoField', 'BLANK_CHOICE_DASH', 'BigIntegerField', 'BinaryField',
- 'BooleanField', 'CharField', 'CommaSeparatedIntegerField', 'DateField',
- 'DateTimeField', 'DecimalField', 'DurationField', 'EmailField', 'Empty',
- 'Field', 'FieldDoesNotExist', 'FilePathField', 'FloatField',
- 'GenericIPAddressField', 'IPAddressField', 'IntegerField', 'NOT_PROVIDED',
- 'NullBooleanField', 'PositiveIntegerField', 'PositiveSmallIntegerField',
- 'SlugField', 'SmallIntegerField', 'TextField', 'TimeField', 'URLField',
- 'UUIDField',
+ 'AutoField', 'BLANK_CHOICE_DASH', 'BigAutoField', 'BigIntegerField',
+ 'BinaryField', 'BooleanField', 'CharField', 'CommaSeparatedIntegerField',
+ 'DateField', 'DateTimeField', 'DecimalField', 'DurationField',
+ 'EmailField', 'Empty', 'Field', 'FieldDoesNotExist', 'FilePathField',
+ 'FloatField', 'GenericIPAddressField', 'IPAddressField', 'IntegerField',
+ 'NOT_PROVIDED', 'NullBooleanField', 'PositiveIntegerField',
+ 'PositiveSmallIntegerField', 'SlugField', 'SmallIntegerField', 'TextField',
+ 'TimeField', 'URLField', 'UUIDField',
)]
@@ -997,6 +997,16 @@ class AutoField(Field):
return None
+class BigAutoField(AutoField):
+ description = _("Big (8 byte) integer")
+
+ def get_internal_type(self):
+ return "BigAutoField"
+
+ def rel_db_type(self, connection):
+ return BigIntegerField().db_type(connection=connection)
+
+
class BooleanField(Field):
empty_strings_allowed = False
default_error_messages = {
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index 6320f20e24..0a33e96122 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -388,12 +388,22 @@ 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`.
+``BigAutoField``
+----------------
+
+.. class:: BigAutoField(**options)
+
+.. versionadded:: 1.10
+
+A 64-bit integer, much like an :class:`AutoField` except that it is
+guaranteed to fit numbers from ``1`` to ``9223372036854775807``.
+
``BigIntegerField``
-------------------
.. class:: BigIntegerField(**options)
-A 64 bit integer, much like an :class:`IntegerField` except that it is
+A 64-bit integer, much like an :class:`IntegerField` except that it is
guaranteed to fit numbers from ``-9223372036854775808`` to
``9223372036854775807``. The default form widget for this field is a
:class:`~django.forms.TextInput`.
diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt
index 8564c8286d..077a27cea1 100644
--- a/docs/releases/1.10.txt
+++ b/docs/releases/1.10.txt
@@ -242,6 +242,10 @@ Models
:class:`~django.db.models.Func`. This attribute can be used to set the number
of arguments the function accepts.
+* Added :class:`~django.db.models.BigAutoField` which acts much like an
+ :class:`~django.db.models.AutoField` except that it is guaranteed
+ to fit numbers from ``1`` to ``9223372036854775807``.
+
Requests and Responses
^^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index 42594e7002..46a1cbe774 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -56,6 +56,8 @@ Model field Form field
=================================== ==================================================
:class:`AutoField` Not represented in the form
+:class:`BigAutoField` Not represented in the form
+
:class:`BigIntegerField` :class:`~django.forms.IntegerField` with
``min_value`` set to -9223372036854775808
and ``max_value`` set to 9223372036854775807.
diff --git a/tests/introspection/models.py b/tests/introspection/models.py
index 9ad52b8f8d..4d961b20be 100644
--- a/tests/introspection/models.py
+++ b/tests/introspection/models.py
@@ -5,6 +5,24 @@ from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
+class City(models.Model):
+ id = models.BigAutoField(primary_key=True)
+ name = models.CharField(max_length=50)
+
+ def __str__(self):
+ return self.name
+
+
+@python_2_unicode_compatible
+class District(models.Model):
+ city = models.ForeignKey(City, models.CASCADE)
+ name = models.CharField(max_length=50)
+
+ def __str__(self):
+ return self.name
+
+
+@python_2_unicode_compatible
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
diff --git a/tests/introspection/tests.py b/tests/introspection/tests.py
index 3cd00060db..9ce5a3f0f6 100644
--- a/tests/introspection/tests.py
+++ b/tests/introspection/tests.py
@@ -6,7 +6,7 @@ from django.db import connection
from django.db.utils import DatabaseError
from django.test import TransactionTestCase, mock, skipUnlessDBFeature
-from .models import Article, Reporter
+from .models import Article, City, Reporter
class IntrospectionTests(TransactionTestCase):
@@ -103,6 +103,12 @@ class IntrospectionTests(TransactionTestCase):
[False, nullable_by_backend, nullable_by_backend, nullable_by_backend, True, True, False]
)
+ @skipUnlessDBFeature('can_introspect_autofield')
+ def test_bigautofield(self):
+ with connection.cursor() as cursor:
+ desc = connection.introspection.get_table_description(cursor, City._meta.db_table)
+ self.assertIn('BigAutoField', [datatype(r[1], r) for r in desc])
+
# Regression test for #9991 - 'real' types in postgres
@skipUnlessDBFeature('has_real_datatype')
def test_postgresql_real_type(self):
diff --git a/tests/many_to_many/models.py b/tests/many_to_many/models.py
index 5688c853d6..3e4cdd2e70 100644
--- a/tests/many_to_many/models.py
+++ b/tests/many_to_many/models.py
@@ -24,11 +24,21 @@ class Publication(models.Model):
@python_2_unicode_compatible
+class Tag(models.Model):
+ id = models.BigAutoField(primary_key=True)
+ name = models.CharField(max_length=50)
+
+ def __str__(self):
+ return self.name
+
+
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
# Assign a unicode string as name to make sure the intermediary model is
# correctly created. Refs #20207
publications = models.ManyToManyField(Publication, name='publications')
+ tags = models.ManyToManyField(Tag, related_name='tags')
def __str__(self):
return self.headline
diff --git a/tests/many_to_one/models.py b/tests/many_to_one/models.py
index 57fc7a617b..05491e99f1 100644
--- a/tests/many_to_one/models.py
+++ b/tests/many_to_one/models.py
@@ -32,6 +32,24 @@ class Article(models.Model):
ordering = ('headline',)
+@python_2_unicode_compatible
+class City(models.Model):
+ id = models.BigAutoField(primary_key=True)
+ name = models.CharField(max_length=50)
+
+ def __str__(self):
+ return self.name
+
+
+@python_2_unicode_compatible
+class District(models.Model):
+ city = models.ForeignKey(City, models.CASCADE)
+ name = models.CharField(max_length=50)
+
+ def __str__(self):
+ return self.name
+
+
# If ticket #1578 ever slips back in, these models will not be able to be
# created (the field names being lower-cased versions of their opposite
# classes is important here).
diff --git a/tests/many_to_one/tests.py b/tests/many_to_one/tests.py
index 69bba0993d..79caec7177 100644
--- a/tests/many_to_one/tests.py
+++ b/tests/many_to_one/tests.py
@@ -9,8 +9,8 @@ from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.translation import ugettext_lazy
from .models import (
- Article, Category, Child, First, Parent, Record, Relation, Reporter,
- School, Student, Third, ToFieldChild,
+ Article, Category, Child, City, District, First, Parent, Record, Relation,
+ Reporter, School, Student, Third, ToFieldChild,
)
@@ -569,6 +569,15 @@ class ManyToOneTests(TestCase):
self.assertIsNot(c.parent, p)
self.assertEqual(c.parent, p)
+ def test_fk_to_bigautofield(self):
+ ch = City.objects.create(name='Chicago')
+ District.objects.create(city=ch, name='Far South')
+ District.objects.create(city=ch, name='North')
+
+ ny = City.objects.create(name='New York', id=2 ** 33)
+ District.objects.create(city=ny, name='Brooklyn')
+ District.objects.create(city=ny, name='Manhattan')
+
def test_multiple_foreignkeys(self):
# Test of multiple ForeignKeys to the same model (bug #7125).
c1 = Category.objects.create(name='First')
diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py
index b624a73392..de32353aa4 100644
--- a/tests/migrations/test_operations.py
+++ b/tests/migrations/test_operations.py
@@ -1806,6 +1806,144 @@ class OperationTests(OperationTestBase):
create_old_man.state_forwards("test_books", new_state)
create_old_man.database_forwards("test_books", editor, project_state, new_state)
+ def test_model_with_bigautofield(self):
+ """
+ A model with BigAutoField can be created.
+ """
+ def create_data(models, schema_editor):
+ Author = models.get_model("test_author", "Author")
+ Book = models.get_model("test_book", "Book")
+ author1 = Author.objects.create(name="Hemingway")
+ Book.objects.create(title="Old Man and The Sea", author=author1)
+ Book.objects.create(id=2 ** 33, title="A farewell to arms", author=author1)
+
+ author2 = Author.objects.create(id=2 ** 33, name="Remarque")
+ Book.objects.create(title="All quiet on the western front", author=author2)
+ Book.objects.create(title="Arc de Triomphe", author=author2)
+
+ create_author = migrations.CreateModel(
+ "Author",
+ [
+ ("id", models.BigAutoField(primary_key=True)),
+ ("name", models.CharField(max_length=100)),
+ ],
+ options={},
+ )
+ create_book = migrations.CreateModel(
+ "Book",
+ [
+ ("id", models.BigAutoField(primary_key=True)),
+ ("title", models.CharField(max_length=100)),
+ ("author", models.ForeignKey(to="test_author.Author", on_delete=models.CASCADE))
+ ],
+ options={},
+ )
+ fill_data = migrations.RunPython(create_data)
+
+ project_state = ProjectState()
+ new_state = project_state.clone()
+ with connection.schema_editor() as editor:
+ create_author.state_forwards("test_author", new_state)
+ create_author.database_forwards("test_author", editor, project_state, new_state)
+
+ project_state = new_state
+ new_state = new_state.clone()
+ with connection.schema_editor() as editor:
+ create_book.state_forwards("test_book", new_state)
+ create_book.database_forwards("test_book", editor, project_state, new_state)
+
+ project_state = new_state
+ new_state = new_state.clone()
+ with connection.schema_editor() as editor:
+ fill_data.state_forwards("fill_data", new_state)
+ fill_data.database_forwards("fill_data", editor, project_state, new_state)
+
+ def test_autofield_foreignfield_growth(self):
+ """
+ A field may be migrated from AutoField to BigAutoField.
+ """
+ def create_initial_data(models, schema_editor):
+ Article = models.get_model("test_article", "Article")
+ Blog = models.get_model("test_blog", "Blog")
+ blog = Blog.objects.create(name="web development done right")
+ Article.objects.create(name="Frameworks", blog=blog)
+ Article.objects.create(name="Programming Languages", blog=blog)
+
+ def create_big_data(models, schema_editor):
+ Article = models.get_model("test_article", "Article")
+ Blog = models.get_model("test_blog", "Blog")
+ blog2 = Blog.objects.create(name="Frameworks", id=2 ** 33)
+ Article.objects.create(name="Django", blog=blog2)
+ Article.objects.create(id=2 ** 33, name="Django2", blog=blog2)
+
+ create_blog = migrations.CreateModel(
+ "Blog",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("name", models.CharField(max_length=100)),
+ ],
+ options={},
+ )
+ create_article = migrations.CreateModel(
+ "Article",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("blog", models.ForeignKey(to="test_blog.Blog", on_delete=models.CASCADE)),
+ ("name", models.CharField(max_length=100)),
+ ("data", models.TextField(default="")),
+ ],
+ options={},
+ )
+ fill_initial_data = migrations.RunPython(create_initial_data, create_initial_data)
+ fill_big_data = migrations.RunPython(create_big_data, create_big_data)
+
+ grow_article_id = migrations.AlterField("Article", "id", models.BigAutoField(primary_key=True))
+ grow_blog_id = migrations.AlterField("Blog", "id", models.BigAutoField(primary_key=True))
+
+ project_state = ProjectState()
+ new_state = project_state.clone()
+ with connection.schema_editor() as editor:
+ create_blog.state_forwards("test_blog", new_state)
+ create_blog.database_forwards("test_blog", editor, project_state, new_state)
+
+ project_state = new_state
+ new_state = new_state.clone()
+ with connection.schema_editor() as editor:
+ create_article.state_forwards("test_article", new_state)
+ create_article.database_forwards("test_article", editor, project_state, new_state)
+
+ project_state = new_state
+ new_state = new_state.clone()
+ with connection.schema_editor() as editor:
+ fill_initial_data.state_forwards("fill_initial_data", new_state)
+ fill_initial_data.database_forwards("fill_initial_data", editor, project_state, new_state)
+
+ project_state = new_state
+ new_state = new_state.clone()
+ with connection.schema_editor() as editor:
+ grow_article_id.state_forwards("test_article", new_state)
+ grow_article_id.database_forwards("test_article", editor, project_state, new_state)
+
+ state = new_state.clone()
+ article = state.apps.get_model("test_article.Article")
+ self.assertIsInstance(article._meta.pk, models.BigAutoField)
+
+ project_state = new_state
+ new_state = new_state.clone()
+ with connection.schema_editor() as editor:
+ grow_blog_id.state_forwards("test_blog", new_state)
+ grow_blog_id.database_forwards("test_blog", editor, project_state, new_state)
+
+ state = new_state.clone()
+ blog = state.apps.get_model("test_blog.Blog")
+ self.assertTrue(isinstance(blog._meta.pk, models.BigAutoField))
+
+ project_state = new_state
+ new_state = new_state.clone()
+ with connection.schema_editor() as editor:
+ fill_big_data.state_forwards("fill_big_data", new_state)
+ fill_big_data.database_forwards("fill_big_data", editor, project_state, new_state)
+
def test_run_python_noop(self):
"""
#24098 - Tests no-op RunPython operations.