Fixed #31007 -- Allowed specifying type of auto-created primary keys.
This also changes the default type of auto-created primary keys for new apps and projects to BigAutoField.
This commit is contained in:
parent
b960e4ed72
commit
b5e12d490a
|
@ -5,6 +5,7 @@ from importlib import import_module
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.utils.deprecation import RemovedInDjango41Warning
|
from django.utils.deprecation import RemovedInDjango41Warning
|
||||||
|
from django.utils.functional import cached_property
|
||||||
from django.utils.module_loading import import_string, module_has_submodule
|
from django.utils.module_loading import import_string, module_has_submodule
|
||||||
|
|
||||||
APPS_MODULE_NAME = 'apps'
|
APPS_MODULE_NAME = 'apps'
|
||||||
|
@ -55,6 +56,15 @@ class AppConfig:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self.label)
|
return '<%s: %s>' % (self.__class__.__name__, self.label)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def default_auto_field(self):
|
||||||
|
from django.conf import settings
|
||||||
|
return settings.DEFAULT_AUTO_FIELD
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _is_default_auto_field_overridden(self):
|
||||||
|
return self.__class__.default_auto_field is not AppConfig.default_auto_field
|
||||||
|
|
||||||
def _path_from_module(self, module):
|
def _path_from_module(self, module):
|
||||||
"""Attempt to determine app's filesystem path from its module."""
|
"""Attempt to determine app's filesystem path from its module."""
|
||||||
# See #21874 for extended discussion of the behavior of this method in
|
# See #21874 for extended discussion of the behavior of this method in
|
||||||
|
|
|
@ -2,4 +2,5 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class {{ camel_case_app_name }}Config(AppConfig):
|
class {{ camel_case_app_name }}Config(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = '{{ app_name }}'
|
name = '{{ app_name }}'
|
||||||
|
|
|
@ -414,6 +414,9 @@ THOUSAND_SEPARATOR = ','
|
||||||
DEFAULT_TABLESPACE = ''
|
DEFAULT_TABLESPACE = ''
|
||||||
DEFAULT_INDEX_TABLESPACE = ''
|
DEFAULT_INDEX_TABLESPACE = ''
|
||||||
|
|
||||||
|
# Default primary key field type.
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
||||||
# Default X-Frame-Options header value
|
# Default X-Frame-Options header value
|
||||||
X_FRAME_OPTIONS = 'DENY'
|
X_FRAME_OPTIONS = 'DENY'
|
||||||
|
|
||||||
|
|
|
@ -118,3 +118,8 @@ USE_TZ = True
|
||||||
# https://docs.djangoproject.com/en/{{ docs_version }}/howto/static-files/
|
# https://docs.djangoproject.com/en/{{ docs_version }}/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
class SimpleAdminConfig(AppConfig):
|
class SimpleAdminConfig(AppConfig):
|
||||||
"""Simple AppConfig which does not do automatic discovery."""
|
"""Simple AppConfig which does not do automatic discovery."""
|
||||||
|
|
||||||
|
default_auto_field = 'django.db.models.AutoField'
|
||||||
default_site = 'django.contrib.admin.sites.AdminSite'
|
default_site = 'django.contrib.admin.sites.AdminSite'
|
||||||
name = 'django.contrib.admin'
|
name = 'django.contrib.admin'
|
||||||
verbose_name = _("Administration")
|
verbose_name = _("Administration")
|
||||||
|
|
|
@ -11,6 +11,7 @@ from .signals import user_logged_in
|
||||||
|
|
||||||
|
|
||||||
class AuthConfig(AppConfig):
|
class AuthConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.AutoField'
|
||||||
name = 'django.contrib.auth'
|
name = 'django.contrib.auth'
|
||||||
verbose_name = _("Authentication and Authorization")
|
verbose_name = _("Authentication and Authorization")
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .management import (
|
||||||
|
|
||||||
|
|
||||||
class ContentTypesConfig(AppConfig):
|
class ContentTypesConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.AutoField'
|
||||||
name = 'django.contrib.contenttypes'
|
name = 'django.contrib.contenttypes'
|
||||||
verbose_name = _("Content Types")
|
verbose_name = _("Content Types")
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,6 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class FlatPagesConfig(AppConfig):
|
class FlatPagesConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.AutoField'
|
||||||
name = 'django.contrib.flatpages'
|
name = 'django.contrib.flatpages'
|
||||||
verbose_name = _("Flat Pages")
|
verbose_name = _("Flat Pages")
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class GISConfig(AppConfig):
|
class GISConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.AutoField'
|
||||||
name = 'django.contrib.gis'
|
name = 'django.contrib.gis'
|
||||||
verbose_name = _("GIS")
|
verbose_name = _("GIS")
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,6 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class RedirectsConfig(AppConfig):
|
class RedirectsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.AutoField'
|
||||||
name = 'django.contrib.redirects'
|
name = 'django.contrib.redirects'
|
||||||
verbose_name = _("Redirects")
|
verbose_name = _("Redirects")
|
||||||
|
|
|
@ -3,5 +3,6 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class SiteMapsConfig(AppConfig):
|
class SiteMapsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.AutoField'
|
||||||
name = 'django.contrib.sitemaps'
|
name = 'django.contrib.sitemaps'
|
||||||
verbose_name = _("Site Maps")
|
verbose_name = _("Site Maps")
|
||||||
|
|
|
@ -8,6 +8,7 @@ from .management import create_default_site
|
||||||
|
|
||||||
|
|
||||||
class SitesConfig(AppConfig):
|
class SitesConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.AutoField'
|
||||||
name = 'django.contrib.sites'
|
name = 'django.contrib.sites'
|
||||||
verbose_name = _("Sites")
|
verbose_name = _("Sites")
|
||||||
|
|
||||||
|
|
|
@ -1290,10 +1290,35 @@ class Model(metaclass=ModelBase):
|
||||||
*cls._check_indexes(databases),
|
*cls._check_indexes(databases),
|
||||||
*cls._check_ordering(),
|
*cls._check_ordering(),
|
||||||
*cls._check_constraints(databases),
|
*cls._check_constraints(databases),
|
||||||
|
*cls._check_default_pk(),
|
||||||
]
|
]
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _check_default_pk(cls):
|
||||||
|
if (
|
||||||
|
cls._meta.pk.auto_created and
|
||||||
|
not settings.is_overridden('DEFAULT_AUTO_FIELD') and
|
||||||
|
not cls._meta.app_config._is_default_auto_field_overridden
|
||||||
|
):
|
||||||
|
return [
|
||||||
|
checks.Warning(
|
||||||
|
f"Auto-created primary key used when not defining a "
|
||||||
|
f"primary key type, by default "
|
||||||
|
f"'{settings.DEFAULT_AUTO_FIELD}'.",
|
||||||
|
hint=(
|
||||||
|
f"Configure the DEFAULT_AUTO_FIELD setting or the "
|
||||||
|
f"{cls._meta.app_config.__class__.__qualname__}."
|
||||||
|
f"default_auto_field attribute to point to a subclass "
|
||||||
|
f"of AutoField, e.g. 'django.db.models.BigAutoField'."
|
||||||
|
),
|
||||||
|
obj=cls,
|
||||||
|
id='models.W042',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
return []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_swappable(cls):
|
def _check_swappable(cls):
|
||||||
"""Check if the swapped model exists."""
|
"""Check if the swapped model exists."""
|
||||||
|
|
|
@ -5,12 +5,13 @@ from collections import defaultdict
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
|
||||||
from django.db import connections
|
from django.db import connections
|
||||||
from django.db.models import AutoField, Manager, OrderWrt, UniqueConstraint
|
from django.db.models import AutoField, Manager, OrderWrt, UniqueConstraint
|
||||||
from django.db.models.query_utils import PathInfo
|
from django.db.models.query_utils import PathInfo
|
||||||
from django.utils.datastructures import ImmutableList, OrderedSet
|
from django.utils.datastructures import ImmutableList, OrderedSet
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.module_loading import import_string
|
||||||
from django.utils.text import camel_case_to_spaces, format_lazy
|
from django.utils.text import camel_case_to_spaces, format_lazy
|
||||||
from django.utils.translation import override
|
from django.utils.translation import override
|
||||||
|
|
||||||
|
@ -217,6 +218,37 @@ class Options:
|
||||||
new_objs.append(obj)
|
new_objs.append(obj)
|
||||||
return new_objs
|
return new_objs
|
||||||
|
|
||||||
|
def _get_default_pk_class(self):
|
||||||
|
pk_class_path = getattr(
|
||||||
|
self.app_config,
|
||||||
|
'default_auto_field',
|
||||||
|
settings.DEFAULT_AUTO_FIELD,
|
||||||
|
)
|
||||||
|
if self.app_config and self.app_config._is_default_auto_field_overridden:
|
||||||
|
app_config_class = type(self.app_config)
|
||||||
|
source = (
|
||||||
|
f'{app_config_class.__module__}.'
|
||||||
|
f'{app_config_class.__qualname__}.default_auto_field'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
source = 'DEFAULT_AUTO_FIELD'
|
||||||
|
if not pk_class_path:
|
||||||
|
raise ImproperlyConfigured(f'{source} must not be empty.')
|
||||||
|
try:
|
||||||
|
pk_class = import_string(pk_class_path)
|
||||||
|
except ImportError as e:
|
||||||
|
msg = (
|
||||||
|
f"{source} refers to the module '{pk_class_path}' that could "
|
||||||
|
f"not be imported."
|
||||||
|
)
|
||||||
|
raise ImproperlyConfigured(msg) from e
|
||||||
|
if not issubclass(pk_class, AutoField):
|
||||||
|
raise ValueError(
|
||||||
|
f"Primary key '{pk_class_path}' referred by {source} must "
|
||||||
|
f"subclass AutoField."
|
||||||
|
)
|
||||||
|
return pk_class
|
||||||
|
|
||||||
def _prepare(self, model):
|
def _prepare(self, model):
|
||||||
if self.order_with_respect_to:
|
if self.order_with_respect_to:
|
||||||
# The app registry will not be ready at this point, so we cannot
|
# The app registry will not be ready at this point, so we cannot
|
||||||
|
@ -250,7 +282,8 @@ class Options:
|
||||||
field.primary_key = True
|
field.primary_key = True
|
||||||
self.setup_pk(field)
|
self.setup_pk(field)
|
||||||
else:
|
else:
|
||||||
auto = AutoField(verbose_name='ID', primary_key=True, auto_created=True)
|
pk_class = self._get_default_pk_class()
|
||||||
|
auto = pk_class(verbose_name='ID', primary_key=True, auto_created=True)
|
||||||
model.add_to_class('id', auto)
|
model.add_to_class('id', auto)
|
||||||
|
|
||||||
def add_manager(self, manager):
|
def add_manager(self, manager):
|
||||||
|
|
|
@ -90,6 +90,7 @@ would provide a proper name for the admin::
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
class RockNRollConfig(AppConfig):
|
class RockNRollConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'rock_n_roll'
|
name = 'rock_n_roll'
|
||||||
verbose_name = "Rock ’n’ roll"
|
verbose_name = "Rock ’n’ roll"
|
||||||
|
|
||||||
|
@ -219,6 +220,16 @@ Configurable attributes
|
||||||
|
|
||||||
By default, this attribute isn't set.
|
By default, this attribute isn't set.
|
||||||
|
|
||||||
|
.. attribute:: AppConfig.default_auto_field
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
|
The implicit primary key type to add to models within this app. You can
|
||||||
|
use this to keep :class:`~django.db.models.AutoField` as the primary key
|
||||||
|
type for third party applications.
|
||||||
|
|
||||||
|
By default, this is the value of :setting:`DEFAULT_AUTO_FIELD`.
|
||||||
|
|
||||||
Read-only attributes
|
Read-only attributes
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
|
@ -378,6 +378,8 @@ Models
|
||||||
* **models.W040**: ``<database>`` does not support indexes with non-key
|
* **models.W040**: ``<database>`` does not support indexes with non-key
|
||||||
columns.
|
columns.
|
||||||
* **models.E041**: ``constraints`` refers to the joined field ``<field name>``.
|
* **models.E041**: ``constraints`` refers to the joined field ``<field name>``.
|
||||||
|
* **models.W042**: Auto-created primary key used when not defining a primary
|
||||||
|
key type, by default ``django.db.models.AutoField``.
|
||||||
|
|
||||||
Security
|
Security
|
||||||
--------
|
--------
|
||||||
|
|
|
@ -415,9 +415,12 @@ cross-site scripting attack.
|
||||||
If ``True``, this field is the primary key for the model.
|
If ``True``, this field is the primary key for the model.
|
||||||
|
|
||||||
If you don't specify ``primary_key=True`` for any field in your model, Django
|
If you don't specify ``primary_key=True`` for any field in your model, Django
|
||||||
will automatically add an :class:`AutoField` to hold the primary key, so you
|
will automatically add a field to hold the primary key, so you don't need to
|
||||||
don't need to set ``primary_key=True`` on any of your fields unless you want to
|
set ``primary_key=True`` on any of your fields unless you want to override the
|
||||||
override the default primary-key behavior. For more, see
|
default primary-key behavior. The type of auto-created primary key fields can
|
||||||
|
be specified per app in :attr:`AppConfig.default_auto_field
|
||||||
|
<django.apps.AppConfig.default_auto_field>` or globally in the
|
||||||
|
:setting:`DEFAULT_AUTO_FIELD` setting. For more, see
|
||||||
:ref:`automatic-primary-key-fields`.
|
:ref:`automatic-primary-key-fields`.
|
||||||
|
|
||||||
``primary_key=True`` implies :attr:`null=False <Field.null>` and
|
``primary_key=True`` implies :attr:`null=False <Field.null>` and
|
||||||
|
@ -428,6 +431,11 @@ The primary key field is read-only. If you change the value of the primary
|
||||||
key on an existing object and then save it, a new object will be created
|
key on an existing object and then save it, a new object will be created
|
||||||
alongside the old one.
|
alongside the old one.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.2
|
||||||
|
|
||||||
|
In older versions, auto-created primary key fields were always
|
||||||
|
:class:`AutoField`\s.
|
||||||
|
|
||||||
``unique``
|
``unique``
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
|
@ -1245,6 +1245,17 @@ format has higher precedence and will be applied instead.
|
||||||
See also :setting:`NUMBER_GROUPING`, :setting:`THOUSAND_SEPARATOR` and
|
See also :setting:`NUMBER_GROUPING`, :setting:`THOUSAND_SEPARATOR` and
|
||||||
:setting:`USE_THOUSAND_SEPARATOR`.
|
:setting:`USE_THOUSAND_SEPARATOR`.
|
||||||
|
|
||||||
|
.. setting:: DEFAULT_AUTO_FIELD
|
||||||
|
|
||||||
|
``DEFAULT_AUTO_FIELD``
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
|
Default: ``'``:class:`django.db.models.AutoField`\ ``'``
|
||||||
|
|
||||||
|
Default primary key field type to use for models that don't have a field with
|
||||||
|
:attr:`primary_key=True <django.db.models.Field.primary_key>`.
|
||||||
|
|
||||||
.. setting:: DEFAULT_CHARSET
|
.. setting:: DEFAULT_CHARSET
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,48 @@ needed. As a consequence, it's deprecated.
|
||||||
|
|
||||||
See :ref:`configuring-applications-ref` for full details.
|
See :ref:`configuring-applications-ref` for full details.
|
||||||
|
|
||||||
|
Customizing type of auto-created primary keys
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
When defining a model, if no field in a model is defined with
|
||||||
|
:attr:`primary_key=True <django.db.models.Field.primary_key>` an implicit
|
||||||
|
primary key is added. The type of this implicit primary key can now be
|
||||||
|
controlled via the :setting:`DEFAULT_AUTO_FIELD` setting and
|
||||||
|
:attr:`AppConfig.default_auto_field <django.apps.AppConfig.default_auto_field>`
|
||||||
|
attribute. No more needing to override primary keys in all models.
|
||||||
|
|
||||||
|
Maintaining the historical behavior, the default value for
|
||||||
|
:setting:`DEFAULT_AUTO_FIELD` is :class:`~django.db.models.AutoField`. Starting
|
||||||
|
with 3.2 new projects are generated with :setting:`DEFAULT_AUTO_FIELD` set to
|
||||||
|
:class:`~django.db.models.BigAutoField`. Also, new apps are generated with
|
||||||
|
:attr:`AppConfig.default_auto_field <django.apps.AppConfig.default_auto_field>`
|
||||||
|
set to :class:`~django.db.models.BigAutoField`. In a future Django release the
|
||||||
|
default value of :setting:`DEFAULT_AUTO_FIELD` will be changed to
|
||||||
|
:class:`~django.db.models.BigAutoField`.
|
||||||
|
|
||||||
|
To avoid unwanted migrations in the future, either explicitly set
|
||||||
|
:setting:`DEFAULT_AUTO_FIELD` to :class:`~django.db.models.AutoField`::
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
||||||
|
or configure it on a per-app basis::
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
class MyAppConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.AutoField'
|
||||||
|
name = 'my_app'
|
||||||
|
|
||||||
|
or on a per-model basis::
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
id = models.AutoField(primary_key=True)
|
||||||
|
|
||||||
|
In anticipation of the changing default, a system check will provide a warning
|
||||||
|
if you do not have an explicit setting for :setting:`DEFAULT_AUTO_FIELD`.
|
||||||
|
|
||||||
``pymemcache`` support
|
``pymemcache`` support
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -259,11 +259,12 @@ details can be found in the :ref:`common model field option reference
|
||||||
Automatic primary key fields
|
Automatic primary key fields
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
By default, Django gives each model the following field::
|
By default, Django gives each model an auto-incrementing primary key with the
|
||||||
|
type specified per app in :attr:`AppConfig.default_auto_field
|
||||||
|
<django.apps.AppConfig.default_auto_field>` or globally in the
|
||||||
|
:setting:`DEFAULT_AUTO_FIELD` setting. For example::
|
||||||
|
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
|
||||||
This is an auto-incrementing primary key.
|
|
||||||
|
|
||||||
If you'd like to specify a custom primary key, specify
|
If you'd like to specify a custom primary key, specify
|
||||||
:attr:`primary_key=True <Field.primary_key>` on one of your fields. If Django
|
:attr:`primary_key=True <Field.primary_key>` on one of your fields. If Django
|
||||||
|
@ -273,6 +274,11 @@ sees you've explicitly set :attr:`Field.primary_key`, it won't add the automatic
|
||||||
Each model requires exactly one field to have :attr:`primary_key=True
|
Each model requires exactly one field to have :attr:`primary_key=True
|
||||||
<Field.primary_key>` (either explicitly declared or automatically added).
|
<Field.primary_key>` (either explicitly declared or automatically added).
|
||||||
|
|
||||||
|
.. versionchanged:: 3.2
|
||||||
|
|
||||||
|
In older versions, auto-created primary key fields were always
|
||||||
|
:class:`AutoField`\s.
|
||||||
|
|
||||||
.. _verbose-field-names:
|
.. _verbose-field-names:
|
||||||
|
|
||||||
Verbose field names
|
Verbose field names
|
||||||
|
|
|
@ -61,6 +61,7 @@ class AdminScriptTestCase(SimpleTestCase):
|
||||||
settings_file.write("%s\n" % extra)
|
settings_file.write("%s\n" % extra)
|
||||||
exports = [
|
exports = [
|
||||||
'DATABASES',
|
'DATABASES',
|
||||||
|
'DEFAULT_AUTO_FIELD',
|
||||||
'ROOT_URLCONF',
|
'ROOT_URLCONF',
|
||||||
'SECRET_KEY',
|
'SECRET_KEY',
|
||||||
]
|
]
|
||||||
|
@ -2188,6 +2189,20 @@ class StartApp(AdminScriptTestCase):
|
||||||
"won't replace conflicting files."
|
"won't replace conflicting files."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_template(self):
|
||||||
|
out, err = self.run_django_admin(['startapp', 'new_app'])
|
||||||
|
self.assertNoOutput(err)
|
||||||
|
app_path = os.path.join(self.test_dir, 'new_app')
|
||||||
|
self.assertIs(os.path.exists(app_path), True)
|
||||||
|
with open(os.path.join(app_path, 'apps.py')) as f:
|
||||||
|
content = f.read()
|
||||||
|
self.assertIn('class NewAppConfig(AppConfig)', content)
|
||||||
|
self.assertIn(
|
||||||
|
"default_auto_field = 'django.db.models.BigAutoField'",
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
self.assertIn("name = 'new_app'", content)
|
||||||
|
|
||||||
|
|
||||||
class DiffSettings(AdminScriptTestCase):
|
class DiffSettings(AdminScriptTestCase):
|
||||||
"""Tests for diffsettings management command."""
|
"""Tests for diffsettings management command."""
|
||||||
|
|
|
@ -31,3 +31,8 @@ class PlainAppsConfig(AppConfig):
|
||||||
class RelabeledAppsConfig(AppConfig):
|
class RelabeledAppsConfig(AppConfig):
|
||||||
name = 'apps'
|
name = 'apps'
|
||||||
label = 'relabeled'
|
label = 'relabeled'
|
||||||
|
|
||||||
|
|
||||||
|
class ModelPKAppsConfig(AppConfig):
|
||||||
|
name = 'apps'
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
|
|
@ -102,8 +102,8 @@ class AppsTests(SimpleTestCase):
|
||||||
def test_no_such_app_config_with_choices(self):
|
def test_no_such_app_config_with_choices(self):
|
||||||
msg = (
|
msg = (
|
||||||
"Module 'apps.apps' does not contain a 'NoSuchConfig' class. "
|
"Module 'apps.apps' does not contain a 'NoSuchConfig' class. "
|
||||||
"Choices are: 'BadConfig', 'MyAdmin', 'MyAuth', 'NoSuchApp', "
|
"Choices are: 'BadConfig', 'ModelPKAppsConfig', 'MyAdmin', "
|
||||||
"'PlainAppsConfig', 'RelabeledAppsConfig'."
|
"'MyAuth', 'NoSuchApp', 'PlainAppsConfig', 'RelabeledAppsConfig'."
|
||||||
)
|
)
|
||||||
with self.assertRaisesMessage(ImportError, msg):
|
with self.assertRaisesMessage(ImportError, msg):
|
||||||
with self.settings(INSTALLED_APPS=['apps.apps.NoSuchConfig']):
|
with self.settings(INSTALLED_APPS=['apps.apps.NoSuchConfig']):
|
||||||
|
@ -436,6 +436,30 @@ class AppConfigTests(SimpleTestCase):
|
||||||
ac = AppConfig('label', Stub(__path__=['a']))
|
ac = AppConfig('label', Stub(__path__=['a']))
|
||||||
self.assertEqual(repr(ac), '<AppConfig: label>')
|
self.assertEqual(repr(ac), '<AppConfig: label>')
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
INSTALLED_APPS=['apps.apps.ModelPKAppsConfig'],
|
||||||
|
DEFAULT_AUTO_FIELD='django.db.models.SmallAutoField',
|
||||||
|
)
|
||||||
|
def test_app_default_auto_field(self):
|
||||||
|
apps_config = apps.get_app_config('apps')
|
||||||
|
self.assertEqual(
|
||||||
|
apps_config.default_auto_field,
|
||||||
|
'django.db.models.BigAutoField',
|
||||||
|
)
|
||||||
|
self.assertIs(apps_config._is_default_auto_field_overridden, True)
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
INSTALLED_APPS=['apps.apps.PlainAppsConfig'],
|
||||||
|
DEFAULT_AUTO_FIELD='django.db.models.SmallAutoField',
|
||||||
|
)
|
||||||
|
def test_default_auto_field_setting(self):
|
||||||
|
apps_config = apps.get_app_config('apps')
|
||||||
|
self.assertEqual(
|
||||||
|
apps_config.default_auto_field,
|
||||||
|
'django.db.models.SmallAutoField',
|
||||||
|
)
|
||||||
|
self.assertIs(apps_config._is_default_auto_field_overridden, False)
|
||||||
|
|
||||||
|
|
||||||
class NamespacePackageAppTests(SimpleTestCase):
|
class NamespacePackageAppTests(SimpleTestCase):
|
||||||
# We need nsapp to be top-level so our multiple-paths tests can add another
|
# We need nsapp to be top-level so our multiple-paths tests can add another
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CheckDefaultPKConfig(AppConfig):
|
||||||
|
name = 'check_framework'
|
||||||
|
|
||||||
|
|
||||||
|
class CheckPKConfig(AppConfig):
|
||||||
|
name = 'check_framework'
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
|
@ -1,3 +1,5 @@
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
from django.core import checks
|
from django.core import checks
|
||||||
from django.core.checks import Error, Warning
|
from django.core.checks import Error, Warning
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -358,3 +360,58 @@ class ConstraintNameTests(TestCase):
|
||||||
constraints = [constraint]
|
constraints = [constraint]
|
||||||
|
|
||||||
self.assertEqual(checks.run_checks(app_configs=apps.get_app_configs()), [])
|
self.assertEqual(checks.run_checks(app_configs=apps.get_app_configs()), [])
|
||||||
|
|
||||||
|
|
||||||
|
def mocked_is_overridden(self, setting):
|
||||||
|
# Force treating DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' as a not
|
||||||
|
# overridden setting.
|
||||||
|
return (
|
||||||
|
setting != 'DEFAULT_AUTO_FIELD' or
|
||||||
|
self.DEFAULT_AUTO_FIELD != 'django.db.models.AutoField'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('django.conf.UserSettingsHolder.is_overridden', mocked_is_overridden)
|
||||||
|
@override_settings(DEFAULT_AUTO_FIELD='django.db.models.AutoField')
|
||||||
|
@isolate_apps('check_framework.apps.CheckDefaultPKConfig', attr_name='apps')
|
||||||
|
@override_system_checks([checks.model_checks.check_all_models])
|
||||||
|
class ModelDefaultAutoFieldTests(SimpleTestCase):
|
||||||
|
def test_auto_created_pk(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(checks.run_checks(app_configs=self.apps.get_app_configs()), [
|
||||||
|
Warning(
|
||||||
|
"Auto-created primary key used when not defining a primary "
|
||||||
|
"key type, by default 'django.db.models.AutoField'.",
|
||||||
|
hint=(
|
||||||
|
"Configure the DEFAULT_AUTO_FIELD setting or the "
|
||||||
|
"CheckDefaultPKConfig.default_auto_field attribute to "
|
||||||
|
"point to a subclass of AutoField, e.g. "
|
||||||
|
"'django.db.models.BigAutoField'."
|
||||||
|
),
|
||||||
|
obj=Model,
|
||||||
|
id='models.W042',
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
@override_settings(DEFAULT_AUTO_FIELD='django.db.models.BigAutoField')
|
||||||
|
def test_default_auto_field_setting(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(checks.run_checks(app_configs=self.apps.get_app_configs()), [])
|
||||||
|
|
||||||
|
def test_explicit_pk(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
|
||||||
|
self.assertEqual(checks.run_checks(app_configs=self.apps.get_app_configs()), [])
|
||||||
|
|
||||||
|
@isolate_apps('check_framework.apps.CheckPKConfig', kwarg_name='apps')
|
||||||
|
def test_app_default_auto_field(self, apps):
|
||||||
|
class ModelWithPkViaAppConfig(models.Model):
|
||||||
|
class Meta:
|
||||||
|
app_label = 'check_framework.apps.CheckPKConfig'
|
||||||
|
|
||||||
|
self.assertEqual(checks.run_checks(app_configs=apps.get_app_configs()), [])
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ModelDefaultPKConfig(AppConfig):
|
||||||
|
name = 'model_options'
|
||||||
|
|
||||||
|
|
||||||
|
class ModelPKConfig(AppConfig):
|
||||||
|
name = 'model_options'
|
||||||
|
default_auto_field = 'django.db.models.SmallAutoField'
|
||||||
|
|
||||||
|
|
||||||
|
class ModelPKNonAutoConfig(AppConfig):
|
||||||
|
name = 'model_options'
|
||||||
|
default_auto_field = 'django.db.models.TextField'
|
||||||
|
|
||||||
|
|
||||||
|
class ModelPKNoneConfig(AppConfig):
|
||||||
|
name = 'model_options'
|
||||||
|
default_auto_field = None
|
||||||
|
|
||||||
|
|
||||||
|
class ModelPKNonexistentConfig(AppConfig):
|
||||||
|
name = 'model_options'
|
||||||
|
default_auto_field = 'django.db.models.NonexistentAutoField'
|
|
@ -0,0 +1,101 @@
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.db import models
|
||||||
|
from django.test import SimpleTestCase, override_settings
|
||||||
|
from django.test.utils import isolate_apps
|
||||||
|
|
||||||
|
|
||||||
|
@isolate_apps('model_options')
|
||||||
|
class TestDefaultPK(SimpleTestCase):
|
||||||
|
@override_settings(DEFAULT_AUTO_FIELD='django.db.models.NonexistentAutoField')
|
||||||
|
def test_default_auto_field_setting_nonexistent(self):
|
||||||
|
msg = (
|
||||||
|
"DEFAULT_AUTO_FIELD refers to the module "
|
||||||
|
"'django.db.models.NonexistentAutoField' that could not be "
|
||||||
|
"imported."
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||||
|
class Model(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@isolate_apps('model_options.apps.ModelPKNonexistentConfig')
|
||||||
|
def test_app_default_auto_field_nonexistent(self):
|
||||||
|
msg = (
|
||||||
|
"model_options.apps.ModelPKNonexistentConfig.default_auto_field "
|
||||||
|
"refers to the module 'django.db.models.NonexistentAutoField' "
|
||||||
|
"that could not be imported."
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||||
|
class Model(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@override_settings(DEFAULT_AUTO_FIELD='django.db.models.TextField')
|
||||||
|
def test_default_auto_field_setting_non_auto(self):
|
||||||
|
msg = (
|
||||||
|
"Primary key 'django.db.models.TextField' referred by "
|
||||||
|
"DEFAULT_AUTO_FIELD must subclass AutoField."
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
class Model(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@isolate_apps('model_options.apps.ModelPKNonAutoConfig')
|
||||||
|
def test_app_default_auto_field_non_auto(self):
|
||||||
|
msg = (
|
||||||
|
"Primary key 'django.db.models.TextField' referred by "
|
||||||
|
"model_options.apps.ModelPKNonAutoConfig.default_auto_field must "
|
||||||
|
"subclass AutoField."
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
class Model(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@override_settings(DEFAULT_AUTO_FIELD=None)
|
||||||
|
def test_default_auto_field_setting_none(self):
|
||||||
|
msg = 'DEFAULT_AUTO_FIELD must not be empty.'
|
||||||
|
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||||
|
class Model(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@isolate_apps('model_options.apps.ModelPKNoneConfig')
|
||||||
|
def test_app_default_auto_field_none(self):
|
||||||
|
msg = (
|
||||||
|
'model_options.apps.ModelPKNoneConfig.default_auto_field must not '
|
||||||
|
'be empty.'
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||||
|
class Model(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@isolate_apps('model_options.apps.ModelDefaultPKConfig')
|
||||||
|
@override_settings(DEFAULT_AUTO_FIELD='django.db.models.SmallAutoField')
|
||||||
|
def test_default_auto_field_setting(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertIsInstance(Model._meta.pk, models.SmallAutoField)
|
||||||
|
|
||||||
|
@isolate_apps('model_options.apps.ModelPKConfig')
|
||||||
|
@override_settings(DEFAULT_AUTO_FIELD='django.db.models.AutoField')
|
||||||
|
def test_app_default_auto_field(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertIsInstance(Model._meta.pk, models.SmallAutoField)
|
||||||
|
|
||||||
|
@isolate_apps('model_options.apps.ModelDefaultPKConfig')
|
||||||
|
@override_settings(DEFAULT_AUTO_FIELD='django.db.models.SmallAutoField')
|
||||||
|
def test_m2m_default_auto_field_setting(self):
|
||||||
|
class M2MModel(models.Model):
|
||||||
|
m2m = models.ManyToManyField('self')
|
||||||
|
|
||||||
|
m2m_pk = M2MModel._meta.get_field('m2m').remote_field.through._meta.pk
|
||||||
|
self.assertIsInstance(m2m_pk, models.SmallAutoField)
|
||||||
|
|
||||||
|
@isolate_apps('model_options.apps.ModelPKConfig')
|
||||||
|
@override_settings(DEFAULT_AUTO_FIELD='django.db.models.AutoField')
|
||||||
|
def test_m2m_app_default_auto_field(self):
|
||||||
|
class M2MModel(models.Model):
|
||||||
|
m2m = models.ManyToManyField('self')
|
||||||
|
|
||||||
|
m2m_pk = M2MModel._meta.get_field('m2m').remote_field.through._meta.pk
|
||||||
|
self.assertIsInstance(m2m_pk, models.SmallAutoField)
|
|
@ -27,3 +27,5 @@ SECRET_KEY = "django_tests_secret_key"
|
||||||
PASSWORD_HASHERS = [
|
PASSWORD_HASHERS = [
|
||||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
Loading…
Reference in New Issue