Fixed #30661 -- Added models.SmallAutoField.
This commit is contained in:
parent
955b382600
commit
194d1dfc18
|
@ -61,6 +61,7 @@ class LayerMapping:
|
||||||
FIELD_TYPES = {
|
FIELD_TYPES = {
|
||||||
models.AutoField: OFTInteger,
|
models.AutoField: OFTInteger,
|
||||||
models.BigAutoField: OFTInteger64,
|
models.BigAutoField: OFTInteger64,
|
||||||
|
models.SmallAutoField: OFTInteger,
|
||||||
models.BooleanField: (OFTInteger, OFTReal, OFTString),
|
models.BooleanField: (OFTInteger, OFTReal, OFTString),
|
||||||
models.IntegerField: (OFTInteger, OFTReal, OFTString),
|
models.IntegerField: (OFTInteger, OFTReal, OFTString),
|
||||||
models.FloatField: (OFTInteger, OFTReal),
|
models.FloatField: (OFTInteger, OFTReal),
|
||||||
|
|
|
@ -143,9 +143,10 @@ class BaseDatabaseFeatures:
|
||||||
# Can the backend introspect a TimeField, instead of a DateTimeField?
|
# Can the backend introspect a TimeField, instead of a DateTimeField?
|
||||||
can_introspect_time_field = True
|
can_introspect_time_field = True
|
||||||
|
|
||||||
# Some backends may not be able to differentiate BigAutoField from other
|
# Some backends may not be able to differentiate BigAutoField or
|
||||||
# fields such as AutoField.
|
# SmallAutoField from other fields such as AutoField.
|
||||||
introspected_big_auto_field_type = 'BigAutoField'
|
introspected_big_auto_field_type = 'BigAutoField'
|
||||||
|
introspected_small_auto_field_type = 'SmallAutoField'
|
||||||
|
|
||||||
# Some backends may not be able to differentiate BooleanField from other
|
# Some backends may not be able to differentiate BooleanField from other
|
||||||
# fields such as IntegerField.
|
# fields such as IntegerField.
|
||||||
|
|
|
@ -182,7 +182,7 @@ class BaseDatabaseSchemaEditor:
|
||||||
))
|
))
|
||||||
# Autoincrement SQL (for backends with post table definition
|
# Autoincrement SQL (for backends with post table definition
|
||||||
# variant).
|
# 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)
|
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)
|
||||||
|
|
|
@ -123,6 +123,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
'PositiveIntegerField': 'integer UNSIGNED',
|
'PositiveIntegerField': 'integer UNSIGNED',
|
||||||
'PositiveSmallIntegerField': 'smallint UNSIGNED',
|
'PositiveSmallIntegerField': 'smallint UNSIGNED',
|
||||||
'SlugField': 'varchar(%(max_length)s)',
|
'SlugField': 'varchar(%(max_length)s)',
|
||||||
|
'SmallAutoField': 'smallint AUTO_INCREMENT',
|
||||||
'SmallIntegerField': 'smallint',
|
'SmallIntegerField': 'smallint',
|
||||||
'TextField': 'longtext',
|
'TextField': 'longtext',
|
||||||
'TimeField': 'time(6)',
|
'TimeField': 'time(6)',
|
||||||
|
|
|
@ -44,6 +44,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
return 'AutoField'
|
return 'AutoField'
|
||||||
elif field_type == 'BigIntegerField':
|
elif field_type == 'BigIntegerField':
|
||||||
return 'BigAutoField'
|
return 'BigAutoField'
|
||||||
|
elif field_type == 'SmallIntegerField':
|
||||||
|
return 'SmallAutoField'
|
||||||
if description.is_unsigned:
|
if description.is_unsigned:
|
||||||
if field_type == 'IntegerField':
|
if field_type == 'IntegerField':
|
||||||
return 'PositiveIntegerField'
|
return 'PositiveIntegerField'
|
||||||
|
|
|
@ -19,6 +19,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
cast_data_types = {
|
cast_data_types = {
|
||||||
'AutoField': 'signed integer',
|
'AutoField': 'signed integer',
|
||||||
'BigAutoField': 'signed integer',
|
'BigAutoField': 'signed integer',
|
||||||
|
'SmallAutoField': 'signed integer',
|
||||||
'CharField': 'char(%(max_length)s)',
|
'CharField': 'char(%(max_length)s)',
|
||||||
'DecimalField': 'decimal(%(max_digits)s, %(decimal_places)s)',
|
'DecimalField': 'decimal(%(max_digits)s, %(decimal_places)s)',
|
||||||
'TextField': 'char',
|
'TextField': 'char',
|
||||||
|
|
|
@ -123,6 +123,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
'PositiveIntegerField': 'NUMBER(11)',
|
'PositiveIntegerField': 'NUMBER(11)',
|
||||||
'PositiveSmallIntegerField': 'NUMBER(11)',
|
'PositiveSmallIntegerField': 'NUMBER(11)',
|
||||||
'SlugField': 'NVARCHAR2(%(max_length)s)',
|
'SlugField': 'NVARCHAR2(%(max_length)s)',
|
||||||
|
'SmallAutoField': 'NUMBER(5) GENERATED BY DEFAULT ON NULL AS IDENTITY',
|
||||||
'SmallIntegerField': 'NUMBER(11)',
|
'SmallIntegerField': 'NUMBER(11)',
|
||||||
'TextField': 'NCLOB',
|
'TextField': 'NCLOB',
|
||||||
'TimeField': 'TIMESTAMP',
|
'TimeField': 'TIMESTAMP',
|
||||||
|
|
|
@ -35,6 +35,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
if scale == 0:
|
if scale == 0:
|
||||||
if precision > 11:
|
if precision > 11:
|
||||||
return 'BigAutoField' if description.is_autofield else 'BigIntegerField'
|
return 'BigAutoField' if description.is_autofield else 'BigIntegerField'
|
||||||
|
elif 1 < precision < 6 and description.is_autofield:
|
||||||
|
return 'SmallAutoField'
|
||||||
elif precision == 1:
|
elif precision == 1:
|
||||||
return 'BooleanField'
|
return 'BooleanField'
|
||||||
elif description.is_autofield:
|
elif description.is_autofield:
|
||||||
|
|
|
@ -56,6 +56,7 @@ END;
|
||||||
cast_data_types = {
|
cast_data_types = {
|
||||||
'AutoField': 'NUMBER(11)',
|
'AutoField': 'NUMBER(11)',
|
||||||
'BigAutoField': 'NUMBER(19)',
|
'BigAutoField': 'NUMBER(19)',
|
||||||
|
'SmallAutoField': 'NUMBER(5)',
|
||||||
'TextField': cast_char_field_without_max_length,
|
'TextField': cast_char_field_without_max_length,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
# Make a new field that's like the new one but with a temporary
|
# Make a new field that's like the new one but with a temporary
|
||||||
# column name.
|
# column name.
|
||||||
new_temp_field = copy.deepcopy(new_field)
|
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)
|
new_temp_field.column = self._generate_temp_name(new_field.column)
|
||||||
# Add it
|
# Add it
|
||||||
self.add_field(model, new_temp_field)
|
self.add_field(model, new_temp_field)
|
||||||
|
|
|
@ -92,6 +92,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
'PositiveIntegerField': 'integer',
|
'PositiveIntegerField': 'integer',
|
||||||
'PositiveSmallIntegerField': 'smallint',
|
'PositiveSmallIntegerField': 'smallint',
|
||||||
'SlugField': 'varchar(%(max_length)s)',
|
'SlugField': 'varchar(%(max_length)s)',
|
||||||
|
'SmallAutoField': 'smallserial',
|
||||||
'SmallIntegerField': 'smallint',
|
'SmallIntegerField': 'smallint',
|
||||||
'TextField': 'text',
|
'TextField': 'text',
|
||||||
'TimeField': 'time',
|
'TimeField': 'time',
|
||||||
|
|
|
@ -37,6 +37,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
return 'AutoField'
|
return 'AutoField'
|
||||||
elif field_type == 'BigIntegerField':
|
elif field_type == 'BigIntegerField':
|
||||||
return 'BigAutoField'
|
return 'BigAutoField'
|
||||||
|
elif field_type == 'SmallIntegerField':
|
||||||
|
return 'SmallAutoField'
|
||||||
return field_type
|
return field_type
|
||||||
|
|
||||||
def get_table_list(self, cursor):
|
def get_table_list(self, cursor):
|
||||||
|
|
|
@ -11,6 +11,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
cast_data_types = {
|
cast_data_types = {
|
||||||
'AutoField': 'integer',
|
'AutoField': 'integer',
|
||||||
'BigAutoField': 'bigint',
|
'BigAutoField': 'bigint',
|
||||||
|
'SmallAutoField': 'smallint',
|
||||||
}
|
}
|
||||||
|
|
||||||
def unification_cast_sql(self, output_field):
|
def unification_cast_sql(self, output_field):
|
||||||
|
|
|
@ -69,15 +69,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
self.sql_alter_column_type += ' USING %(column)s::%(type)s'
|
self.sql_alter_column_type += ' USING %(column)s::%(type)s'
|
||||||
# Make ALTER TYPE with SERIAL make sense.
|
# Make ALTER TYPE with SERIAL make sense.
|
||||||
table = strip_quotes(model._meta.db_table)
|
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)
|
column = strip_quotes(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": col_type,
|
"type": serial_fields_map[new_type.lower()],
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
|
|
|
@ -104,6 +104,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
'PositiveIntegerField': 'integer unsigned',
|
'PositiveIntegerField': 'integer unsigned',
|
||||||
'PositiveSmallIntegerField': 'smallint unsigned',
|
'PositiveSmallIntegerField': 'smallint unsigned',
|
||||||
'SlugField': 'varchar(%(max_length)s)',
|
'SlugField': 'varchar(%(max_length)s)',
|
||||||
|
'SmallAutoField': 'integer',
|
||||||
'SmallIntegerField': 'smallint',
|
'SmallIntegerField': 'smallint',
|
||||||
'TextField': 'text',
|
'TextField': 'text',
|
||||||
'TimeField': 'time',
|
'TimeField': 'time',
|
||||||
|
@ -116,6 +117,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
data_types_suffix = {
|
data_types_suffix = {
|
||||||
'AutoField': 'AUTOINCREMENT',
|
'AutoField': 'AUTOINCREMENT',
|
||||||
'BigAutoField': 'AUTOINCREMENT',
|
'BigAutoField': 'AUTOINCREMENT',
|
||||||
|
'SmallAutoField': '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.
|
||||||
|
|
|
@ -19,6 +19,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
can_introspect_positive_integer_field = True
|
can_introspect_positive_integer_field = True
|
||||||
can_introspect_small_integer_field = True
|
can_introspect_small_integer_field = True
|
||||||
introspected_big_auto_field_type = 'AutoField'
|
introspected_big_auto_field_type = 'AutoField'
|
||||||
|
introspected_small_auto_field_type = 'AutoField'
|
||||||
supports_transactions = True
|
supports_transactions = True
|
||||||
atomic_transactions = False
|
atomic_transactions = False
|
||||||
can_rollback_ddl = True
|
can_rollback_ddl = True
|
||||||
|
|
|
@ -57,9 +57,9 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
|
|
||||||
def get_field_type(self, data_type, description):
|
def get_field_type(self, data_type, description):
|
||||||
field_type = super().get_field_type(data_type, description)
|
field_type = super().get_field_type(data_type, description)
|
||||||
if description.pk and field_type in {'BigIntegerField', 'IntegerField'}:
|
if description.pk and field_type in {'BigIntegerField', 'IntegerField', 'SmallIntegerField'}:
|
||||||
# No support for BigAutoField as SQLite treats all integer primary
|
# No support for BigAutoField or SmallAutoField as SQLite treats
|
||||||
# keys as signed 64-bit integers.
|
# all integer primary keys as signed 64-bit integers.
|
||||||
return 'AutoField'
|
return 'AutoField'
|
||||||
return field_type
|
return field_type
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,8 @@ __all__ = [
|
||||||
'EmailField', 'Empty', 'Field', 'FieldDoesNotExist', 'FilePathField',
|
'EmailField', 'Empty', 'Field', 'FieldDoesNotExist', 'FilePathField',
|
||||||
'FloatField', 'GenericIPAddressField', 'IPAddressField', 'IntegerField',
|
'FloatField', 'GenericIPAddressField', 'IPAddressField', 'IntegerField',
|
||||||
'NOT_PROVIDED', 'NullBooleanField', 'PositiveIntegerField',
|
'NOT_PROVIDED', 'NullBooleanField', 'PositiveIntegerField',
|
||||||
'PositiveSmallIntegerField', 'SlugField', 'SmallIntegerField', 'TextField',
|
'PositiveSmallIntegerField', 'SlugField', 'SmallAutoField',
|
||||||
'TimeField', 'URLField', 'UUIDField',
|
'SmallIntegerField', 'TextField', 'TimeField', 'URLField', 'UUIDField',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -985,6 +985,16 @@ class BigAutoField(AutoField):
|
||||||
return BigIntegerField().db_type(connection=connection)
|
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):
|
class BooleanField(Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
|
|
|
@ -1085,6 +1085,17 @@ It uses :class:`~django.core.validators.validate_slug` or
|
||||||
If ``True``, the field accepts Unicode letters in addition to ASCII
|
If ``True``, the field accepts Unicode letters in addition to ASCII
|
||||||
letters. Defaults to ``False``.
|
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``
|
``SmallIntegerField``
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
|
|
@ -294,6 +294,11 @@ Models
|
||||||
* :class:`~django.db.models.Avg` and :class:`~django.db.models.Sum` now support
|
* :class:`~django.db.models.Avg` and :class:`~django.db.models.Sum` now support
|
||||||
the ``distinct`` argument.
|
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
|
Requests and Responses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,8 @@ Model field Form field
|
||||||
|
|
||||||
:class:`SlugField` :class:`~django.forms.SlugField`
|
:class:`SlugField` :class:`~django.forms.SlugField`
|
||||||
|
|
||||||
|
:class:`SmallAutoField` Not represented in the form
|
||||||
|
|
||||||
:class:`SmallIntegerField` :class:`~django.forms.IntegerField`
|
:class:`SmallIntegerField` :class:`~django.forms.IntegerField`
|
||||||
|
|
||||||
:class:`TextField` :class:`~django.forms.CharField` with
|
:class:`TextField` :class:`~django.forms.CharField` with
|
||||||
|
|
|
@ -55,6 +55,7 @@ class CastTests(TestCase):
|
||||||
for field_class in (
|
for field_class in (
|
||||||
models.AutoField,
|
models.AutoField,
|
||||||
models.BigAutoField,
|
models.BigAutoField,
|
||||||
|
models.SmallAutoField,
|
||||||
models.IntegerField,
|
models.IntegerField,
|
||||||
models.BigIntegerField,
|
models.BigIntegerField,
|
||||||
models.SmallIntegerField,
|
models.SmallIntegerField,
|
||||||
|
|
|
@ -9,6 +9,11 @@ class City(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Country(models.Model):
|
||||||
|
id = models.SmallAutoField(primary_key=True)
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
|
||||||
class District(models.Model):
|
class District(models.Model):
|
||||||
city = models.ForeignKey(City, models.CASCADE, primary_key=True)
|
city = models.ForeignKey(City, models.CASCADE, primary_key=True)
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
|
|
|
@ -5,7 +5,9 @@ from django.db.models import Index
|
||||||
from django.db.utils import DatabaseError
|
from django.db.utils import DatabaseError
|
||||||
from django.test import TransactionTestCase, skipUnlessDBFeature
|
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):
|
class IntrospectionTests(TransactionTestCase):
|
||||||
|
@ -115,6 +117,15 @@ class IntrospectionTests(TransactionTestCase):
|
||||||
[connection.introspection.get_field_type(r[1], r) for r in desc],
|
[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
|
# 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):
|
||||||
|
|
|
@ -27,8 +27,14 @@ class Article(models.Model):
|
||||||
return self.headline
|
return self.headline
|
||||||
|
|
||||||
|
|
||||||
|
class Country(models.Model):
|
||||||
|
id = models.SmallAutoField(primary_key=True)
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
|
||||||
class City(models.Model):
|
class City(models.Model):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
country = models.ForeignKey(Country, models.CASCADE, related_name='cities', null=True)
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -8,8 +8,9 @@ from django.test import TestCase
|
||||||
from django.utils.translation import gettext_lazy
|
from django.utils.translation import gettext_lazy
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Article, Category, Child, ChildNullableParent, City, District, First,
|
Article, Category, Child, ChildNullableParent, City, Country, District,
|
||||||
Parent, Record, Relation, Reporter, School, Student, Third, ToFieldChild,
|
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='Brooklyn')
|
||||||
District.objects.create(city=ny, name='Manhattan')
|
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):
|
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')
|
||||||
|
|
|
@ -2651,9 +2651,13 @@ class OperationTests(OperationTestBase):
|
||||||
fill_data.state_forwards("fill_data", new_state)
|
fill_data.state_forwards("fill_data", new_state)
|
||||||
fill_data.database_forwards("fill_data", editor, project_state, 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):
|
def create_initial_data(models, schema_editor):
|
||||||
Article = models.get_model("test_article", "Article")
|
Article = models.get_model("test_article", "Article")
|
||||||
|
@ -2665,14 +2669,14 @@ class OperationTests(OperationTestBase):
|
||||||
def create_big_data(models, schema_editor):
|
def create_big_data(models, schema_editor):
|
||||||
Article = models.get_model("test_article", "Article")
|
Article = models.get_model("test_article", "Article")
|
||||||
Blog = models.get_model("test_blog", "Blog")
|
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(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(
|
create_blog = migrations.CreateModel(
|
||||||
"Blog",
|
"Blog",
|
||||||
[
|
[
|
||||||
("id", models.AutoField(primary_key=True)),
|
("id", source_field(primary_key=True)),
|
||||||
("name", models.CharField(max_length=100)),
|
("name", models.CharField(max_length=100)),
|
||||||
],
|
],
|
||||||
options={},
|
options={},
|
||||||
|
@ -2680,7 +2684,7 @@ class OperationTests(OperationTestBase):
|
||||||
create_article = migrations.CreateModel(
|
create_article = migrations.CreateModel(
|
||||||
"Article",
|
"Article",
|
||||||
[
|
[
|
||||||
("id", models.AutoField(primary_key=True)),
|
("id", source_field(primary_key=True)),
|
||||||
("blog", models.ForeignKey(to="test_blog.Blog", on_delete=models.CASCADE)),
|
("blog", models.ForeignKey(to="test_blog.Blog", on_delete=models.CASCADE)),
|
||||||
("name", models.CharField(max_length=100)),
|
("name", models.CharField(max_length=100)),
|
||||||
("data", models.TextField(default="")),
|
("data", models.TextField(default="")),
|
||||||
|
@ -2690,8 +2694,8 @@ class OperationTests(OperationTestBase):
|
||||||
fill_initial_data = migrations.RunPython(create_initial_data, create_initial_data)
|
fill_initial_data = migrations.RunPython(create_initial_data, create_initial_data)
|
||||||
fill_big_data = migrations.RunPython(create_big_data, create_big_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_article_id = migrations.AlterField('Article', 'id', target_field(primary_key=True))
|
||||||
grow_blog_id = migrations.AlterField("Blog", "id", models.BigAutoField(primary_key=True))
|
grow_blog_id = migrations.AlterField('Blog', 'id', target_field(primary_key=True))
|
||||||
|
|
||||||
project_state = ProjectState()
|
project_state = ProjectState()
|
||||||
new_state = project_state.clone()
|
new_state = project_state.clone()
|
||||||
|
@ -2719,7 +2723,7 @@ class OperationTests(OperationTestBase):
|
||||||
|
|
||||||
state = new_state.clone()
|
state = new_state.clone()
|
||||||
article = state.apps.get_model("test_article.Article")
|
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
|
project_state = new_state
|
||||||
new_state = new_state.clone()
|
new_state = new_state.clone()
|
||||||
|
@ -2729,7 +2733,7 @@ class OperationTests(OperationTestBase):
|
||||||
|
|
||||||
state = new_state.clone()
|
state = new_state.clone()
|
||||||
blog = state.apps.get_model("test_blog.Blog")
|
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
|
project_state = new_state
|
||||||
new_state = new_state.clone()
|
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.state_forwards("fill_big_data", new_state)
|
||||||
fill_big_data.database_forwards("fill_big_data", editor, project_state, 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):
|
def test_run_python_noop(self):
|
||||||
"""
|
"""
|
||||||
#24098 - Tests no-op RunPython operations.
|
#24098 - Tests no-op RunPython operations.
|
||||||
|
|
|
@ -14,7 +14,8 @@ from django.db.models.deletion import CASCADE, PROTECT
|
||||||
from django.db.models.fields import (
|
from django.db.models.fields import (
|
||||||
AutoField, BigAutoField, BigIntegerField, BinaryField, BooleanField,
|
AutoField, BigAutoField, BigIntegerField, BinaryField, BooleanField,
|
||||||
CharField, DateField, DateTimeField, IntegerField, PositiveIntegerField,
|
CharField, DateField, DateTimeField, IntegerField, PositiveIntegerField,
|
||||||
SlugField, TextField, TimeField, UUIDField,
|
SlugField, SmallAutoField, SmallIntegerField, TextField, TimeField,
|
||||||
|
UUIDField,
|
||||||
)
|
)
|
||||||
from django.db.models.fields.related import (
|
from django.db.models.fields.related import (
|
||||||
ForeignKey, ForeignObject, ManyToManyField, OneToOneField,
|
ForeignKey, ForeignObject, ManyToManyField, OneToOneField,
|
||||||
|
@ -1179,6 +1180,28 @@ class SchemaTests(TransactionTestCase):
|
||||||
# Fail on PostgreSQL if sequence is missing an owner.
|
# Fail on PostgreSQL if sequence is missing an owner.
|
||||||
self.assertIsNotNone(Author.objects.create(name='Bar'))
|
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):
|
def test_alter_int_pk_to_autofield_pk(self):
|
||||||
"""
|
"""
|
||||||
Should be able to rename an IntegerField(primary_key=True) to
|
Should be able to rename an IntegerField(primary_key=True) to
|
||||||
|
@ -1211,6 +1234,28 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.alter_field(IntegerPK, old_field, new_field, strict=True)
|
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):
|
def test_alter_int_pk_to_int_unique(self):
|
||||||
"""
|
"""
|
||||||
Should be able to rename an IntegerField(primary_key=True) to
|
Should be able to rename an IntegerField(primary_key=True) to
|
||||||
|
|
Loading…
Reference in New Issue