Fixed #14286 -- Added models.BigAutoField.

This commit is contained in:
Alexander Sosnovskiy 2015-07-02 11:43:15 +03:00 committed by Tim Graham
parent a1d0c60fa0
commit 2a7ce34600
19 changed files with 260 additions and 20 deletions

View File

@ -15,7 +15,8 @@ from django.contrib.gis.gdal import (
SpatialReference, SpatialReference,
) )
from django.contrib.gis.gdal.field import ( 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.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
from django.db import connections, models, router, transaction from django.db import connections, models, router, transaction
@ -60,6 +61,7 @@ class LayerMapping(object):
# counterparts. # counterparts.
FIELD_TYPES = { FIELD_TYPES = {
models.AutoField: OFTInteger, models.AutoField: OFTInteger,
models.BigAutoField: OFTInteger64,
models.IntegerField: (OFTInteger, OFTReal, OFTString), models.IntegerField: (OFTInteger, OFTReal, OFTString),
models.FloatField: (OFTInteger, OFTReal), models.FloatField: (OFTInteger, OFTReal),
models.DateField: OFTDate, models.DateField: OFTDate,

View File

@ -261,7 +261,7 @@ class BaseDatabaseSchemaEditor(object):
definition, definition,
)) ))
# Autoincrement SQL (for backends with post table definition variant) # 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) autoinc_sql = self.connection.ops.autoinc_sql(model._meta.db_table, field.column)
if autoinc_sql: if autoinc_sql:
self.deferred_sql.extend(autoinc_sql) self.deferred_sql.extend(autoinc_sql)

View File

@ -153,6 +153,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# If a column type is set to None, it won't be included in the output. # If a column type is set to None, it won't be included in the output.
_data_types = { _data_types = {
'AutoField': 'integer AUTO_INCREMENT', 'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob', 'BinaryField': 'longblob',
'BooleanField': 'bool', 'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)', 'CharField': 'varchar(%(max_length)s)',

View File

@ -38,8 +38,12 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
def get_field_type(self, data_type, description): def get_field_type(self, data_type, description):
field_type = super(DatabaseIntrospection, self).get_field_type(data_type, description) field_type = super(DatabaseIntrospection, self).get_field_type(data_type, description)
if field_type == 'IntegerField' and 'auto_increment' in description.extra: if 'auto_increment' in description.extra:
return 'AutoField' if field_type == 'IntegerField':
return 'AutoField'
elif field_type == 'BigIntegerField':
return 'BigAutoField'
return field_type return field_type
def get_table_list(self, cursor): def get_table_list(self, cursor):

View File

@ -92,6 +92,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# output (the "qn_" prefix is stripped before the lookup is performed. # output (the "qn_" prefix is stripped before the lookup is performed.
data_types = { data_types = {
'AutoField': 'NUMBER(11)', 'AutoField': 'NUMBER(11)',
'BigAutoField': 'NUMBER(19)',
'BinaryField': 'BLOB', 'BinaryField': 'BLOB',
'BooleanField': 'NUMBER(1)', 'BooleanField': 'NUMBER(1)',
'CharField': 'NVARCHAR2(%(max_length)s)', 'CharField': 'NVARCHAR2(%(max_length)s)',

View File

@ -72,6 +72,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# If a column type is set to None, it won't be included in the output. # If a column type is set to None, it won't be included in the output.
data_types = { data_types = {
'AutoField': 'serial', 'AutoField': 'serial',
'BigAutoField': 'bigserial',
'BinaryField': 'bytea', 'BinaryField': 'bytea',
'BooleanField': 'boolean', 'BooleanField': 'boolean',
'CharField': 'varchar(%(max_length)s)', 'CharField': 'varchar(%(max_length)s)',

View File

@ -46,8 +46,11 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
def get_field_type(self, data_type, description): def get_field_type(self, data_type, description):
field_type = super(DatabaseIntrospection, self).get_field_type(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: if description.default and 'nextval' in description.default:
return 'AutoField' if field_type == 'IntegerField':
return 'AutoField'
elif field_type == 'BigIntegerField':
return 'BigAutoField'
return field_type return field_type
def get_table_list(self, cursor): def get_table_list(self, cursor):

View File

@ -54,14 +54,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
""" """
Makes ALTER TYPE with SERIAL make sense. Makes ALTER TYPE with SERIAL make sense.
""" """
if new_type.lower() == "serial": if new_type.lower() in ("serial", "bigserial"):
column = new_field.column column = new_field.column
sequence_name = "%s_%s_seq" % (table, column) sequence_name = "%s_%s_seq" % (table, column)
col_type = "integer" if new_type.lower() == "serial" else "bigint"
return ( return (
( (
self.sql_alter_column_type % { self.sql_alter_column_type % {
"column": self.quote_name(column), "column": self.quote_name(column),
"type": "integer", "type": col_type,
}, },
[], [],
), ),

View File

@ -93,6 +93,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# schema inspection is more useful. # schema inspection is more useful.
data_types = { data_types = {
'AutoField': 'integer', 'AutoField': 'integer',
'BigAutoField': 'integer',
'BinaryField': 'BLOB', 'BinaryField': 'BLOB',
'BooleanField': 'bool', 'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)', 'CharField': 'varchar(%(max_length)s)',
@ -120,6 +121,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
} }
data_types_suffix = { data_types_suffix = {
'AutoField': 'AUTOINCREMENT', 'AutoField': 'AUTOINCREMENT',
'BigAutoField': 'AUTOINCREMENT',
} }
# SQLite requires LIKE statements to include an ESCAPE clause if the value # SQLite requires LIKE statements to include an ESCAPE clause if the value
# being escaped has a percent or underscore in it. # being escaped has a percent or underscore in it.

View File

@ -42,14 +42,14 @@ from django.utils.translation import ugettext_lazy as _
# Avoid "TypeError: Item in ``from list'' not a string" -- unicode_literals # Avoid "TypeError: Item in ``from list'' not a string" -- unicode_literals
# makes these strings unicode # makes these strings unicode
__all__ = [str(x) for x in ( __all__ = [str(x) for x in (
'AutoField', 'BLANK_CHOICE_DASH', 'BigIntegerField', 'BinaryField', 'AutoField', 'BLANK_CHOICE_DASH', 'BigAutoField', 'BigIntegerField',
'BooleanField', 'CharField', 'CommaSeparatedIntegerField', 'DateField', 'BinaryField', 'BooleanField', 'CharField', 'CommaSeparatedIntegerField',
'DateTimeField', 'DecimalField', 'DurationField', 'EmailField', 'Empty', 'DateField', 'DateTimeField', 'DecimalField', 'DurationField',
'Field', 'FieldDoesNotExist', 'FilePathField', 'FloatField', 'EmailField', 'Empty', 'Field', 'FieldDoesNotExist', 'FilePathField',
'GenericIPAddressField', 'IPAddressField', 'IntegerField', 'NOT_PROVIDED', 'FloatField', 'GenericIPAddressField', 'IPAddressField', 'IntegerField',
'NullBooleanField', 'PositiveIntegerField', 'PositiveSmallIntegerField', 'NOT_PROVIDED', 'NullBooleanField', 'PositiveIntegerField',
'SlugField', 'SmallIntegerField', 'TextField', 'TimeField', 'URLField', 'PositiveSmallIntegerField', 'SlugField', 'SmallIntegerField', 'TextField',
'UUIDField', 'TimeField', 'URLField', 'UUIDField',
)] )]
@ -997,6 +997,16 @@ class AutoField(Field):
return None 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): class BooleanField(Field):
empty_strings_allowed = False empty_strings_allowed = False
default_error_messages = { default_error_messages = {

View File

@ -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 primary key field will automatically be added to your model if you don't specify
otherwise. See :ref:`automatic-primary-key-fields`. 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`` ``BigIntegerField``
------------------- -------------------
.. class:: BigIntegerField(**options) .. 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 guaranteed to fit numbers from ``-9223372036854775808`` to
``9223372036854775807``. The default form widget for this field is a ``9223372036854775807``. The default form widget for this field is a
:class:`~django.forms.TextInput`. :class:`~django.forms.TextInput`.

View File

@ -242,6 +242,10 @@ Models
:class:`~django.db.models.Func`. This attribute can be used to set the number :class:`~django.db.models.Func`. This attribute can be used to set the number
of arguments the function accepts. 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 Requests and Responses
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^

View File

@ -56,6 +56,8 @@ Model field Form field
=================================== ================================================== =================================== ==================================================
:class:`AutoField` Not represented in the form :class:`AutoField` Not represented in the form
:class:`BigAutoField` Not represented in the form
:class:`BigIntegerField` :class:`~django.forms.IntegerField` with :class:`BigIntegerField` :class:`~django.forms.IntegerField` with
``min_value`` set to -9223372036854775808 ``min_value`` set to -9223372036854775808
and ``max_value`` set to 9223372036854775807. and ``max_value`` set to 9223372036854775807.

View File

@ -4,6 +4,24 @@ from django.db import models
from django.utils.encoding import python_2_unicode_compatible 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 @python_2_unicode_compatible
class Reporter(models.Model): class Reporter(models.Model):
first_name = models.CharField(max_length=30) first_name = models.CharField(max_length=30)

View File

@ -6,7 +6,7 @@ from django.db import connection
from django.db.utils import DatabaseError from django.db.utils import DatabaseError
from django.test import TransactionTestCase, mock, skipUnlessDBFeature from django.test import TransactionTestCase, mock, skipUnlessDBFeature
from .models import Article, Reporter from .models import Article, City, Reporter
class IntrospectionTests(TransactionTestCase): class IntrospectionTests(TransactionTestCase):
@ -103,6 +103,12 @@ class IntrospectionTests(TransactionTestCase):
[False, nullable_by_backend, nullable_by_backend, nullable_by_backend, True, True, False] [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 # Regression test for #9991 - 'real' types in postgres
@skipUnlessDBFeature('has_real_datatype') @skipUnlessDBFeature('has_real_datatype')
def test_postgresql_real_type(self): def test_postgresql_real_type(self):

View File

@ -23,12 +23,22 @@ class Publication(models.Model):
ordering = ('title',) ordering = ('title',)
@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 @python_2_unicode_compatible
class Article(models.Model): class Article(models.Model):
headline = models.CharField(max_length=100) headline = models.CharField(max_length=100)
# Assign a unicode string as name to make sure the intermediary model is # Assign a unicode string as name to make sure the intermediary model is
# correctly created. Refs #20207 # correctly created. Refs #20207
publications = models.ManyToManyField(Publication, name='publications') publications = models.ManyToManyField(Publication, name='publications')
tags = models.ManyToManyField(Tag, related_name='tags')
def __str__(self): def __str__(self):
return self.headline return self.headline

View File

@ -32,6 +32,24 @@ class Article(models.Model):
ordering = ('headline',) 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 # 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 # created (the field names being lower-cased versions of their opposite
# classes is important here). # classes is important here).

View File

@ -9,8 +9,8 @@ from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
from .models import ( from .models import (
Article, Category, Child, First, Parent, Record, Relation, Reporter, Article, Category, Child, City, District, First, Parent, Record, Relation,
School, Student, Third, ToFieldChild, Reporter, School, Student, Third, ToFieldChild,
) )
@ -569,6 +569,15 @@ class ManyToOneTests(TestCase):
self.assertIsNot(c.parent, p) self.assertIsNot(c.parent, p)
self.assertEqual(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): def test_multiple_foreignkeys(self):
# Test of multiple ForeignKeys to the same model (bug #7125). # Test of multiple ForeignKeys to the same model (bug #7125).
c1 = Category.objects.create(name='First') c1 = Category.objects.create(name='First')

View File

@ -1806,6 +1806,144 @@ class OperationTests(OperationTestBase):
create_old_man.state_forwards("test_books", new_state) create_old_man.state_forwards("test_books", new_state)
create_old_man.database_forwards("test_books", editor, project_state, 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): def test_run_python_noop(self):
""" """
#24098 - Tests no-op RunPython operations. #24098 - Tests no-op RunPython operations.