summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAymeric Augustin <aymeric.augustin@m4x.org>2013-12-11 21:44:27 +0100
committerAymeric Augustin <aymeric.augustin@m4x.org>2013-12-17 10:17:43 +0100
commit860c2c8bc5c77194c41464655851379bf512a052 (patch)
tree98a3688dbbb67151d944dfc8e943b00bced701b1
parentfe1389e911b0cdc487e5547c09c920c12f4e1ce0 (diff)
Moved django.db.models.loading to django.apps.cache.
This commit doesn't contain any code changes; it's purely a refactoring.
-rw-r--r--django/apps/__init__.py0
-rw-r--r--django/apps/cache.py396
-rw-r--r--django/contrib/auth/tests/test_management.py2
-rw-r--r--django/core/management/commands/makemigrations.py2
-rw-r--r--django/core/management/commands/migrate.py2
-rw-r--r--django/core/management/commands/shell.py2
-rw-r--r--django/core/management/validation.py2
-rw-r--r--django/db/backends/sqlite3/schema.py2
-rw-r--r--django/db/migrations/loader.py2
-rw-r--r--django/db/migrations/questioner.py2
-rw-r--r--django/db/migrations/recorder.py2
-rw-r--r--django/db/migrations/state.py2
-rw-r--r--django/db/migrations/writer.py4
-rw-r--r--django/db/models/__init__.py4
-rw-r--r--django/db/models/base.py2
-rw-r--r--django/db/models/fields/__init__.py2
-rw-r--r--django/db/models/loading.py384
-rw-r--r--django/db/models/options.py2
-rw-r--r--django/db/models/signals.py2
-rw-r--r--django/test/testcases.py2
-rw-r--r--docs/internals/deprecation.txt3
-rw-r--r--tests/app_cache/models.py2
-rw-r--r--tests/app_cache/tests.py6
-rw-r--r--tests/app_loading/tests.py2
-rw-r--r--tests/contenttypes_tests/tests.py2
-rw-r--r--tests/defer_regress/tests.py2
-rw-r--r--tests/empty/tests.py2
-rw-r--r--tests/invalid_models/tests.py2
-rw-r--r--tests/managers_regress/tests.py2
-rw-r--r--tests/migrations/models.py2
-rw-r--r--tests/migrations/test_commands.py2
-rw-r--r--tests/migrations/test_state.py4
-rw-r--r--tests/migrations/test_writer.py2
-rw-r--r--tests/proxy_model_inheritance/tests.py2
-rw-r--r--tests/proxy_models/tests.py2
-rwxr-xr-xtests/runtests.py4
-rw-r--r--tests/schema/models.py2
-rw-r--r--tests/swappable_models/tests.py2
-rw-r--r--tests/tablespaces/tests.py2
-rw-r--r--tests/validation/test_unique.py2
40 files changed, 448 insertions, 419 deletions
diff --git a/django/apps/__init__.py b/django/apps/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/apps/__init__.py
diff --git a/django/apps/cache.py b/django/apps/cache.py
new file mode 100644
index 0000000000..a5b645646f
--- /dev/null
+++ b/django/apps/cache.py
@@ -0,0 +1,396 @@
+"Utilities for loading models and the modules that contain them."
+
+from collections import OrderedDict
+import copy
+import imp
+from importlib import import_module
+import os
+import sys
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.module_loading import module_has_submodule
+from django.utils._os import upath
+from django.utils import six
+
+__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
+ 'load_app', 'app_cache_ready')
+
+MODELS_MODULE_NAME = 'models'
+
+
+class ModelDict(OrderedDict):
+ """
+ We need to special-case the deepcopy for this, as the keys are modules,
+ which can't be deep copied.
+ """
+ def __deepcopy__(self, memo):
+ return self.__class__([(key, copy.deepcopy(value, memo))
+ for key, value in self.items()])
+
+
+class UnavailableApp(Exception):
+ pass
+
+
+def _initialize():
+ """
+ Returns a dictionary to be used as the initial value of the
+ [shared] state of the app cache.
+ """
+ return dict(
+ # Keys of app_store are the model modules for each application.
+ app_store=ModelDict(),
+
+ # Mapping of installed app_labels to model modules for that app.
+ app_labels={},
+
+ # Mapping of app_labels to a dictionary of model names to model code.
+ # May contain apps that are not installed.
+ app_models=ModelDict(),
+
+ # Mapping of app_labels to errors raised when trying to import the app.
+ app_errors={},
+
+ # Pending lookups for lazy relations
+ pending_lookups={},
+
+ # List of app_labels that allows restricting the set of apps.
+ # Used by TransactionTestCase.available_apps for performance reasons.
+ available_apps=None,
+
+ # -- Everything below here is only used when populating the cache --
+ loads_installed=True,
+ loaded=False,
+ handled=set(),
+ postponed=[],
+ nesting_level=0,
+ _get_models_cache={},
+ )
+
+
+class BaseAppCache(object):
+ """
+ A cache that stores installed applications and their models. Used to
+ provide reverse-relations and for app introspection (e.g. admin).
+
+ This provides the base (non-Borg) AppCache class - the AppCache
+ subclass adds borg-like behaviour for the few cases where it's needed.
+ """
+
+ def __init__(self):
+ self.__dict__ = _initialize()
+ # This stops _populate loading from INSTALLED_APPS and ignores the
+ # only_installed arguments to get_model[s]
+ self.loads_installed = False
+
+ def _populate(self):
+ """
+ Fill in all the cache information. This method is threadsafe, in the
+ sense that every caller will see the same state upon return, and if the
+ cache is already initialised, it does no work.
+ """
+ if self.loaded:
+ return
+ if not self.loads_installed:
+ self.loaded = True
+ return
+ # Note that we want to use the import lock here - the app loading is
+ # in many cases initiated implicitly by importing, and thus it is
+ # possible to end up in deadlock when one thread initiates loading
+ # without holding the importer lock and another thread then tries to
+ # import something which also launches the app loading. For details of
+ # this situation see #18251.
+ imp.acquire_lock()
+ try:
+ if self.loaded:
+ return
+ for app_name in settings.INSTALLED_APPS:
+ if app_name in self.handled:
+ continue
+ self.load_app(app_name, True)
+ if not self.nesting_level:
+ for app_name in self.postponed:
+ self.load_app(app_name)
+ self.loaded = True
+ finally:
+ imp.release_lock()
+
+ def _label_for(self, app_mod):
+ """
+ Return app_label for given models module.
+
+ """
+ return app_mod.__name__.split('.')[-2]
+
+ def load_app(self, app_name, can_postpone=False):
+ """
+ Loads the app with the provided fully qualified name, and returns the
+ model module.
+ """
+ app_module = import_module(app_name)
+ self.handled.add(app_name)
+ self.nesting_level += 1
+ try:
+ models = import_module('%s.%s' % (app_name, MODELS_MODULE_NAME))
+ except ImportError:
+ self.nesting_level -= 1
+ # If the app doesn't have a models module, we can just ignore the
+ # ImportError and return no models for it.
+ if not module_has_submodule(app_module, MODELS_MODULE_NAME):
+ return None
+ # But if the app does have a models module, we need to figure out
+ # whether to suppress or propagate the error. If can_postpone is
+ # True then it may be that the package is still being imported by
+ # Python and the models module isn't available yet. So we add the
+ # app to the postponed list and we'll try it again after all the
+ # recursion has finished (in populate). If can_postpone is False
+ # then it's time to raise the ImportError.
+ else:
+ if can_postpone:
+ self.postponed.append(app_name)
+ return None
+ else:
+ raise
+
+ self.nesting_level -= 1
+ if models not in self.app_store:
+ self.app_store[models] = len(self.app_store)
+ self.app_labels[self._label_for(models)] = models
+ return models
+
+ def app_cache_ready(self):
+ """
+ Returns true if the model cache is fully populated.
+
+ Useful for code that wants to cache the results of get_models() for
+ themselves once it is safe to do so.
+ """
+ return self.loaded
+
+ def get_apps(self):
+ """
+ Returns a list of all installed modules that contain models.
+ """
+ self._populate()
+
+ apps = self.app_store.items()
+ if self.available_apps is not None:
+ apps = [elt for elt in apps
+ if self._label_for(elt[0]) in self.available_apps]
+
+ # Ensure the returned list is always in the same order (with new apps
+ # added at the end). This avoids unstable ordering on the admin app
+ # list page, for example.
+ apps = sorted(apps, key=lambda elt: elt[1])
+
+ return [elt[0] for elt in apps]
+
+ def _get_app_package(self, app):
+ return '.'.join(app.__name__.split('.')[:-1])
+
+ def get_app_package(self, app_label):
+ return self._get_app_package(self.get_app(app_label))
+
+ def _get_app_path(self, app):
+ if hasattr(app, '__path__'): # models/__init__.py package
+ app_path = app.__path__[0]
+ else: # models.py module
+ app_path = app.__file__
+ return os.path.dirname(upath(app_path))
+
+ def get_app_path(self, app_label):
+ return self._get_app_path(self.get_app(app_label))
+
+ def get_app_paths(self):
+ """
+ Returns a list of paths to all installed apps.
+
+ Useful for discovering files at conventional locations inside apps
+ (static files, templates, etc.)
+ """
+ self._populate()
+
+ app_paths = []
+ for app in self.get_apps():
+ app_paths.append(self._get_app_path(app))
+ return app_paths
+
+ def get_app(self, app_label, emptyOK=False):
+ """
+ Returns the module containing the models for the given app_label.
+
+ Returns None if the app has no models in it and emptyOK is True.
+
+ Raises UnavailableApp when set_available_apps() in in effect and
+ doesn't include app_label.
+ """
+ self._populate()
+ imp.acquire_lock()
+ try:
+ for app_name in settings.INSTALLED_APPS:
+ if app_label == app_name.split('.')[-1]:
+ mod = self.load_app(app_name, False)
+ if mod is None and not emptyOK:
+ raise ImproperlyConfigured("App with label %s is missing a models.py module." % app_label)
+ if self.available_apps is not None and app_label not in self.available_apps:
+ raise UnavailableApp("App with label %s isn't available." % app_label)
+ return mod
+ raise ImproperlyConfigured("App with label %s could not be found" % app_label)
+ finally:
+ imp.release_lock()
+
+ def get_app_errors(self):
+ "Returns the map of known problems with the INSTALLED_APPS."
+ self._populate()
+ return self.app_errors
+
+ def get_models(self, app_mod=None,
+ include_auto_created=False, include_deferred=False,
+ only_installed=True, include_swapped=False):
+ """
+ Given a module containing models, returns a list of the models.
+ Otherwise returns a list of all installed models.
+
+ By default, auto-created models (i.e., m2m models without an
+ explicit intermediate table) are not included. However, if you
+ specify include_auto_created=True, they will be.
+
+ By default, models created to satisfy deferred attribute
+ queries are *not* included in the list of models. However, if
+ you specify include_deferred, they will be.
+
+ By default, models that aren't part of installed apps will *not*
+ be included in the list of models. However, if you specify
+ only_installed=False, they will be. If you're using a non-default
+ AppCache, this argument does nothing - all models will be included.
+
+ By default, models that have been swapped out will *not* be
+ included in the list of models. However, if you specify
+ include_swapped, they will be.
+ """
+ if not self.loads_installed:
+ only_installed = False
+ cache_key = (app_mod, include_auto_created, include_deferred, only_installed, include_swapped)
+ model_list = None
+ try:
+ model_list = self._get_models_cache[cache_key]
+ if self.available_apps is not None and only_installed:
+ model_list = [m for m in model_list if m._meta.app_label in self.available_apps]
+ return model_list
+ except KeyError:
+ pass
+ self._populate()
+ if app_mod:
+ if app_mod in self.app_store:
+ app_list = [self.app_models.get(self._label_for(app_mod), ModelDict())]
+ else:
+ app_list = []
+ else:
+ if only_installed:
+ app_list = [self.app_models.get(app_label, ModelDict())
+ for app_label in six.iterkeys(self.app_labels)]
+ else:
+ app_list = six.itervalues(self.app_models)
+ model_list = []
+ for app in app_list:
+ model_list.extend(
+ model for model in app.values()
+ if ((not model._deferred or include_deferred) and
+ (not model._meta.auto_created or include_auto_created) and
+ (not model._meta.swapped or include_swapped))
+ )
+ self._get_models_cache[cache_key] = model_list
+ if self.available_apps is not None and only_installed:
+ model_list = [m for m in model_list if m._meta.app_label in self.available_apps]
+ return model_list
+
+ def get_model(self, app_label, model_name,
+ seed_cache=True, only_installed=True):
+ """
+ Returns the model matching the given app_label and case-insensitive
+ model_name.
+
+ Returns None if no model is found.
+
+ Raises UnavailableApp when set_available_apps() in in effect and
+ doesn't include app_label.
+ """
+ if not self.loads_installed:
+ only_installed = False
+ if seed_cache:
+ self._populate()
+ if only_installed and app_label not in self.app_labels:
+ return None
+ if (self.available_apps is not None and only_installed
+ and app_label not in self.available_apps):
+ raise UnavailableApp("App with label %s isn't available." % app_label)
+ try:
+ return self.app_models[app_label][model_name.lower()]
+ except KeyError:
+ return None
+
+ def register_models(self, app_label, *models):
+ """
+ Register a set of models as belonging to an app.
+ """
+ for model in models:
+ # Store as 'name: model' pair in a dictionary
+ # in the app_models dictionary
+ model_name = model._meta.model_name
+ model_dict = self.app_models.setdefault(app_label, ModelDict())
+ if model_name in model_dict:
+ # The same model may be imported via different paths (e.g.
+ # appname.models and project.appname.models). We use the source
+ # filename as a means to detect identity.
+ fname1 = os.path.abspath(upath(sys.modules[model.__module__].__file__))
+ fname2 = os.path.abspath(upath(sys.modules[model_dict[model_name].__module__].__file__))
+ # Since the filename extension could be .py the first time and
+ # .pyc or .pyo the second time, ignore the extension when
+ # comparing.
+ if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
+ continue
+ model_dict[model_name] = model
+ self._get_models_cache.clear()
+
+ def set_available_apps(self, available):
+ if not set(available).issubset(set(settings.INSTALLED_APPS)):
+ extra = set(available) - set(settings.INSTALLED_APPS)
+ raise ValueError("Available apps isn't a subset of installed "
+ "apps, extra apps: " + ", ".join(extra))
+ self.available_apps = set(app.rsplit('.', 1)[-1] for app in available)
+
+ def unset_available_apps(self):
+ self.available_apps = None
+
+
+class AppCache(BaseAppCache):
+ """
+ A cache that stores installed applications and their models. Used to
+ provide reverse-relations and for app introspection (e.g. admin).
+
+ Borg version of the BaseAppCache class.
+ """
+
+ __shared_state = _initialize()
+
+ def __init__(self):
+ self.__dict__ = self.__shared_state
+
+
+cache = AppCache()
+
+
+# These methods were always module level, so are kept that way for backwards
+# compatibility.
+get_apps = cache.get_apps
+get_app_package = cache.get_app_package
+get_app_path = cache.get_app_path
+get_app_paths = cache.get_app_paths
+get_app = cache.get_app
+get_app_errors = cache.get_app_errors
+get_models = cache.get_models
+get_model = cache.get_model
+register_models = cache.register_models
+load_app = cache.load_app
+app_cache_ready = cache.app_cache_ready
diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py
index bd51f39977..698947d57c 100644
--- a/django/contrib/auth/tests/test_management.py
+++ b/django/contrib/auth/tests/test_management.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
from datetime import date
+from django.apps.cache import get_app
from django.contrib.auth import models, management
from django.contrib.auth.management import create_permissions
from django.contrib.auth.management.commands import changepassword
@@ -12,7 +13,6 @@ from django.core import exceptions
from django.core.management import call_command
from django.core.management.base import CommandError
from django.core.management.validation import get_validation_errors
-from django.db.models.loading import get_app
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import six
diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py
index 239a0a416c..7c4bb4f7cf 100644
--- a/django/core/management/commands/makemigrations.py
+++ b/django/core/management/commands/makemigrations.py
@@ -3,6 +3,7 @@ import os
import operator
from optparse import make_option
+from django.apps.cache import cache
from django.core.management.base import BaseCommand, CommandError
from django.core.exceptions import ImproperlyConfigured
from django.db import connections, DEFAULT_DB_ALIAS, migrations
@@ -11,7 +12,6 @@ from django.db.migrations.autodetector import MigrationAutodetector
from django.db.migrations.questioner import MigrationQuestioner, InteractiveMigrationQuestioner
from django.db.migrations.state import ProjectState
from django.db.migrations.writer import MigrationWriter
-from django.db.models.loading import cache
from django.utils.six.moves import reduce
diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py
index 093c8a79d0..1966223042 100644
--- a/django/core/management/commands/migrate.py
+++ b/django/core/management/commands/migrate.py
@@ -6,6 +6,7 @@ from importlib import import_module
import itertools
import traceback
+from django.apps.cache import cache
from django.conf import settings
from django.core.management import call_command
from django.core.management.base import BaseCommand, CommandError
@@ -16,7 +17,6 @@ from django.db.migrations.executor import MigrationExecutor
from django.db.migrations.loader import MigrationLoader, AmbiguityError
from django.db.migrations.state import ProjectState
from django.db.migrations.autodetector import MigrationAutodetector
-from django.db.models.loading import cache
from django.utils.module_loading import module_has_submodule
diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py
index 00a6602c0b..0d84c9b2ec 100644
--- a/django/core/management/commands/shell.py
+++ b/django/core/management/commands/shell.py
@@ -66,7 +66,7 @@ class Command(NoArgsCommand):
def handle_noargs(self, **options):
# XXX: (Temporary) workaround for ticket #1796: force early loading of all
# models from installed apps.
- from django.db.models.loading import get_models
+ from django.apps.cache import get_models
get_models()
use_plain = options.get('plain', False)
diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index 6bdcf853d4..459923a088 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -26,8 +26,8 @@ def get_validation_errors(outfile, app=None):
validates all models of all installed apps. Writes errors, if any, to outfile.
Returns number of errors.
"""
+ from django.apps.cache import get_app_errors
from django.db import connection, models
- from django.db.models.loading import get_app_errors
from django.db.models.deletion import SET_NULL, SET_DEFAULT
e = ModelErrorCollection(outfile)
diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py
index f04095507f..69e3b61b87 100644
--- a/django/db/backends/sqlite3/schema.py
+++ b/django/db/backends/sqlite3/schema.py
@@ -1,6 +1,6 @@
+from django.apps.cache import BaseAppCache
from django.db.backends.schema import BaseDatabaseSchemaEditor
from django.db.models.fields.related import ManyToManyField
-from django.db.models.loading import BaseAppCache
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py
index f2510b8368..101588c4a1 100644
--- a/django/db/migrations/loader.py
+++ b/django/db/migrations/loader.py
@@ -1,7 +1,7 @@
import os
import sys
from importlib import import_module
-from django.db.models.loading import cache
+from django.apps.cache import cache
from django.db.migrations.recorder import MigrationRecorder
from django.db.migrations.graph import MigrationGraph
from django.utils import six
diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py
index 7e798d3105..7874f23ed0 100644
--- a/django/db/migrations/questioner.py
+++ b/django/db/migrations/questioner.py
@@ -2,7 +2,7 @@ import importlib
import os
import sys
-from django.db.models.loading import cache
+from django.apps.cache import cache
from django.utils import datetime_safe
from django.utils.six.moves import input
from django.core.exceptions import ImproperlyConfigured
diff --git a/django/db/migrations/recorder.py b/django/db/migrations/recorder.py
index be804e52aa..bf1cd225c2 100644
--- a/django/db/migrations/recorder.py
+++ b/django/db/migrations/recorder.py
@@ -1,5 +1,5 @@
+from django.apps.cache import BaseAppCache
from django.db import models
-from django.db.models.loading import BaseAppCache
from django.utils.timezone import now
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py
index 769b0005f8..6aa44ba108 100644
--- a/django/db/migrations/state.py
+++ b/django/db/migrations/state.py
@@ -1,5 +1,5 @@
+from django.apps.cache import BaseAppCache
from django.db import models
-from django.db.models.loading import BaseAppCache
from django.db.models.options import DEFAULT_NAMES, normalize_unique_together
from django.utils import six
from django.utils.module_loading import import_by_path
diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py
index 22b0977ba4..150455b09c 100644
--- a/django/db/migrations/writer.py
+++ b/django/db/migrations/writer.py
@@ -3,12 +3,12 @@ import datetime
import types
import os
from importlib import import_module
-from django.utils import six
+from django.apps.cache import cache
from django.db import models
-from django.db.models.loading import cache
from django.db.migrations.loader import MigrationLoader
from django.utils.encoding import force_text
from django.utils.functional import Promise
+from django.utils import six
class MigrationWriter(object):
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index 12c31f89fd..ad76347494 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -1,9 +1,9 @@
from functools import wraps
-from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured # NOQA
-from django.db.models.loading import ( # NOQA
+from django.apps.cache import ( # NOQA
get_apps, get_app_path, get_app_paths, get_app, get_models, get_model,
register_models, UnavailableApp)
+from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured # NOQA
from django.db.models.query import Q, QuerySet, Prefetch # NOQA
from django.db.models.expressions import F # NOQA
from django.db.models.manager import Manager # NOQA
diff --git a/django/db/models/base.py b/django/db/models/base.py
index dd4850c3d4..f22506aa92 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -5,6 +5,7 @@ import sys
from functools import update_wrapper
from django.utils.six.moves import zip
+from django.apps.cache import get_model, MODELS_MODULE_NAME
import django.db.models.manager # NOQA: Imported to register signal handler.
from django.conf import settings
from django.core.exceptions import (ObjectDoesNotExist,
@@ -19,7 +20,6 @@ from django.db.models.query_utils import DeferredAttribute, deferred_class_facto
from django.db.models.deletion import Collector
from django.db.models.options import Options
from django.db.models import signals
-from django.db.models.loading import get_model, MODELS_MODULE_NAME
from django.utils.translation import ugettext_lazy as _
from django.utils.functional import curry
from django.utils.encoding import force_str, force_text
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 33adaedc7e..d69a4346e2 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -9,8 +9,8 @@ import warnings
from base64 import b64decode, b64encode
from itertools import tee
+from django.apps.cache import get_model
from django.db import connection
-from django.db.models.loading import get_model
from django.db.models.query_utils import QueryWrapper
from django.conf import settings
from django import forms
diff --git a/django/db/models/loading.py b/django/db/models/loading.py
index 21440216e0..795de130b5 100644
--- a/django/db/models/loading.py
+++ b/django/db/models/loading.py
@@ -1,387 +1,15 @@
-"Utilities for loading models and the modules that contain them."
+import warnings
-from collections import OrderedDict
-import copy
-import imp
-from importlib import import_module
-import os
-import sys
+from django.apps.cache import cache
-from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
-from django.utils.module_loading import module_has_submodule
-from django.utils._os import upath
-from django.utils import six
+warnings.warn(
+ "The utilities in django.db.models.loading are deprecated "
+ "in favor of the new application loading system.",
+ PendingDeprecationWarning, stacklevel=2)
__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
'load_app', 'app_cache_ready')
-MODELS_MODULE_NAME = 'models'
-
-
-class ModelDict(OrderedDict):
- """
- We need to special-case the deepcopy for this, as the keys are modules,
- which can't be deep copied.
- """
- def __deepcopy__(self, memo):
- return self.__class__([(key, copy.deepcopy(value, memo))
- for key, value in self.items()])
-
-
-class UnavailableApp(Exception):
- pass
-
-
-def _initialize():
- """
- Returns a dictionary to be used as the initial value of the
- [shared] state of the app cache.
- """
- return dict(
- # Keys of app_store are the model modules for each application.
- app_store=ModelDict(),
-
- # Mapping of installed app_labels to model modules for that app.
- app_labels={},
-
- # Mapping of app_labels to a dictionary of model names to model code.
- # May contain apps that are not installed.
- app_models=ModelDict(),
-
- # Mapping of app_labels to errors raised when trying to import the app.
- app_errors={},
-
- # Pending lookups for lazy relations
- pending_lookups={},
-
- # List of app_labels that allows restricting the set of apps.
- # Used by TransactionTestCase.available_apps for performance reasons.
- available_apps=None,
-
- # -- Everything below here is only used when populating the cache --
- loads_installed=True,
- loaded=False,
- handled=set(),
- postponed=[],
- nesting_level=0,
- _get_models_cache={},
- )
-
-
-class BaseAppCache(object):
- """
- A cache that stores installed applications and their models. Used to
- provide reverse-relations and for app introspection (e.g. admin).
-
- This provides the base (non-Borg) AppCache class - the AppCache
- subclass adds borg-like behaviour for the few cases where it's needed.
- """
-
- def __init__(self):
- self.__dict__ = _initialize()
- # This stops _populate loading from INSTALLED_APPS and ignores the
- # only_installed arguments to get_model[s]
- self.loads_installed = False
-
- def _populate(self):
- """
- Fill in all the cache information. This method is threadsafe, in the
- sense that every caller will see the same state upon return, and if the
- cache is already initialised, it does no work.
- """
- if self.loaded:
- return
- if not self.loads_installed:
- self.loaded = True
- return
- # Note that we want to use the import lock here - the app loading is
- # in many cases initiated implicitly by importing, and thus it is
- # possible to end up in deadlock when one thread initiates loading
- # without holding the importer lock and another thread then tries to
- # import something which also launches the app loading. For details of
- # this situation see #18251.
- imp.acquire_lock()
- try:
- if self.loaded:
- return
- for app_name in settings.INSTALLED_APPS:
- if app_name in self.handled:
- continue
- self.load_app(app_name, True)
- if not self.nesting_level:
- for app_name in self.postponed:
- self.load_app(app_name)
- self.loaded = True
- finally:
- imp.release_lock()
-
- def _label_for(self, app_mod):
- """
- Return app_label for given models module.
-
- """
- return app_mod.__name__.split('.')[-2]
-
- def load_app(self, app_name, can_postpone=False):
- """
- Loads the app with the provided fully qualified name, and returns the
- model module.
- """
- app_module = import_module(app_name)
- self.handled.add(app_name)
- self.nesting_level += 1
- try:
- models = import_module('%s.%s' % (app_name, MODELS_MODULE_NAME))
- except ImportError:
- self.nesting_level -= 1
- # If the app doesn't have a models module, we can just ignore the
- # ImportError and return no models for it.
- if not module_has_submodule(app_module, MODELS_MODULE_NAME):
- return None
- # But if the app does have a models module, we need to figure out
- # whether to suppress or propagate the error. If can_postpone is
- # True then it may be that the package is still being imported by
- # Python and the models module isn't available yet. So we add the
- # app to the postponed list and we'll try it again after all the
- # recursion has finished (in populate). If can_postpone is False
- # then it's time to raise the ImportError.
- else:
- if can_postpone:
- self.postponed.append(app_name)
- return None
- else:
- raise
-
- self.nesting_level -= 1
- if models not in self.app_store:
- self.app_store[models] = len(self.app_store)
- self.app_labels[self._label_for(models)] = models
- return models
-
- def app_cache_ready(self):
- """
- Returns true if the model cache is fully populated.
-
- Useful for code that wants to cache the results of get_models() for
- themselves once it is safe to do so.
- """
- return self.loaded
-
- def get_apps(self):
- """
- Returns a list of all installed modules that contain models.
- """
- self._populate()
-
- apps = self.app_store.items()
- if self.available_apps is not None:
- apps = [elt for elt in apps
- if self._label_for(elt[0]) in self.available_apps]
-
- # Ensure the returned list is always in the same order (with new apps
- # added at the end). This avoids unstable ordering on the admin app
- # list page, for example.
- apps = sorted(apps, key=lambda elt: elt[1])
-
- return [elt[0] for elt in apps]
-
- def _get_app_package(self, app):
- return '.'.join(app.__name__.split('.')[:-1])
-
- def get_app_package(self, app_label):
- return self._get_app_package(self.get_app(app_label))
-
- def _get_app_path(self, app):
- if hasattr(app, '__path__'): # models/__init__.py package
- app_path = app.__path__[0]
- else: # models.py module
- app_path = app.__file__
- return os.path.dirname(upath(app_path))
-
- def get_app_path(self, app_label):
- return self._get_app_path(self.get_app(app_label))
-
- def get_app_paths(self):
- """
- Returns a list of paths to all installed apps.
-
- Useful for discovering files at conventional locations inside apps
- (static files, templates, etc.)
- """
- self._populate()
-
- app_paths = []
- for app in self.get_apps():
- app_paths.append(self._get_app_path(app))
- return app_paths
-
- def get_app(self, app_label, emptyOK=False):
- """
- Returns the module containing the models for the given app_label.
-
- Returns None if the app has no models in it and emptyOK is True.
-
- Raises UnavailableApp when set_available_apps() in in effect and
- doesn't include app_label.
- """
- self._populate()
- imp.acquire_lock()
- try:
- for app_name in settings.INSTALLED_APPS:
- if app_label == app_name.split('.')[-1]:
- mod = self.load_app(app_name, False)
- if mod is None and not emptyOK:
- raise ImproperlyConfigured("App with label %s is missing a models.py module." % app_label)
- if self.available_apps is not None and app_label not in self.available_apps:
- raise UnavailableApp("App with label %s isn't available." % app_label)
- return mod
- raise ImproperlyConfigured("App with label %s could not be found" % app_label)
- finally:
- imp.release_lock()
-
- def get_app_errors(self):
- "Returns the map of known problems with the INSTALLED_APPS."
- self._populate()
- return self.app_errors
-
- def get_models(self, app_mod=None,
- include_auto_created=False, include_deferred=False,
- only_installed=True, include_swapped=False):
- """
- Given a module containing models, returns a list of the models.
- Otherwise returns a list of all installed models.
-
- By default, auto-created models (i.e., m2m models without an
- explicit intermediate table) are not included. However, if you
- specify include_auto_created=True, they will be.
-
- By default, models created to satisfy deferred attribute
- queries are *not* included in the list of models. However, if
- you specify include_deferred, they will be.
-
- By default, models that aren't part of installed apps will *not*
- be included in the list of models. However, if you specify
- only_installed=False, they will be. If you're using a non-default
- AppCache, this argument does nothing - all models will be included.
-
- By default, models that have been swapped out will *not* be
- included in the list of models. However, if you specify
- include_swapped, they will be.
- """
- if not self.loads_installed:
- only_installed = False
- cache_key = (app_mod, include_auto_created, include_deferred, only_installed, include_swapped)
- model_list = None
- try:
- model_list = self._get_models_cache[cache_key]
- if self.available_apps is not None and only_installed:
- model_list = [m for m in model_list if m._meta.app_label in self.available_apps]
-
- return model_list
- except KeyError:
- pass
- self._populate()
- if app_mod:
- if app_mod in self.app_store:
- app_list = [self.app_models.get(self._label_for(app_mod), ModelDict())]
- else:
- app_list = []
- else:
- if only_installed:
- app_list = [self.app_models.get(app_label, ModelDict())
- for app_label in six.iterkeys(self.app_labels)]
- else:
- app_list = six.itervalues(self.app_models)
- model_list = []
- for app in app_list:
- model_list.extend(
- model for model in app.values()
- if ((not model._deferred or include_deferred) and
- (not model._meta.auto_created or include_auto_created) and
- (not model._meta.swapped or include_swapped))
- )
- self._get_models_cache[cache_key] = model_list
- if self.available_apps is not None and only_installed:
- model_list = [m for m in model_list if m._meta.app_label in self.available_apps]
- return model_list
-
- def get_model(self, app_label, model_name,
- seed_cache=True, only_installed=True):
- """
- Returns the model matching the given app_label and case-insensitive
- model_name.
-
- Returns None if no model is found.
-
- Raises UnavailableApp when set_available_apps() in in effect and
- doesn't include app_label.
- """
- if not self.loads_installed:
- only_installed = False
- if seed_cache:
- self._populate()
- if only_installed and app_label not in self.app_labels:
- return None
- if (self.available_apps is not None and only_installed
- and app_label not in self.available_apps):
- raise UnavailableApp("App with label %s isn't available." % app_label)
- try:
- return self.app_models[app_label][model_name.lower()]
- except KeyError:
- return None
-
- def register_models(self, app_label, *models):
- """
- Register a set of models as belonging to an app.
- """
- for model in models:
- # Store as 'name: model' pair in a dictionary
- # in the app_models dictionary
- model_name = model._meta.model_name
- model_dict = self.app_models.setdefault(app_label, ModelDict())
- if model_name in model_dict:
- # The same model may be imported via different paths (e.g.
- # appname.models and project.appname.models). We use the source
- # filename as a means to detect identity.
- fname1 = os.path.abspath(upath(sys.modules[model.__module__].__file__))
- fname2 = os.path.abspath(upath(sys.modules[model_dict[model_name].__module__].__file__))
- # Since the filename extension could be .py the first time and
- # .pyc or .pyo the second time, ignore the extension when
- # comparing.
- if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
- continue
- model_dict[model_name] = model
- self._get_models_cache.clear()
-
- def set_available_apps(self, available):
- if not set(available).issubset(set(settings.INSTALLED_APPS)):
- extra = set(available) - set(settings.INSTALLED_APPS)
- raise ValueError("Available apps isn't a subset of installed "
- "apps, extra apps: " + ", ".join(extra))
- self.available_apps = set(app.rsplit('.', 1)[-1] for app in available)
-
- def unset_available_apps(self):
- self.available_apps = None
-
-
-class AppCache(BaseAppCache):
- """
- A cache that stores installed applications and their models. Used to
- provide reverse-relations and for app introspection (e.g. admin).
-
- Borg version of the BaseAppCache class.
- """
-
- __shared_state = _initialize()
-
- def __init__(self):
- self.__dict__ = self.__shared_state
-
-
-cache = AppCache()
-
-
# These methods were always module level, so are kept that way for backwards
# compatibility.
get_apps = cache.get_apps
diff --git a/django/db/models/options.py b/django/db/models/options.py
index b14e61573c..5d99066343 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -5,11 +5,11 @@ import re
from bisect import bisect
import warnings
+from django.apps.cache import app_cache_ready, cache
from django.conf import settings
from django.db.models.fields.related import ManyToManyRel
from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.fields.proxy import OrderWrt
-from django.db.models.loading import app_cache_ready, cache
from django.utils import six
from django.utils.functional import cached_property
from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible
diff --git a/django/db/models/signals.py b/django/db/models/signals.py
index 6b011c2099..2543cf5f4a 100644
--- a/django/db/models/signals.py
+++ b/django/db/models/signals.py
@@ -1,6 +1,6 @@
from collections import defaultdict
-from django.db.models.loading import get_model
+from django.apps.cache import get_model
from django.dispatch import Signal
from django.utils import six
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 4dbff55204..52900ec478 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -15,6 +15,7 @@ import unittest
from unittest import skipIf # NOQA: Imported here for backward compatibility
from unittest.util import safe_repr
+from django.apps.cache import cache
from django.conf import settings
from django.core import mail
from django.core.exceptions import ValidationError, ImproperlyConfigured
@@ -25,7 +26,6 @@ from django.core.management.commands import flush
from django.core.servers.basehttp import WSGIRequestHandler, WSGIServer
from django.core.urlresolvers import clear_url_caches, set_urlconf
from django.db import connection, connections, DEFAULT_DB_ALIAS, transaction
-from django.db.models.loading import cache
from django.forms.fields import CharField
from django.http import QueryDict
from django.test.client import Client
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 2e53901536..d548f275fe 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -222,6 +222,9 @@ these changes.
* ``django.core.cache.get_cache`` will be removed. Add suitable entries
to :setting:`CACHES` and use :data:`django.core.cache.caches` instead.
+* ``django.db.models.loading`` will be removed. Use the new application
+ loading APIs instead.
+
2.0
---
diff --git a/tests/app_cache/models.py b/tests/app_cache/models.py
index 1b4d33c2f9..cc092390ab 100644
--- a/tests/app_cache/models.py
+++ b/tests/app_cache/models.py
@@ -1,5 +1,5 @@
+from django.apps.cache import BaseAppCache
from django.db import models
-from django.db.models.loading import BaseAppCache
# We're testing app cache presence on load, so this is handy.
diff --git a/tests/app_cache/tests.py b/tests/app_cache/tests.py
index b72b862de3..564f9f6f48 100644
--- a/tests/app_cache/tests.py
+++ b/tests/app_cache/tests.py
@@ -1,7 +1,9 @@
from __future__ import absolute_import
-from django.test import TestCase
-from django.db.models.loading import cache, BaseAppCache
+
+from django.apps.cache import cache, BaseAppCache
from django.db import models
+from django.test import TestCase
+
from .models import TotallyNormal, SoAlternative, new_app_cache
diff --git a/tests/app_loading/tests.py b/tests/app_loading/tests.py
index 20ec064d69..4795ca4139 100644
--- a/tests/app_loading/tests.py
+++ b/tests/app_loading/tests.py
@@ -5,7 +5,7 @@ import os
import sys
from unittest import TestCase
-from django.db.models.loading import cache, load_app, get_model, get_models, AppCache
+from django.apps.cache import cache, load_app, get_model, get_models, AppCache
from django.test.utils import override_settings
from django.utils._os import upath
diff --git a/tests/contenttypes_tests/tests.py b/tests/contenttypes_tests/tests.py
index 9d1e1f77ea..a56d196933 100644
--- a/tests/contenttypes_tests/tests.py
+++ b/tests/contenttypes_tests/tests.py
@@ -1,8 +1,8 @@
from __future__ import unicode_literals
+from django.apps.cache import BaseAppCache
from django.contrib.contenttypes.models import ContentType
from django.db import models
-from django.db.models.loading import BaseAppCache
from django.test import TestCase
from .models import Author, Article
diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py
index c03388b50e..0107e8167c 100644
--- a/tests/defer_regress/tests.py
+++ b/tests/defer_regress/tests.py
@@ -2,10 +2,10 @@ from __future__ import unicode_literals
from operator import attrgetter
+from django.apps.cache import cache
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.backends.db import SessionStore
from django.db.models import Count
-from django.db.models.loading import cache
from django.test import TestCase
from django.test.utils import override_settings
diff --git a/tests/empty/tests.py b/tests/empty/tests.py
index 007d04c363..2a9f568aea 100644
--- a/tests/empty/tests.py
+++ b/tests/empty/tests.py
@@ -1,5 +1,5 @@
+from django.apps.cache import get_app
from django.core.exceptions import ImproperlyConfigured
-from django.db.models.loading import get_app
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import six
diff --git a/tests/invalid_models/tests.py b/tests/invalid_models/tests.py
index 9c9db91da9..2f7815f96d 100644
--- a/tests/invalid_models/tests.py
+++ b/tests/invalid_models/tests.py
@@ -2,8 +2,8 @@ import copy
import sys
import unittest
+from django.apps.cache import cache, load_app
from django.core.management.validation import get_validation_errors
-from django.db.models.loading import cache, load_app
from django.test.utils import override_settings
from django.utils.six import StringIO
diff --git a/tests/managers_regress/tests.py b/tests/managers_regress/tests.py
index 3798b91ef5..11ad52ce44 100644
--- a/tests/managers_regress/tests.py
+++ b/tests/managers_regress/tests.py
@@ -1,8 +1,8 @@
from __future__ import unicode_literals
import copy
+from django.apps.cache import cache
from django.db import models
-from django.db.models.loading import cache
from django.template import Context, Template
from django.test import TestCase
from django.test.utils import override_settings
diff --git a/tests/migrations/models.py b/tests/migrations/models.py
index 3bb50289be..9726e4457a 100644
--- a/tests/migrations/models.py
+++ b/tests/migrations/models.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+from django.apps.cache import BaseAppCache
from django.db import models
-from django.db.models.loading import BaseAppCache
from django.utils.encoding import python_2_unicode_compatible
diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
index 48fb68b03d..fa8a212533 100644
--- a/tests/migrations/test_commands.py
+++ b/tests/migrations/test_commands.py
@@ -6,8 +6,8 @@ import copy
import os
import shutil
+from django.apps.cache import cache
from django.core.management import call_command, CommandError
-from django.db.models.loading import cache
from django.test.utils import override_settings
from django.utils import six
from django.utils._os import upath
diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py
index 9cbbbf294d..13e575862d 100644
--- a/tests/migrations/test_state.py
+++ b/tests/migrations/test_state.py
@@ -1,7 +1,7 @@
-from django.test import TestCase
+from django.apps.cache import BaseAppCache
from django.db import models
-from django.db.models.loading import BaseAppCache
from django.db.migrations.state import ProjectState, ModelState, InvalidBasesError
+from django.test import TestCase
class StateTests(TestCase):
diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py
index 0d64d40350..11109c8186 100644
--- a/tests/migrations/test_writer.py
+++ b/tests/migrations/test_writer.py
@@ -6,10 +6,10 @@ import copy
import datetime
import os
+from django.apps.cache import cache
from django.core.validators import RegexValidator, EmailValidator
from django.db import models, migrations
from django.db.migrations.writer import MigrationWriter
-from django.db.models.loading import cache
from django.test import TestCase, override_settings
from django.utils import six
from django.utils.deconstruct import deconstructible
diff --git a/tests/proxy_model_inheritance/tests.py b/tests/proxy_model_inheritance/tests.py
index 9941506303..af22e7caed 100644
--- a/tests/proxy_model_inheritance/tests.py
+++ b/tests/proxy_model_inheritance/tests.py
@@ -3,9 +3,9 @@ from __future__ import unicode_literals
import os
import sys
+from django.apps.cache import cache, load_app
from django.conf import settings
from django.core.management import call_command
-from django.db.models.loading import cache, load_app
from django.test import TestCase, TransactionTestCase
from django.test.utils import override_settings
from django.utils._os import upath
diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py
index 8be7929ac4..a900366744 100644
--- a/tests/proxy_models/tests.py
+++ b/tests/proxy_models/tests.py
@@ -1,13 +1,13 @@
from __future__ import unicode_literals
import copy
+from django.apps.cache import cache
from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.core import management
from django.core.exceptions import FieldError
from django.db import models, DEFAULT_DB_ALIAS
from django.db.models import signals
-from django.db.models.loading import cache
from django.test import TestCase
from django.test.utils import override_settings
diff --git a/tests/runtests.py b/tests/runtests.py
index f37c0e9dda..8bcb06e522 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -80,14 +80,14 @@ def get_test_modules():
def get_installed():
- from django.db.models.loading import get_apps
+ from django.apps.cache import get_apps
return [app.__name__.rsplit('.', 1)[0] for app in get_apps()]
def setup(verbosity, test_labels):
import django
+ from django.apps.cache import get_apps, load_app
from django.conf import settings
- from django.db.models.loading import get_apps, load_app
from django.test import TransactionTestCase, TestCase
print("Testing against Django installed in '%s'" % os.path.dirname(django.__file__))
diff --git a/tests/schema/models.py b/tests/schema/models.py
index 06ba8fb760..2df97b935c 100644
--- a/tests/schema/models.py
+++ b/tests/schema/models.py
@@ -1,5 +1,5 @@
+from django.apps.cache import BaseAppCache
from django.db import models
-from django.db.models.loading import BaseAppCache
# Because we want to test creation and deletion of these as separate things,
# these models are all inserted into a separate AppCache so the main test
diff --git a/tests/swappable_models/tests.py b/tests/swappable_models/tests.py
index 85484615a3..ae42366745 100644
--- a/tests/swappable_models/tests.py
+++ b/tests/swappable_models/tests.py
@@ -2,10 +2,10 @@ from __future__ import unicode_literals
from django.utils.six import StringIO
+from django.apps.cache import cache
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core import management
-from django.db.models.loading import cache
from django.test import TestCase
from django.test.utils import override_settings
diff --git a/tests/tablespaces/tests.py b/tests/tablespaces/tests.py
index 6a81643a0c..0ee1b0e742 100644
--- a/tests/tablespaces/tests.py
+++ b/tests/tablespaces/tests.py
@@ -2,9 +2,9 @@ from __future__ import unicode_literals
import copy
+from django.apps.cache import cache
from django.conf import settings
from django.db import connection
-from django.db.models.loading import cache
from django.core.management.color import no_style
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
diff --git a/tests/validation/test_unique.py b/tests/validation/test_unique.py
index b3d389e4ec..995949fed7 100644
--- a/tests/validation/test_unique.py
+++ b/tests/validation/test_unique.py
@@ -3,9 +3,9 @@ from __future__ import unicode_literals
import datetime
import unittest
+from django.apps.cache import BaseAppCache
from django.core.exceptions import ValidationError
from django.db import models
-from django.db.models.loading import BaseAppCache
from django.test import TestCase
from .models import (CustomPKModel, UniqueTogetherModel, UniqueFieldsModel,