Commit 88786afb authored by Markus Holtermann's avatar Markus Holtermann
Browse files

Fixed #24147 -- Prevented managers leaking model during migrations

Thanks Tim Graham for the review.
parent ec7ef5af
Loading
Loading
Loading
Loading
+15 −3
Original line number Diff line number Diff line
@@ -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(
+38 −0
Original line number Diff line number Diff line
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):