diff --git a/django/apps/base.py b/django/apps/base.py index 40b72c9448..e0d637c96e 100644 --- a/django/apps/base.py +++ b/django/apps/base.py @@ -53,7 +53,7 @@ class AppConfig(object): self.models = None def __repr__(self): - return '' % self.label + return '<%s: %s>' % (self.__class__.__name__, self.label) @classmethod def create(cls, entry): diff --git a/django/apps/registry.py b/django/apps/registry.py index 42affbbc13..6556fbf852 100644 --- a/django/apps/registry.py +++ b/django/apps/registry.py @@ -27,9 +27,8 @@ class Apps(object): if master and hasattr(sys.modules[__name__], 'apps'): raise RuntimeError("You may create only one master registry.") - # When master is set to False, the registry isn't populated from - # INSTALLED_APPS and ignores the only_installed arguments to - # get_model[s]. + # When master is set to False, the registry ignores the only_installed + # arguments to get_model[s]. self.master = master # Mapping of app labels => model names => model classes. Every time a @@ -48,9 +47,9 @@ class Apps(object): # set_available_apps and set_installed_apps. self.stored_app_configs = [] - # Internal flags used when populating the master registry. - self._apps_loaded = not self.master - self._models_loaded = not self.master + # Internal flags used when populating the registry. + self._apps_loaded = False + self._models_loaded = False # Pending lookups for lazy relations. self._pending_lookups = {} @@ -82,8 +81,11 @@ class Apps(object): # Therefore we simply import them sequentially. if installed_apps is None: installed_apps = settings.INSTALLED_APPS - for app_name in installed_apps: - app_config = AppConfig.create(app_name) + for entry in installed_apps: + if isinstance(entry, AppConfig): + app_config = entry + else: + app_config = AppConfig.create(entry) self.app_configs[app_config.label] = app_config self.get_models.cache_clear() diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index 198b2c516c..21009f4da5 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -1,3 +1,4 @@ +from django.apps import AppConfig from django.apps.registry import Apps from django.db import models from django.db.models.options import DEFAULT_NAMES, normalize_unique_together @@ -33,6 +34,11 @@ class ProjectState(object): "Turns the project state into actual models in a new Apps" if self.apps is None: self.apps = Apps() + # Populate the app registry with a stub for each application. + app_labels = set(model_state.app_label for model_state in self.models.values()) + app_configs = [AppConfigStub(label) for label in sorted(app_labels)] + self.apps.populate_apps(app_configs) + self.apps.populate_models() # We keep trying to render the models in a loop, ignoring invalid # base errors, until the size of the unrendered models doesn't # decrease by at least one, meaning there's a base dependency loop/ @@ -68,6 +74,19 @@ class ProjectState(object): return not (self == other) +class AppConfigStub(AppConfig): + """ + Stubs a Django AppConfig. Only provides a label and a dict of models. + """ + def __init__(self, label): + self.label = label + self.path = None + super(AppConfigStub, self).__init__(None, None) + + def import_models(self, all_models): + self.models = all_models + + class ModelState(object): """ Represents a Django Model. We don't use the actual Model class diff --git a/tests/apps/tests.py b/tests/apps/tests.py index f7de95ebe3..4e988493fc 100644 --- a/tests/apps/tests.py +++ b/tests/apps/tests.py @@ -50,8 +50,10 @@ class AppsTests(TestCase): Tests the ready property of a registry other than the master. """ apps = Apps() - # Currently, non-master app registries are artificially considered - # ready regardless of whether populate_models() has run. + self.assertFalse(apps.ready) + apps.populate_apps([]) + self.assertFalse(apps.ready) + apps.populate_models() self.assertTrue(apps.ready) def test_bad_app_config(self):