mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							b960e4ed72
						
					
				
				
					commit
					b5e12d490a
				
			| @@ -5,6 +5,7 @@ from importlib import import_module | ||||
|  | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| from django.utils.deprecation import RemovedInDjango41Warning | ||||
| from django.utils.functional import cached_property | ||||
| from django.utils.module_loading import import_string, module_has_submodule | ||||
|  | ||||
| APPS_MODULE_NAME = 'apps' | ||||
| @@ -55,6 +56,15 @@ class AppConfig: | ||||
|     def __repr__(self): | ||||
|         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): | ||||
|         """Attempt to determine app's filesystem path from its module.""" | ||||
|         # 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): | ||||
|     default_auto_field = 'django.db.models.BigAutoField' | ||||
|     name = '{{ app_name }}' | ||||
|   | ||||
| @@ -414,6 +414,9 @@ THOUSAND_SEPARATOR = ',' | ||||
| DEFAULT_TABLESPACE = '' | ||||
| DEFAULT_INDEX_TABLESPACE = '' | ||||
|  | ||||
| # Default primary key field type. | ||||
| DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' | ||||
|  | ||||
| # Default X-Frame-Options header value | ||||
| X_FRAME_OPTIONS = 'DENY' | ||||
|  | ||||
|   | ||||
| @@ -118,3 +118,8 @@ USE_TZ = True | ||||
| # https://docs.djangoproject.com/en/{{ docs_version }}/howto/static-files/ | ||||
|  | ||||
| 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): | ||||
|     """Simple AppConfig which does not do automatic discovery.""" | ||||
|  | ||||
|     default_auto_field = 'django.db.models.AutoField' | ||||
|     default_site = 'django.contrib.admin.sites.AdminSite' | ||||
|     name = 'django.contrib.admin' | ||||
|     verbose_name = _("Administration") | ||||
|   | ||||
| @@ -11,6 +11,7 @@ from .signals import user_logged_in | ||||
|  | ||||
|  | ||||
| class AuthConfig(AppConfig): | ||||
|     default_auto_field = 'django.db.models.AutoField' | ||||
|     name = 'django.contrib.auth' | ||||
|     verbose_name = _("Authentication and Authorization") | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,7 @@ from .management import ( | ||||
|  | ||||
|  | ||||
| class ContentTypesConfig(AppConfig): | ||||
|     default_auto_field = 'django.db.models.AutoField' | ||||
|     name = 'django.contrib.contenttypes' | ||||
|     verbose_name = _("Content Types") | ||||
|  | ||||
|   | ||||
| @@ -3,5 +3,6 @@ from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
|  | ||||
| class FlatPagesConfig(AppConfig): | ||||
|     default_auto_field = 'django.db.models.AutoField' | ||||
|     name = 'django.contrib.flatpages' | ||||
|     verbose_name = _("Flat Pages") | ||||
|   | ||||
| @@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
|  | ||||
| class GISConfig(AppConfig): | ||||
|     default_auto_field = 'django.db.models.AutoField' | ||||
|     name = 'django.contrib.gis' | ||||
|     verbose_name = _("GIS") | ||||
|  | ||||
|   | ||||
| @@ -3,5 +3,6 @@ from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
|  | ||||
| class RedirectsConfig(AppConfig): | ||||
|     default_auto_field = 'django.db.models.AutoField' | ||||
|     name = 'django.contrib.redirects' | ||||
|     verbose_name = _("Redirects") | ||||
|   | ||||
| @@ -3,5 +3,6 @@ from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
|  | ||||
| class SiteMapsConfig(AppConfig): | ||||
|     default_auto_field = 'django.db.models.AutoField' | ||||
|     name = 'django.contrib.sitemaps' | ||||
|     verbose_name = _("Site Maps") | ||||
|   | ||||
| @@ -8,6 +8,7 @@ from .management import create_default_site | ||||
|  | ||||
|  | ||||
| class SitesConfig(AppConfig): | ||||
|     default_auto_field = 'django.db.models.AutoField' | ||||
|     name = 'django.contrib.sites' | ||||
|     verbose_name = _("Sites") | ||||
|  | ||||
|   | ||||
| @@ -1290,10 +1290,35 @@ class Model(metaclass=ModelBase): | ||||
|                 *cls._check_indexes(databases), | ||||
|                 *cls._check_ordering(), | ||||
|                 *cls._check_constraints(databases), | ||||
|                 *cls._check_default_pk(), | ||||
|             ] | ||||
|  | ||||
|         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 | ||||
|     def _check_swappable(cls): | ||||
|         """Check if the swapped model exists.""" | ||||
|   | ||||
| @@ -5,12 +5,13 @@ from collections import defaultdict | ||||
|  | ||||
| from django.apps import apps | ||||
| 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.models import AutoField, Manager, OrderWrt, UniqueConstraint | ||||
| from django.db.models.query_utils import PathInfo | ||||
| from django.utils.datastructures import ImmutableList, OrderedSet | ||||
| 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.translation import override | ||||
|  | ||||
| @@ -217,6 +218,37 @@ class Options: | ||||
|             new_objs.append(obj) | ||||
|         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): | ||||
|         if self.order_with_respect_to: | ||||
|             # The app registry will not be ready at this point, so we cannot | ||||
| @@ -250,7 +282,8 @@ class Options: | ||||
|                 field.primary_key = True | ||||
|                 self.setup_pk(field) | ||||
|             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) | ||||
|  | ||||
|     def add_manager(self, manager): | ||||
|   | ||||
| @@ -90,6 +90,7 @@ would provide a proper name for the admin:: | ||||
|     from django.apps import AppConfig | ||||
|  | ||||
|     class RockNRollConfig(AppConfig): | ||||
|         default_auto_field = 'django.db.models.BigAutoField' | ||||
|         name = 'rock_n_roll' | ||||
|         verbose_name = "Rock ’n’ roll" | ||||
|  | ||||
| @@ -219,6 +220,16 @@ Configurable attributes | ||||
|  | ||||
|     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 | ||||
| -------------------- | ||||
|  | ||||
|   | ||||
| @@ -378,6 +378,8 @@ Models | ||||
| * **models.W040**: ``<database>`` does not support indexes with non-key | ||||
|   columns. | ||||
| * **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 | ||||
| -------- | ||||
|   | ||||
| @@ -415,9 +415,12 @@ cross-site scripting attack. | ||||
| 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 | ||||
| will automatically add an :class:`AutoField` to hold the primary key, so you | ||||
| don't need to set ``primary_key=True`` on any of your fields unless you want to | ||||
| override the default primary-key behavior. For more, see | ||||
| will automatically add a field to hold the primary key, so you don't need to | ||||
| set ``primary_key=True`` on any of your fields unless you want to override the | ||||
| 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`. | ||||
|  | ||||
| ``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 | ||||
| alongside the old one. | ||||
|  | ||||
| .. versionchanged:: 3.2 | ||||
|  | ||||
|     In older versions, auto-created primary key fields were always | ||||
|     :class:`AutoField`\s. | ||||
|  | ||||
| ``unique`` | ||||
| ---------- | ||||
|  | ||||
|   | ||||
| @@ -1245,6 +1245,17 @@ format has higher precedence and will be applied instead. | ||||
| See also :setting:`NUMBER_GROUPING`, :setting:`THOUSAND_SEPARATOR` and | ||||
| :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 | ||||
|  | ||||
|   | ||||
| @@ -53,6 +53,48 @@ needed. As a consequence, it's deprecated. | ||||
|  | ||||
| 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 | ||||
| ---------------------- | ||||
|  | ||||
|   | ||||
| @@ -259,11 +259,12 @@ details can be found in the :ref:`common model field option reference | ||||
| 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) | ||||
|  | ||||
| This is an auto-incrementing primary key. | ||||
|     id = models.BigAutoField(primary_key=True) | ||||
|  | ||||
| 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 | ||||
| @@ -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 | ||||
| <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 | ||||
|   | ||||
| @@ -61,6 +61,7 @@ class AdminScriptTestCase(SimpleTestCase): | ||||
|                 settings_file.write("%s\n" % extra) | ||||
|             exports = [ | ||||
|                 'DATABASES', | ||||
|                 'DEFAULT_AUTO_FIELD', | ||||
|                 'ROOT_URLCONF', | ||||
|                 'SECRET_KEY', | ||||
|             ] | ||||
| @@ -2188,6 +2189,20 @@ class StartApp(AdminScriptTestCase): | ||||
|             "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): | ||||
|     """Tests for diffsettings management command.""" | ||||
|   | ||||
| @@ -31,3 +31,8 @@ class PlainAppsConfig(AppConfig): | ||||
| class RelabeledAppsConfig(AppConfig): | ||||
|     name = 'apps' | ||||
|     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): | ||||
|         msg = ( | ||||
|             "Module 'apps.apps' does not contain a 'NoSuchConfig' class. " | ||||
|             "Choices are: 'BadConfig', 'MyAdmin', 'MyAuth', 'NoSuchApp', " | ||||
|             "'PlainAppsConfig', 'RelabeledAppsConfig'." | ||||
|             "Choices are: 'BadConfig', 'ModelPKAppsConfig', 'MyAdmin', " | ||||
|             "'MyAuth', 'NoSuchApp', 'PlainAppsConfig', 'RelabeledAppsConfig'." | ||||
|         ) | ||||
|         with self.assertRaisesMessage(ImportError, msg): | ||||
|             with self.settings(INSTALLED_APPS=['apps.apps.NoSuchConfig']): | ||||
| @@ -436,6 +436,30 @@ class AppConfigTests(SimpleTestCase): | ||||
|         ac = AppConfig('label', Stub(__path__=['a'])) | ||||
|         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): | ||||
|     # We need nsapp to be top-level so our multiple-paths tests can add another | ||||
|   | ||||
							
								
								
									
										10
									
								
								tests/check_framework/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								tests/check_framework/apps.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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.checks import Error, Warning | ||||
| from django.db import models | ||||
| @@ -358,3 +360,58 @@ class ConstraintNameTests(TestCase): | ||||
|                 constraints = [constraint] | ||||
|  | ||||
|         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()), []) | ||||
|   | ||||
							
								
								
									
										25
									
								
								tests/model_options/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/model_options/apps.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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' | ||||
							
								
								
									
										101
									
								
								tests/model_options/test_default_pk.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								tests/model_options/test_default_pk.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 = [ | ||||
|     'django.contrib.auth.hashers.MD5PasswordHasher', | ||||
| ] | ||||
|  | ||||
| DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user