Fixed #24147 -- Prevented managers leaking model during migrations

Thanks Tim Graham for the review.
This commit is contained in:
Markus Holtermann 2015-01-13 18:18:23 +01:00
parent ec7ef5afbb
commit 88786afbff
2 changed files with 53 additions and 3 deletions

View File

@ -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(

View File

@ -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):