diff options
| author | Marten Kenbeek <marten.knbk@gmail.com> | 2015-04-06 18:02:46 +0200 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2015-04-20 14:42:23 -0400 |
| commit | 039d7881b441c6b358a963d652ffa2eada3a8892 (patch) | |
| tree | 5ae39821c0bff00dce3e74f1fb4d14e819ba9fdd | |
| parent | 6387d9d41fa9c4292a010bdae16feb349cb3c94a (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.py | 58 | ||||
| -rw-r--r-- | tests/migrations/test_state.py | 16 |
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. |
