Fixed #24147 -- Prevented managers leaking model during migrations
Thanks Tim Graham for the review.
This commit is contained in:
parent
ec7ef5afbb
commit
88786afbff
|
@ -400,6 +400,19 @@ class ModelState(object):
|
|||
field_class = import_string(path)
|
||||
yield name, field_class(*args, **kwargs)
|
||||
|
||||
def construct_managers(self):
|
||||
"Deep-clone the managers using deconstruction"
|
||||
# Sort all managers by their creation counter
|
||||
sorted_managers = sorted(self.managers, key=lambda v: v[1].creation_counter)
|
||||
for mgr_name, manager in sorted_managers:
|
||||
as_manager, manager_path, qs_path, args, kwargs = manager.deconstruct()
|
||||
if as_manager:
|
||||
qs_class = import_string(qs_path)
|
||||
yield mgr_name, qs_class.as_manager()
|
||||
else:
|
||||
manager_class = import_string(manager_path)
|
||||
yield mgr_name, manager_class(*args, **kwargs)
|
||||
|
||||
def clone(self):
|
||||
"Returns an exact copy of this ModelState"
|
||||
return self.__class__(
|
||||
|
@ -408,7 +421,7 @@ class ModelState(object):
|
|||
fields=list(self.construct_fields()),
|
||||
options=dict(self.options),
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
managers=list(self.construct_managers()),
|
||||
)
|
||||
|
||||
def render(self, apps):
|
||||
|
@ -431,8 +444,7 @@ class ModelState(object):
|
|||
body['__module__'] = "__fake__"
|
||||
|
||||
# Restore managers
|
||||
for mgr_name, manager in self.managers:
|
||||
body[mgr_name] = manager
|
||||
body.update(self.construct_managers())
|
||||
|
||||
# Then, make a Model object (apps.register_model is called in __new__)
|
||||
return type(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.apps.registry import Apps
|
||||
from django.db import models
|
||||
from django.db.migrations.operations import RemoveField
|
||||
from django.db.migrations.state import ProjectState, ModelState, InvalidBasesError
|
||||
from django.test import TestCase
|
||||
|
||||
|
@ -505,6 +506,43 @@ class StateTests(TestCase):
|
|||
["id", "author"],
|
||||
)
|
||||
|
||||
def test_manager_refer_correct_model_version(self):
|
||||
"""
|
||||
#24147 - Tests that managers refer to the correct version of a
|
||||
historical model
|
||||
"""
|
||||
project_state = ProjectState()
|
||||
project_state.add_model(ModelState(
|
||||
app_label="migrations",
|
||||
name="Tag",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True)),
|
||||
("hidden", models.BooleanField()),
|
||||
],
|
||||
managers=[
|
||||
('food_mgr', FoodManager('a', 'b')),
|
||||
('food_qs', FoodQuerySet.as_manager()),
|
||||
]
|
||||
))
|
||||
|
||||
old_model = project_state.apps.get_model('migrations', 'tag')
|
||||
|
||||
new_state = project_state.clone()
|
||||
operation = RemoveField("tag", "hidden")
|
||||
operation.state_forwards("migrations", new_state)
|
||||
|
||||
new_model = new_state.apps.get_model('migrations', 'tag')
|
||||
|
||||
self.assertIsNot(old_model, new_model)
|
||||
self.assertIs(old_model, old_model.food_mgr.model)
|
||||
self.assertIs(old_model, old_model.food_qs.model)
|
||||
self.assertIs(new_model, new_model.food_mgr.model)
|
||||
self.assertIs(new_model, new_model.food_qs.model)
|
||||
self.assertIsNot(old_model.food_mgr, new_model.food_mgr)
|
||||
self.assertIsNot(old_model.food_qs, new_model.food_qs)
|
||||
self.assertIsNot(old_model.food_mgr.model, new_model.food_mgr.model)
|
||||
self.assertIsNot(old_model.food_qs.model, new_model.food_qs.model)
|
||||
|
||||
|
||||
class ModelStateTests(TestCase):
|
||||
def test_custom_model_base(self):
|
||||
|
|
Loading…
Reference in New Issue