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.
This commit is contained in:
parent
a12826bba7
commit
2eb7cb2fff
|
@ -665,6 +665,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
|
||||||
|
|
Loading…
Reference in New Issue