Commit 039d7881 authored by Marten Kenbeek's avatar Marten Kenbeek Committed by Tim Graham
Browse files

Refs #24397 -- Sped up model reloading in ProjectState.

Created bulk_update() context manager on StateApps. Sped up unregistering
models in reload_models() by using this context mananger.
parent 6387d9d4
Loading
Loading
Loading
Loading
+33 −25
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ from __future__ import unicode_literals

import copy
from collections import OrderedDict
from contextlib import contextmanager

from django.apps import AppConfig
from django.apps.registry import Apps, apps as global_apps
@@ -111,11 +112,9 @@ class ProjectState(object):
            related_models.add((app_label, model_name))

            # Unregister all related models
            with self.apps.bulk_update():
                for rel_app_label, rel_model_name in related_models:
                    self.apps.unregister_model(rel_app_label, rel_model_name)
            # Need to do it once all models are unregistered to avoid corrupting
            # existing models' _meta
            self.apps.clear_cache()

            states_to_be_rendered = []
            # Gather all models states of those models that will be rerendered.
@@ -226,6 +225,18 @@ class StateApps(Apps):
            labels = (".".join(model_key) for model_key in self._pending_operations)
            raise ValueError(msg % ", ".join(labels))

    @contextmanager
    def bulk_update(self):
        # Avoid clearing each model's cache for each change. Instead, clear
        # all caches when we're finished updating the model instances.
        ready = self.ready
        self.ready = False
        try:
            yield
        finally:
            self.ready = ready
            self.clear_cache()

    def render_multiple(self, model_states):
        # We keep trying to render the models in a loop, ignoring invalid
        # base errors, until the size of the unrendered models doesn't
@@ -234,7 +245,7 @@ class StateApps(Apps):
        if not model_states:
            return
        # Prevent that all model caches are expired for each render.
        self.ready = False
        with self.bulk_update():
            unrendered_models = model_states
            while unrendered_models:
                new_unrendered_models = []
@@ -244,7 +255,6 @@ class StateApps(Apps):
                    except InvalidBasesError:
                        new_unrendered_models.append(model)
                if len(new_unrendered_models) == len(unrendered_models):
                self.ready = True
                    raise InvalidBasesError(
                        "Cannot resolve bases for %r\nThis can happen if you are inheriting models from an "
                        "app with migrations (e.g. contrib.auth)\n in an app with no migrations; see "
@@ -252,8 +262,6 @@ class StateApps(Apps):
                        "for more" % (new_unrendered_models, get_docs_version())
                    )
                unrendered_models = new_unrendered_models
        self.ready = True
        self.clear_cache()

    def clone(self):
        """
+16 −0
Original line number Diff line number Diff line
@@ -160,6 +160,22 @@ class StateTests(TestCase):
        self.assertEqual([mgr.args for name, mgr in food_order_manager_state.managers],
                         [('a', 'b', 1, 2), ('x', 'y', 3, 4)])

    def test_apps_bulk_update(self):
        """
        StateApps.bulk_update() should update apps.ready to False and reset
        the value afterwards.
        """
        project_state = ProjectState()
        apps = project_state.apps
        with apps.bulk_update():
            self.assertFalse(apps.ready)
        self.assertTrue(apps.ready)
        with self.assertRaises(ValueError):
            with apps.bulk_update():
                self.assertFalse(apps.ready)
                raise ValueError()
        self.assertTrue(apps.ready)

    def test_render(self):
        """
        Tests rendering a ProjectState into an Apps.