mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	[1.10.x] Fixed #26643 -- Prevented unnecessary AlterModelManagers operations caused by the manager inheritance refactor.
This also makes migrations respect the base_manager_name and
default_manager_name model options.
Thanks Anthony King and Matthew Schinckel for the initial patches.
Backport of 2eb7cb2fff from master
			
			
This commit is contained in:
		| @@ -663,6 +663,8 @@ class AlterModelOptions(ModelOptionOperation): | |||||||
|  |  | ||||||
|     # Model options we want to compare and preserve in an AlterModelOptions op |     # Model options we want to compare and preserve in an AlterModelOptions op | ||||||
|     ALTER_OPTION_KEYS = [ |     ALTER_OPTION_KEYS = [ | ||||||
|  |         "base_manager_name", | ||||||
|  |         "default_manager_name", | ||||||
|         "get_latest_by", |         "get_latest_by", | ||||||
|         "managed", |         "managed", | ||||||
|         "ordering", |         "ordering", | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
|  |  | ||||||
| import copy | import copy | ||||||
|  | import warnings | ||||||
| from collections import OrderedDict | from collections import OrderedDict | ||||||
| from contextlib import contextmanager | from contextlib import contextmanager | ||||||
|  |  | ||||||
| @@ -13,6 +14,7 @@ from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT | |||||||
| from django.db.models.options import DEFAULT_NAMES, normalize_together | from django.db.models.options import DEFAULT_NAMES, normalize_together | ||||||
| from django.db.models.utils import make_model_tuple | from django.db.models.utils import make_model_tuple | ||||||
| from django.utils import six | from django.utils import six | ||||||
|  | from django.utils.deprecation import RemovedInDjango20Warning | ||||||
| from django.utils.encoding import force_text, smart_text | from django.utils.encoding import force_text, smart_text | ||||||
| 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.module_loading import import_string | ||||||
| @@ -444,28 +446,24 @@ class ModelState(object): | |||||||
|             bases = (models.Model,) |             bases = (models.Model,) | ||||||
|  |  | ||||||
|         managers = [] |         managers = [] | ||||||
|  |         default_manager_shim = None | ||||||
|         # Make sure the default manager is always first since ordering chooses |  | ||||||
|         # the default manager. |  | ||||||
|         if not model._default_manager.auto_created: |  | ||||||
|             if model._default_manager.use_in_migrations: |  | ||||||
|                 default_manager = copy.copy(model._default_manager) |  | ||||||
|                 default_manager._set_creation_counter() |  | ||||||
|  |  | ||||||
|             # If the default manager doesn't have `use_in_migrations = True`, |  | ||||||
|             # shim a default manager so another manager isn't promoted in its |  | ||||||
|             # place. |  | ||||||
|             else: |  | ||||||
|                 default_manager = models.Manager() |  | ||||||
|                 default_manager.model = model |  | ||||||
|                 default_manager.name = model._default_manager.name |  | ||||||
|             managers.append((force_text(default_manager.name), default_manager)) |  | ||||||
|  |  | ||||||
|         for manager in model._meta.managers: |         for manager in model._meta.managers: | ||||||
|             if manager.use_in_migrations and manager is not model._default_manager: |             if manager.use_in_migrations: | ||||||
|                 manager = copy.copy(manager) |                 new_manager = copy.copy(manager) | ||||||
|                 manager._set_creation_counter() |                 new_manager._set_creation_counter() | ||||||
|                 managers.append((force_text(manager.name), manager)) |             elif manager is model._base_manager or manager is model._default_manager: | ||||||
|  |                 new_manager = models.Manager() | ||||||
|  |                 new_manager.model = manager.model | ||||||
|  |                 new_manager.name = manager.name | ||||||
|  |                 if manager is model._default_manager: | ||||||
|  |                     default_manager_shim = new_manager | ||||||
|  |             else: | ||||||
|  |                 continue | ||||||
|  |             managers.append((force_text(manager.name), new_manager)) | ||||||
|  |  | ||||||
|  |         # Ignore a shimmed default manager called objects if it's the only one. | ||||||
|  |         if managers == [('objects', default_manager_shim)]: | ||||||
|  |             managers = [] | ||||||
|  |  | ||||||
|         # Construct the new ModelState |         # Construct the new ModelState | ||||||
|         return cls( |         return cls( | ||||||
| @@ -541,12 +539,17 @@ class ModelState(object): | |||||||
|         # Restore managers |         # Restore managers | ||||||
|         body.update(self.construct_managers()) |         body.update(self.construct_managers()) | ||||||
|  |  | ||||||
|         # Then, make a Model object (apps.register_model is called in __new__) |         with warnings.catch_warnings(): | ||||||
|         return type( |             warnings.filterwarnings( | ||||||
|             str(self.name), |                 "ignore", "Managers from concrete parents will soon qualify as default managers", | ||||||
|             bases, |                 RemovedInDjango20Warning) | ||||||
|             body, |  | ||||||
|         ) |             # Then, make a Model object (apps.register_model is called in __new__) | ||||||
|  |             return type( | ||||||
|  |                 str(self.name), | ||||||
|  |                 bases, | ||||||
|  |                 body, | ||||||
|  |             ) | ||||||
|  |  | ||||||
|     def get_field_by_name(self, name): |     def get_field_by_name(self, name): | ||||||
|         for fname, field in self.fields: |         for fname, field in self.fields: | ||||||
|   | |||||||
| @@ -191,6 +191,78 @@ class StateTests(SimpleTestCase): | |||||||
|         author_state = project_state.models['migrations', 'author'] |         author_state = project_state.models['migrations', 'author'] | ||||||
|         self.assertEqual(author_state.managers, [('authors', custom_manager)]) |         self.assertEqual(author_state.managers, [('authors', custom_manager)]) | ||||||
|  |  | ||||||
|  |     def test_custom_default_manager_named_objects_with_false_migration_flag(self): | ||||||
|  |         """ | ||||||
|  |         When a manager is added with a name of 'objects' but it does not | ||||||
|  |         have `use_in_migrations = True`, no migration should be added to the | ||||||
|  |         model state (#26643). | ||||||
|  |         """ | ||||||
|  |         new_apps = Apps(['migrations']) | ||||||
|  |  | ||||||
|  |         class Author(models.Model): | ||||||
|  |             objects = models.Manager() | ||||||
|  |  | ||||||
|  |             class Meta: | ||||||
|  |                 app_label = 'migrations' | ||||||
|  |                 apps = new_apps | ||||||
|  |  | ||||||
|  |         project_state = ProjectState.from_apps(new_apps) | ||||||
|  |         author_state = project_state.models['migrations', 'author'] | ||||||
|  |         self.assertEqual(author_state.managers, []) | ||||||
|  |  | ||||||
|  |     def test_custom_default_manager(self): | ||||||
|  |         new_apps = Apps(['migrations']) | ||||||
|  |  | ||||||
|  |         class Author(models.Model): | ||||||
|  |             manager1 = models.Manager() | ||||||
|  |             manager2 = models.Manager() | ||||||
|  |  | ||||||
|  |             class Meta: | ||||||
|  |                 app_label = 'migrations' | ||||||
|  |                 apps = new_apps | ||||||
|  |                 default_manager_name = 'manager2' | ||||||
|  |  | ||||||
|  |         project_state = ProjectState.from_apps(new_apps) | ||||||
|  |         author_state = project_state.models['migrations', 'author'] | ||||||
|  |         self.assertEqual(author_state.options['default_manager_name'], 'manager2') | ||||||
|  |         self.assertEqual(author_state.managers, [('manager2', Author.manager1)]) | ||||||
|  |  | ||||||
|  |     def test_custom_base_manager(self): | ||||||
|  |         new_apps = Apps(['migrations']) | ||||||
|  |  | ||||||
|  |         class Author(models.Model): | ||||||
|  |             manager1 = models.Manager() | ||||||
|  |             manager2 = models.Manager() | ||||||
|  |  | ||||||
|  |             class Meta: | ||||||
|  |                 app_label = 'migrations' | ||||||
|  |                 apps = new_apps | ||||||
|  |                 base_manager_name = 'manager2' | ||||||
|  |  | ||||||
|  |         class Author2(models.Model): | ||||||
|  |             manager1 = models.Manager() | ||||||
|  |             manager2 = models.Manager() | ||||||
|  |  | ||||||
|  |             class Meta: | ||||||
|  |                 app_label = 'migrations' | ||||||
|  |                 apps = new_apps | ||||||
|  |                 base_manager_name = 'manager1' | ||||||
|  |  | ||||||
|  |         project_state = ProjectState.from_apps(new_apps) | ||||||
|  |  | ||||||
|  |         author_state = project_state.models['migrations', 'author'] | ||||||
|  |         self.assertEqual(author_state.options['base_manager_name'], 'manager2') | ||||||
|  |         self.assertEqual(author_state.managers, [ | ||||||
|  |             ('manager1', Author.manager1), | ||||||
|  |             ('manager2', Author.manager2), | ||||||
|  |         ]) | ||||||
|  |  | ||||||
|  |         author2_state = project_state.models['migrations', 'author2'] | ||||||
|  |         self.assertEqual(author2_state.options['base_manager_name'], 'manager1') | ||||||
|  |         self.assertEqual(author2_state.managers, [ | ||||||
|  |             ('manager1', Author2.manager1), | ||||||
|  |         ]) | ||||||
|  |  | ||||||
|     def test_apps_bulk_update(self): |     def test_apps_bulk_update(self): | ||||||
|         """ |         """ | ||||||
|         StateApps.bulk_update() should update apps.ready to False and reset |         StateApps.bulk_update() should update apps.ready to False and reset | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user