summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarten Kenbeek <marten.knbk@gmail.com>2015-04-06 18:02:46 +0200
committerTim Graham <timograham@gmail.com>2015-04-20 14:42:23 -0400
commit039d7881b441c6b358a963d652ffa2eada3a8892 (patch)
tree5ae39821c0bff00dce3e74f1fb4d14e819ba9fdd
parent6387d9d41fa9c4292a010bdae16feb349cb3c94a (diff)
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.
-rw-r--r--django/db/migrations/state.py58
-rw-r--r--tests/migrations/test_state.py16
2 files changed, 49 insertions, 25 deletions
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py
index 6feb9cbcc9..9150705ffc 100644
--- a/django/db/migrations/state.py
+++ b/django/db/migrations/state.py
@@ -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
- 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()
+ 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)
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,26 +245,23 @@ class StateApps(Apps):
if not model_states:
return
# Prevent that all model caches are expired for each render.
- self.ready = False
- unrendered_models = model_states
- while unrendered_models:
- new_unrendered_models = []
- for model in unrendered_models:
- try:
- model.render(self)
- 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 "
- "https://docs.djangoproject.com/en/%s/topics/migrations/#dependencies "
- "for more" % (new_unrendered_models, get_docs_version())
- )
- unrendered_models = new_unrendered_models
- self.ready = True
- self.clear_cache()
+ with self.bulk_update():
+ unrendered_models = model_states
+ while unrendered_models:
+ new_unrendered_models = []
+ for model in unrendered_models:
+ try:
+ model.render(self)
+ except InvalidBasesError:
+ new_unrendered_models.append(model)
+ if len(new_unrendered_models) == len(unrendered_models):
+ 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 "
+ "https://docs.djangoproject.com/en/%s/topics/migrations/#dependencies "
+ "for more" % (new_unrendered_models, get_docs_version())
+ )
+ unrendered_models = new_unrendered_models
def clone(self):
"""
diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py
index 530ca60c6f..551ca11459 100644
--- a/tests/migrations/test_state.py
+++ b/tests/migrations/test_state.py
@@ -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.