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.
This commit is contained in:
Marten Kenbeek 2015-04-06 18:02:46 +02:00 committed by Tim Graham
parent 6387d9d41f
commit 039d7881b4
2 changed files with 49 additions and 25 deletions

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
import copy import copy
from collections import OrderedDict from collections import OrderedDict
from contextlib import contextmanager
from django.apps import AppConfig from django.apps import AppConfig
from django.apps.registry import Apps, apps as global_apps from django.apps.registry import Apps, apps as global_apps
@ -111,11 +112,9 @@ class ProjectState(object):
related_models.add((app_label, model_name)) related_models.add((app_label, model_name))
# Unregister all related models # Unregister all related models
for rel_app_label, rel_model_name in related_models: with self.apps.bulk_update():
self.apps.unregister_model(rel_app_label, rel_model_name) for rel_app_label, rel_model_name in related_models:
# Need to do it once all models are unregistered to avoid corrupting self.apps.unregister_model(rel_app_label, rel_model_name)
# existing models' _meta
self.apps.clear_cache()
states_to_be_rendered = [] states_to_be_rendered = []
# Gather all models states of those models that will be rerendered. # 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) labels = (".".join(model_key) for model_key in self._pending_operations)
raise ValueError(msg % ", ".join(labels)) 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): def render_multiple(self, model_states):
# We keep trying to render the models in a loop, ignoring invalid # We keep trying to render the models in a loop, ignoring invalid
# base errors, until the size of the unrendered models doesn't # base errors, until the size of the unrendered models doesn't
@ -234,26 +245,23 @@ class StateApps(Apps):
if not model_states: if not model_states:
return return
# Prevent that all model caches are expired for each render. # Prevent that all model caches are expired for each render.
self.ready = False with self.bulk_update():
unrendered_models = model_states unrendered_models = model_states
while unrendered_models: while unrendered_models:
new_unrendered_models = [] new_unrendered_models = []
for model in unrendered_models: for model in unrendered_models:
try: try:
model.render(self) model.render(self)
except InvalidBasesError: except InvalidBasesError:
new_unrendered_models.append(model) new_unrendered_models.append(model)
if len(new_unrendered_models) == len(unrendered_models): if len(new_unrendered_models) == len(unrendered_models):
self.ready = True raise InvalidBasesError(
raise InvalidBasesError( "Cannot resolve bases for %r\nThis can happen if you are inheriting models from an "
"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 "
"app with migrations (e.g. contrib.auth)\n in an app with no migrations; see " "https://docs.djangoproject.com/en/%s/topics/migrations/#dependencies "
"https://docs.djangoproject.com/en/%s/topics/migrations/#dependencies " "for more" % (new_unrendered_models, get_docs_version())
"for more" % (new_unrendered_models, get_docs_version()) )
) unrendered_models = new_unrendered_models
unrendered_models = new_unrendered_models
self.ready = True
self.clear_cache()
def clone(self): def clone(self):
""" """

View File

@ -160,6 +160,22 @@ class StateTests(TestCase):
self.assertEqual([mgr.args for name, mgr in food_order_manager_state.managers], self.assertEqual([mgr.args for name, mgr in food_order_manager_state.managers],
[('a', 'b', 1, 2), ('x', 'y', 3, 4)]) [('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): def test_render(self):
""" """
Tests rendering a ProjectState into an Apps. Tests rendering a ProjectState into an Apps.