summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Pope <nick.pope@flightdataservices.com>2019-07-26 22:05:22 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-08-02 11:39:01 +0200
commit194d1dfc186cc8d2b35dabf64f3ed38b757cbd98 (patch)
tree51ffabb34edc5b191ce8079c9149b77c88c2749e
parent955b382600e4626265cc20e5773bdbcfd01fc4af (diff)
Fixed #30661 -- Added models.SmallAutoField.
-rw-r--r--django/contrib/gis/utils/layermapping.py1
-rw-r--r--django/db/backends/base/features.py5
-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.py2
-rw-r--r--django/db/backends/mysql/operations.py1
-rw-r--r--django/db/backends/oracle/base.py1
-rw-r--r--django/db/backends/oracle/introspection.py2
-rw-r--r--django/db/backends/oracle/operations.py1
-rw-r--r--django/db/backends/oracle/schema.py2
-rw-r--r--django/db/backends/postgresql/base.py1
-rw-r--r--django/db/backends/postgresql/introspection.py2
-rw-r--r--django/db/backends/postgresql/operations.py1
-rw-r--r--django/db/backends/postgresql/schema.py6
-rw-r--r--django/db/backends/sqlite3/base.py2
-rw-r--r--django/db/backends/sqlite3/features.py1
-rw-r--r--django/db/backends/sqlite3/introspection.py6
-rw-r--r--django/db/models/fields/__init__.py14
-rw-r--r--docs/ref/models/fields.txt11
-rw-r--r--docs/releases/3.0.txt5
-rw-r--r--docs/topics/forms/modelforms.txt2
-rw-r--r--tests/db_functions/comparison/test_cast.py1
-rw-r--r--tests/introspection/models.py5
-rw-r--r--tests/introspection/tests.py13
-rw-r--r--tests/many_to_one/models.py6
-rw-r--r--tests/many_to_one/tests.py14
-rw-r--r--tests/migrations/test_operations.py48
-rw-r--r--tests/schema/tests.py47
28 files changed, 177 insertions, 26 deletions
diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py
index 0d65d0fe40..9e211184bb 100644
--- a/django/contrib/gis/utils/layermapping.py
+++ b/django/contrib/gis/utils/layermapping.py
@@ -61,6 +61,7 @@ class LayerMapping:
FIELD_TYPES = {
models.AutoField: OFTInteger,
models.BigAutoField: OFTInteger64,
+ models.SmallAutoField: OFTInteger,
models.BooleanField: (OFTInteger, OFTReal, OFTString),
models.IntegerField: (OFTInteger, OFTReal, OFTString),
models.FloatField: (OFTInteger, OFTReal),
diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py
index bb4b59e7c8..70878f92a2 100644
--- a/django/db/backends/base/features.py
+++ b/django/db/backends/base/features.py
@@ -143,9 +143,10 @@ class BaseDatabaseFeatures:
# Can the backend introspect a TimeField, instead of a DateTimeField?
can_introspect_time_field = True
- # Some backends may not be able to differentiate BigAutoField from other
- # fields such as AutoField.
+ # Some backends may not be able to differentiate BigAutoField or
+ # SmallAutoField from other fields such as AutoField.
introspected_big_auto_field_type = 'BigAutoField'
+ introspected_small_auto_field_type = 'SmallAutoField'
# Some backends may not be able to differentiate BooleanField from other
# fields such as IntegerField.
diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index a5a4163c19..3540fd5d0d 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -182,7 +182,7 @@ class BaseDatabaseSchemaEditor:
))
# Autoincrement SQL (for backends with post table definition
# variant).
- if field.get_internal_type() in ('AutoField', 'BigAutoField'):
+ if field.get_internal_type() in ('AutoField', 'BigAutoField', 'SmallAutoField'):
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 821f24fdf1..9a0ede0ac8 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -123,6 +123,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
+ 'SmallAutoField': 'smallint AUTO_INCREMENT',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time(6)',
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
index db1a387679..05e73c78dc 100644
--- a/django/db/backends/mysql/introspection.py
+++ b/django/db/backends/mysql/introspection.py
@@ -44,6 +44,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
return 'AutoField'
elif field_type == 'BigIntegerField':
return 'BigAutoField'
+ elif field_type == 'SmallIntegerField':
+ return 'SmallAutoField'
if description.is_unsigned:
if field_type == 'IntegerField':
return 'PositiveIntegerField'
diff --git a/django/db/backends/mysql/operations.py b/django/db/backends/mysql/operations.py
index ef908ad8ae..e940286720 100644
--- a/django/db/backends/mysql/operations.py
+++ b/django/db/backends/mysql/operations.py
@@ -19,6 +19,7 @@ class DatabaseOperations(BaseDatabaseOperations):
cast_data_types = {
'AutoField': 'signed integer',
'BigAutoField': 'signed integer',
+ 'SmallAutoField': 'signed integer',
'CharField': 'char(%(max_length)s)',
'DecimalField': 'decimal(%(max_digits)s, %(decimal_places)s)',
'TextField': 'char',
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 0fbe96ab9b..ef33d9fad7 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -123,6 +123,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
'PositiveIntegerField': 'NUMBER(11)',
'PositiveSmallIntegerField': 'NUMBER(11)',
'SlugField': 'NVARCHAR2(%(max_length)s)',
+ 'SmallAutoField': 'NUMBER(5) GENERATED BY DEFAULT ON NULL AS IDENTITY',
'SmallIntegerField': 'NUMBER(11)',
'TextField': 'NCLOB',
'TimeField': 'TIMESTAMP',
diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py
index 1fcba05510..2322ae0b5d 100644
--- a/django/db/backends/oracle/introspection.py
+++ b/django/db/backends/oracle/introspection.py
@@ -35,6 +35,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
if scale == 0:
if precision > 11:
return 'BigAutoField' if description.is_autofield else 'BigIntegerField'
+ elif 1 < precision < 6 and description.is_autofield:
+ return 'SmallAutoField'
elif precision == 1:
return 'BooleanField'
elif description.is_autofield:
diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py
index 77c568e84b..016d4c9a59 100644
--- a/django/db/backends/oracle/operations.py
+++ b/django/db/backends/oracle/operations.py
@@ -56,6 +56,7 @@ END;
cast_data_types = {
'AutoField': 'NUMBER(11)',
'BigAutoField': 'NUMBER(19)',
+ 'SmallAutoField': 'NUMBER(5)',
'TextField': cast_char_field_without_max_length,
}
diff --git a/django/db/backends/oracle/schema.py b/django/db/backends/oracle/schema.py
index 3356788a82..d0f664a6ff 100644
--- a/django/db/backends/oracle/schema.py
+++ b/django/db/backends/oracle/schema.py
@@ -90,7 +90,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
# Make a new field that's like the new one but with a temporary
# column name.
new_temp_field = copy.deepcopy(new_field)
- new_temp_field.null = (new_field.get_internal_type() not in ('AutoField', 'BigAutoField'))
+ new_temp_field.null = (new_field.get_internal_type() not in ('AutoField', 'BigAutoField', 'SmallAutoField'))
new_temp_field.column = self._generate_temp_name(new_field.column)
# Add it
self.add_field(model, new_temp_field)
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index 7e34a3a177..eea8aeb135 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -92,6 +92,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
'PositiveIntegerField': 'integer',
'PositiveSmallIntegerField': 'smallint',
'SlugField': 'varchar(%(max_length)s)',
+ 'SmallAutoField': 'smallserial',
'SmallIntegerField': 'smallint',
'TextField': 'text',
'TimeField': 'time',
diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
index 1c9c7e63a5..b7bf18691c 100644
--- a/django/db/backends/postgresql/introspection.py
+++ b/django/db/backends/postgresql/introspection.py
@@ -37,6 +37,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
return 'AutoField'
elif field_type == 'BigIntegerField':
return 'BigAutoField'
+ elif field_type == 'SmallIntegerField':
+ return 'SmallAutoField'
return field_type
def get_table_list(self, cursor):
diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py
index 0c497edf71..61bac5e55a 100644
--- a/django/db/backends/postgresql/operations.py
+++ b/django/db/backends/postgresql/operations.py
@@ -11,6 +11,7 @@ class DatabaseOperations(BaseDatabaseOperations):
cast_data_types = {
'AutoField': 'integer',
'BigAutoField': 'bigint',
+ 'SmallAutoField': 'smallint',
}
def unification_cast_sql(self, output_field):
diff --git a/django/db/backends/postgresql/schema.py b/django/db/backends/postgresql/schema.py
index b7f52ccf25..eb5b182680 100644
--- a/django/db/backends/postgresql/schema.py
+++ b/django/db/backends/postgresql/schema.py
@@ -69,15 +69,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
self.sql_alter_column_type += ' USING %(column)s::%(type)s'
# Make ALTER TYPE with SERIAL make sense.
table = strip_quotes(model._meta.db_table)
- if new_type.lower() in ("serial", "bigserial"):
+ serial_fields_map = {'bigserial': 'bigint', 'serial': 'integer', 'smallserial': 'smallint'}
+ if new_type.lower() in serial_fields_map:
column = strip_quotes(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": col_type,
+ "type": serial_fields_map[new_type.lower()],
},
[],
),
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index fff65197f9..7a4439ae88 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -104,6 +104,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
'PositiveIntegerField': 'integer unsigned',
'PositiveSmallIntegerField': 'smallint unsigned',
'SlugField': 'varchar(%(max_length)s)',
+ 'SmallAutoField': 'integer',
'SmallIntegerField': 'smallint',
'TextField': 'text',
'TimeField': 'time',
@@ -116,6 +117,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
data_types_suffix = {
'AutoField': 'AUTOINCREMENT',
'BigAutoField': 'AUTOINCREMENT',
+ 'SmallAutoField': '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/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py
index 3d52a8036f..18b76f8c86 100644
--- a/django/db/backends/sqlite3/features.py
+++ b/django/db/backends/sqlite3/features.py
@@ -19,6 +19,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
can_introspect_positive_integer_field = True
can_introspect_small_integer_field = True
introspected_big_auto_field_type = 'AutoField'
+ introspected_small_auto_field_type = 'AutoField'
supports_transactions = True
atomic_transactions = False
can_rollback_ddl = True
diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py
index faf9328415..5e33d2a7e2 100644
--- a/django/db/backends/sqlite3/introspection.py
+++ b/django/db/backends/sqlite3/introspection.py
@@ -57,9 +57,9 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
def get_field_type(self, data_type, description):
field_type = super().get_field_type(data_type, description)
- if description.pk and field_type in {'BigIntegerField', 'IntegerField'}:
- # No support for BigAutoField as SQLite treats all integer primary
- # keys as signed 64-bit integers.
+ if description.pk and field_type in {'BigIntegerField', 'IntegerField', 'SmallIntegerField'}:
+ # No support for BigAutoField or SmallAutoField as SQLite treats
+ # all integer primary keys as signed 64-bit integers.
return 'AutoField'
return field_type
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 1388dffc58..ff686e4f62 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -38,8 +38,8 @@ __all__ = [
'EmailField', 'Empty', 'Field', 'FieldDoesNotExist', 'FilePathField',
'FloatField', 'GenericIPAddressField', 'IPAddressField', 'IntegerField',
'NOT_PROVIDED', 'NullBooleanField', 'PositiveIntegerField',
- 'PositiveSmallIntegerField', 'SlugField', 'SmallIntegerField', 'TextField',
- 'TimeField', 'URLField', 'UUIDField',
+ 'PositiveSmallIntegerField', 'SlugField', 'SmallAutoField',
+ 'SmallIntegerField', 'TextField', 'TimeField', 'URLField', 'UUIDField',
]
@@ -985,6 +985,16 @@ class BigAutoField(AutoField):
return BigIntegerField().db_type(connection=connection)
+class SmallAutoField(AutoField):
+ description = _('Small integer')
+
+ def get_internal_type(self):
+ return 'SmallAutoField'
+
+ def rel_db_type(self, connection):
+ return SmallIntegerField().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 ead39f0572..c267501bf4 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -1085,6 +1085,17 @@ It uses :class:`~django.core.validators.validate_slug` or
If ``True``, the field accepts Unicode letters in addition to ASCII
letters. Defaults to ``False``.
+``SmallAutoField``
+------------------
+
+.. class:: SmallAutoField(**options)
+
+.. versionadded:: 3.0
+
+Like an :class:`AutoField`, but only allows values under a certain
+(database-dependent) limit. Values from ``1`` to ``32767`` are safe in all
+databases supported by Django.
+
``SmallIntegerField``
---------------------
diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt
index da1578c0eb..9de85ab770 100644
--- a/docs/releases/3.0.txt
+++ b/docs/releases/3.0.txt
@@ -294,6 +294,11 @@ Models
* :class:`~django.db.models.Avg` and :class:`~django.db.models.Sum` now support
the ``distinct`` argument.
+* Added :class:`~django.db.models.SmallAutoField` which acts much like an
+ :class:`~django.db.models.AutoField` except that it only allows values under
+ a certain (database-dependent) limit. Values from ``1`` to ``32767`` are safe
+ in all databases supported by Django.
+
Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index 2a13c2d1df..d8b137f624 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -110,6 +110,8 @@ Model field Form field
:class:`SlugField` :class:`~django.forms.SlugField`
+:class:`SmallAutoField` Not represented in the form
+
:class:`SmallIntegerField` :class:`~django.forms.IntegerField`
:class:`TextField` :class:`~django.forms.CharField` with
diff --git a/tests/db_functions/comparison/test_cast.py b/tests/db_functions/comparison/test_cast.py
index c5698d6d0e..988eaa4dad 100644
--- a/tests/db_functions/comparison/test_cast.py
+++ b/tests/db_functions/comparison/test_cast.py
@@ -55,6 +55,7 @@ class CastTests(TestCase):
for field_class in (
models.AutoField,
models.BigAutoField,
+ models.SmallAutoField,
models.IntegerField,
models.BigIntegerField,
models.SmallIntegerField,
diff --git a/tests/introspection/models.py b/tests/introspection/models.py
index fa663de2fd..d069c5820e 100644
--- a/tests/introspection/models.py
+++ b/tests/introspection/models.py
@@ -9,6 +9,11 @@ class City(models.Model):
return self.name
+class Country(models.Model):
+ id = models.SmallAutoField(primary_key=True)
+ name = models.CharField(max_length=50)
+
+
class District(models.Model):
city = models.ForeignKey(City, models.CASCADE, primary_key=True)
name = models.CharField(max_length=50)
diff --git a/tests/introspection/tests.py b/tests/introspection/tests.py
index 7edb01a4b7..4a7ce11e7a 100644
--- a/tests/introspection/tests.py
+++ b/tests/introspection/tests.py
@@ -5,7 +5,9 @@ from django.db.models import Index
from django.db.utils import DatabaseError
from django.test import TransactionTestCase, skipUnlessDBFeature
-from .models import Article, ArticleReporter, City, Comment, District, Reporter
+from .models import (
+ Article, ArticleReporter, City, Comment, Country, District, Reporter,
+)
class IntrospectionTests(TransactionTestCase):
@@ -115,6 +117,15 @@ class IntrospectionTests(TransactionTestCase):
[connection.introspection.get_field_type(r[1], r) for r in desc],
)
+ @skipUnlessDBFeature('can_introspect_autofield')
+ def test_smallautofield(self):
+ with connection.cursor() as cursor:
+ desc = connection.introspection.get_table_description(cursor, Country._meta.db_table)
+ self.assertIn(
+ connection.features.introspected_small_auto_field_type,
+ [connection.introspection.get_field_type(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_one/models.py b/tests/many_to_one/models.py
index 6230129bbd..6e00fb18fa 100644
--- a/tests/many_to_one/models.py
+++ b/tests/many_to_one/models.py
@@ -27,8 +27,14 @@ class Article(models.Model):
return self.headline
+class Country(models.Model):
+ id = models.SmallAutoField(primary_key=True)
+ name = models.CharField(max_length=50)
+
+
class City(models.Model):
id = models.BigAutoField(primary_key=True)
+ country = models.ForeignKey(Country, models.CASCADE, related_name='cities', null=True)
name = models.CharField(max_length=50)
def __str__(self):
diff --git a/tests/many_to_one/tests.py b/tests/many_to_one/tests.py
index e3121a7701..a926574b15 100644
--- a/tests/many_to_one/tests.py
+++ b/tests/many_to_one/tests.py
@@ -8,8 +8,9 @@ from django.test import TestCase
from django.utils.translation import gettext_lazy
from .models import (
- Article, Category, Child, ChildNullableParent, City, District, First,
- Parent, Record, Relation, Reporter, School, Student, Third, ToFieldChild,
+ Article, Category, Child, ChildNullableParent, City, Country, District,
+ First, Parent, Record, Relation, Reporter, School, Student, Third,
+ ToFieldChild,
)
@@ -576,6 +577,15 @@ class ManyToOneTests(TestCase):
District.objects.create(city=ny, name='Brooklyn')
District.objects.create(city=ny, name='Manhattan')
+ def test_fk_to_smallautofield(self):
+ us = Country.objects.create(name='United States')
+ City.objects.create(country=us, name='Chicago')
+ City.objects.create(country=us, name='New York')
+
+ uk = Country.objects.create(name='United Kingdom', id=2 ** 11)
+ City.objects.create(country=uk, name='London')
+ City.objects.create(country=uk, name='Edinburgh')
+
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 3b2129a933..3c321d4cb4 100644
--- a/tests/migrations/test_operations.py
+++ b/tests/migrations/test_operations.py
@@ -2651,9 +2651,13 @@ class OperationTests(OperationTestBase):
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):
+ def _test_autofield_foreignfield_growth(self, source_field, target_field, target_value):
"""
- A field may be migrated from AutoField to BigAutoField.
+ A field may be migrated in the following ways:
+
+ - AutoField to BigAutoField
+ - SmallAutoField to AutoField
+ - SmallAutoField to BigAutoField
"""
def create_initial_data(models, schema_editor):
Article = models.get_model("test_article", "Article")
@@ -2665,14 +2669,14 @@ class OperationTests(OperationTestBase):
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)
+ blog2 = Blog.objects.create(name="Frameworks", id=target_value)
Article.objects.create(name="Django", blog=blog2)
- Article.objects.create(id=2 ** 33, name="Django2", blog=blog2)
+ Article.objects.create(id=target_value, name="Django2", blog=blog2)
create_blog = migrations.CreateModel(
"Blog",
[
- ("id", models.AutoField(primary_key=True)),
+ ("id", source_field(primary_key=True)),
("name", models.CharField(max_length=100)),
],
options={},
@@ -2680,7 +2684,7 @@ class OperationTests(OperationTestBase):
create_article = migrations.CreateModel(
"Article",
[
- ("id", models.AutoField(primary_key=True)),
+ ("id", source_field(primary_key=True)),
("blog", models.ForeignKey(to="test_blog.Blog", on_delete=models.CASCADE)),
("name", models.CharField(max_length=100)),
("data", models.TextField(default="")),
@@ -2690,8 +2694,8 @@ class OperationTests(OperationTestBase):
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))
+ grow_article_id = migrations.AlterField('Article', 'id', target_field(primary_key=True))
+ grow_blog_id = migrations.AlterField('Blog', 'id', target_field(primary_key=True))
project_state = ProjectState()
new_state = project_state.clone()
@@ -2719,7 +2723,7 @@ class OperationTests(OperationTestBase):
state = new_state.clone()
article = state.apps.get_model("test_article.Article")
- self.assertIsInstance(article._meta.pk, models.BigAutoField)
+ self.assertIsInstance(article._meta.pk, target_field)
project_state = new_state
new_state = new_state.clone()
@@ -2729,7 +2733,7 @@ class OperationTests(OperationTestBase):
state = new_state.clone()
blog = state.apps.get_model("test_blog.Blog")
- self.assertIsInstance(blog._meta.pk, models.BigAutoField)
+ self.assertIsInstance(blog._meta.pk, target_field)
project_state = new_state
new_state = new_state.clone()
@@ -2737,6 +2741,30 @@ class OperationTests(OperationTestBase):
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_autofield__bigautofield_foreignfield_growth(self):
+ """A field may be migrated from AutoField to BigAutoField."""
+ self._test_autofield_foreignfield_growth(
+ models.AutoField,
+ models.BigAutoField,
+ 2 ** 33,
+ )
+
+ def test_smallfield_autofield_foreignfield_growth(self):
+ """A field may be migrated from SmallAutoField to AutoField."""
+ self._test_autofield_foreignfield_growth(
+ models.SmallAutoField,
+ models.AutoField,
+ 2 ** 22,
+ )
+
+ def test_smallfield_bigautofield_foreignfield_growth(self):
+ """A field may be migrated from SmallAutoField to BigAutoField."""
+ self._test_autofield_foreignfield_growth(
+ models.SmallAutoField,
+ models.BigAutoField,
+ 2 ** 33,
+ )
+
def test_run_python_noop(self):
"""
#24098 - Tests no-op RunPython operations.
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index a765528d50..7276912c71 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -14,7 +14,8 @@ from django.db.models.deletion import CASCADE, PROTECT
from django.db.models.fields import (
AutoField, BigAutoField, BigIntegerField, BinaryField, BooleanField,
CharField, DateField, DateTimeField, IntegerField, PositiveIntegerField,
- SlugField, TextField, TimeField, UUIDField,
+ SlugField, SmallAutoField, SmallIntegerField, TextField, TimeField,
+ UUIDField,
)
from django.db.models.fields.related import (
ForeignKey, ForeignObject, ManyToManyField, OneToOneField,
@@ -1179,6 +1180,28 @@ class SchemaTests(TransactionTestCase):
# Fail on PostgreSQL if sequence is missing an owner.
self.assertIsNotNone(Author.objects.create(name='Bar'))
+ def test_alter_autofield_pk_to_smallautofield_pk_sequence_owner(self):
+ """
+ Converting an implicit PK to SmallAutoField(primary_key=True) should
+ keep a sequence owner on PostgreSQL.
+ """
+ with connection.schema_editor() as editor:
+ editor.create_model(Author)
+ old_field = Author._meta.get_field('id')
+ new_field = SmallAutoField(primary_key=True)
+ new_field.set_attributes_from_name('id')
+ new_field.model = Author
+ with connection.schema_editor() as editor:
+ editor.alter_field(Author, old_field, new_field, strict=True)
+
+ Author.objects.create(name='Foo', pk=1)
+ with connection.cursor() as cursor:
+ sequence_reset_sqls = connection.ops.sequence_reset_sql(no_style(), [Author])
+ if sequence_reset_sqls:
+ cursor.execute(sequence_reset_sqls[0])
+ # Fail on PostgreSQL if sequence is missing an owner.
+ self.assertIsNotNone(Author.objects.create(name='Bar'))
+
def test_alter_int_pk_to_autofield_pk(self):
"""
Should be able to rename an IntegerField(primary_key=True) to
@@ -1211,6 +1234,28 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_field(IntegerPK, old_field, new_field, strict=True)
+ @isolate_apps('schema')
+ def test_alter_smallint_pk_to_smallautofield_pk(self):
+ """
+ Should be able to rename an SmallIntegerField(primary_key=True) to
+ SmallAutoField(primary_key=True).
+ """
+ class SmallIntegerPK(Model):
+ i = SmallIntegerField(primary_key=True)
+
+ class Meta:
+ app_label = 'schema'
+
+ with connection.schema_editor() as editor:
+ editor.create_model(SmallIntegerPK)
+ self.isolated_local_models = [SmallIntegerPK]
+ old_field = SmallIntegerPK._meta.get_field('i')
+ new_field = SmallAutoField(primary_key=True)
+ new_field.model = SmallIntegerPK
+ new_field.set_attributes_from_name('i')
+ with connection.schema_editor() as editor:
+ editor.alter_field(SmallIntegerPK, old_field, new_field, strict=True)
+
def test_alter_int_pk_to_int_unique(self):
"""
Should be able to rename an IntegerField(primary_key=True) to