Fixed #14286 -- Added models.BigAutoField.
This commit is contained in:
parent
a1d0c60fa0
commit
2a7ce34600
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)',
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)',
|
||||
|
|
|
@ -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)',
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
[],
|
||||
),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -4,6 +4,24 @@ from django.db import models
|
|||
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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -23,12 +23,22 @@ class Publication(models.Model):
|
|||
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
|
||||
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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue