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)
|
field_class = import_string(path)
|
||||||
yield name, field_class(*args, **kwargs)
|
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):
|
def clone(self):
|
||||||
"Returns an exact copy of this ModelState"
|
"Returns an exact copy of this ModelState"
|
||||||
return self.__class__(
|
return self.__class__(
|
||||||
|
@ -408,7 +421,7 @@ class ModelState(object):
|
||||||
fields=list(self.construct_fields()),
|
fields=list(self.construct_fields()),
|
||||||
options=dict(self.options),
|
options=dict(self.options),
|
||||||
bases=self.bases,
|
bases=self.bases,
|
||||||
managers=self.managers,
|
managers=list(self.construct_managers()),
|
||||||
)
|
)
|
||||||
|
|
||||||
def render(self, apps):
|
def render(self, apps):
|
||||||
|
@ -431,8 +444,7 @@ class ModelState(object):
|
||||||
body['__module__'] = "__fake__"
|
body['__module__'] = "__fake__"
|
||||||
|
|
||||||
# Restore managers
|
# Restore managers
|
||||||
for mgr_name, manager in self.managers:
|
body.update(self.construct_managers())
|
||||||
body[mgr_name] = manager
|
|
||||||
|
|
||||||
# Then, make a Model object (apps.register_model is called in __new__)
|
# Then, make a Model object (apps.register_model is called in __new__)
|
||||||
return type(
|
return type(
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from django.apps.registry import Apps
|
from django.apps.registry import Apps
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.migrations.operations import RemoveField
|
||||||
from django.db.migrations.state import ProjectState, ModelState, InvalidBasesError
|
from django.db.migrations.state import ProjectState, ModelState, InvalidBasesError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
@ -505,6 +506,43 @@ class StateTests(TestCase):
|
||||||
["id", "author"],
|
["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):
|
class ModelStateTests(TestCase):
|
||||||
def test_custom_model_base(self):
|
def test_custom_model_base(self):
|
||||||
|
|
Loading…
Reference in New Issue