From 99742c0dd88f32d1d7d6a139968c1e03f3defe68 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 11 Dec 2013 21:44:27 +0100 Subject: [PATCH 01/22] Moved django.db.models.loading to django.apps.cache. This commit doesn't contain any code changes; it's purely a refactoring. --- django/apps/__init__.py | 0 django/apps/cache.py | 396 ++++++++++++++++++ django/contrib/auth/tests/test_management.py | 2 +- .../management/commands/makemigrations.py | 2 +- django/core/management/commands/migrate.py | 2 +- django/core/management/commands/shell.py | 2 +- django/core/management/validation.py | 2 +- django/db/backends/sqlite3/schema.py | 2 +- django/db/migrations/loader.py | 2 +- django/db/migrations/questioner.py | 2 +- django/db/migrations/recorder.py | 2 +- django/db/migrations/state.py | 2 +- django/db/migrations/writer.py | 4 +- django/db/models/__init__.py | 4 +- django/db/models/base.py | 2 +- django/db/models/fields/__init__.py | 2 +- django/db/models/loading.py | 384 +---------------- django/db/models/options.py | 2 +- django/db/models/signals.py | 2 +- django/test/testcases.py | 2 +- docs/internals/deprecation.txt | 3 + tests/app_cache/models.py | 2 +- tests/app_cache/tests.py | 6 +- tests/app_loading/tests.py | 2 +- tests/contenttypes_tests/tests.py | 2 +- tests/defer_regress/tests.py | 2 +- tests/empty/tests.py | 2 +- tests/invalid_models/tests.py | 2 +- tests/managers_regress/tests.py | 2 +- tests/migrations/models.py | 2 +- tests/migrations/test_commands.py | 2 +- tests/migrations/test_state.py | 4 +- tests/migrations/test_writer.py | 2 +- tests/proxy_model_inheritance/tests.py | 2 +- tests/proxy_models/tests.py | 2 +- tests/runtests.py | 4 +- tests/schema/models.py | 2 +- tests/swappable_models/tests.py | 2 +- tests/tablespaces/tests.py | 2 +- tests/validation/test_unique.py | 2 +- 40 files changed, 448 insertions(+), 419 deletions(-) create mode 100644 django/apps/__init__.py create mode 100644 django/apps/cache.py diff --git a/django/apps/__init__.py b/django/apps/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/django/apps/cache.py b/django/apps/cache.py new file mode 100644 index 0000000000000..a5b645646f583 --- /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 bd51f39977ada..698947d57c107 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.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 239a0a416c5b8..7c4bb4f7cf495 100644 --- a/django/core/management/commands/makemigrations.py +++ b/django/core/management/commands/makemigrations.py @@ -3,6 +3,7 @@ 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.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 093c8a79d0950..196622304239a 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -6,6 +6,7 @@ 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.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 00a6602c0bb7c..0d84c9b2ece43 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -66,7 +66,7 @@ def run_shell(self, shell=None): 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 6bdcf853d43db..459923a0884e6 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 f04095507f545..69e3b61b874a5 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 f2510b83681d2..101588c4a11b3 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 7e798d310525f..7874f23ed0792 100644 --- a/django/db/migrations/questioner.py +++ b/django/db/migrations/questioner.py @@ -2,7 +2,7 @@ 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 be804e52aa58a..bf1cd225c232a 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 769b0005f81e3..6aa44ba10832a 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 22b0977ba40d4..150455b09ce5a 100644 --- a/django/db/migrations/writer.py +++ b/django/db/migrations/writer.py @@ -3,12 +3,12 @@ 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 12c31f89fd266..ad76347494d3b 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 dd4850c3d4929..f22506aa92dfc 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -5,6 +5,7 @@ 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.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 33adaedc7edf8..d69a4346e2a38 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -9,8 +9,8 @@ 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 21440216e0a36..795de130b5010 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 b14e61573c54a..5d99066343b8b 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -5,11 +5,11 @@ 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 6b011c20997ee..2543cf5f4af81 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 4dbff55204de1..52900ec47848e 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -15,6 +15,7 @@ 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.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 2e53901536909..d548f275fe6e1 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 1b4d33c2f9928..cc092390abd68 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 b72b862de3c60..564f9f6f48a7d 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 20ec064d69e6a..4795ca41399cc 100644 --- a/tests/app_loading/tests.py +++ b/tests/app_loading/tests.py @@ -5,7 +5,7 @@ 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 9d1e1f77eae1b..a56d19693308a 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 c03388b50ec13..0107e8167c229 100644 --- a/tests/defer_regress/tests.py +++ b/tests/defer_regress/tests.py @@ -2,10 +2,10 @@ 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 007d04c363aa1..2a9f568aeaf9b 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 9c9db91da9dda..2f7815f96d667 100644 --- a/tests/invalid_models/tests.py +++ b/tests/invalid_models/tests.py @@ -2,8 +2,8 @@ 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 3798b91ef5028..11ad52ce4450e 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 3bb50289bed8d..9726e4457a780 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 48fb68b03dfab..fa8a212533356 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -6,8 +6,8 @@ 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 9cbbbf294d7cc..13e575862dc37 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 0d64d403503fa..11109c8186531 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -6,10 +6,10 @@ 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 9941506303b4b..af22e7caedd63 100644 --- a/tests/proxy_model_inheritance/tests.py +++ b/tests/proxy_model_inheritance/tests.py @@ -3,9 +3,9 @@ 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 8be7929ac48d9..a900366744525 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 f37c0e9dda03d..8bcb06e522e41 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 06ba8fb760c83..2df97b935cced 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 85484615a3ec0..ae423667453d4 100644 --- a/tests/swappable_models/tests.py +++ b/tests/swappable_models/tests.py @@ -2,10 +2,10 @@ 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 6a81643a0c019..0ee1b0e742f95 100644 --- a/tests/tablespaces/tests.py +++ b/tests/tablespaces/tests.py @@ -2,9 +2,9 @@ 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 b3d389e4ecfa5..995949fed75a5 100644 --- a/tests/validation/test_unique.py +++ b/tests/validation/test_unique.py @@ -3,9 +3,9 @@ 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, From 1535a5dca4c1cd1ffa1aea78bd14343c156dbef9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 11 Dec 2013 22:19:19 +0100 Subject: [PATCH 02/22] Removed BaseAppCache.app_store. It was only storing redundant information. This is part of the effort to allow applications without a models module. --- django/apps/cache.py | 32 +++++++++++--------------- tests/app_loading/tests.py | 2 -- tests/invalid_models/tests.py | 2 -- tests/managers_regress/tests.py | 6 ----- tests/migrations/test_commands.py | 2 -- tests/migrations/test_writer.py | 20 ++++++---------- tests/proxy_model_inheritance/tests.py | 2 -- tests/proxy_models/tests.py | 2 -- tests/tablespaces/tests.py | 2 -- 9 files changed, 20 insertions(+), 50 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index a5b645646f583..7410151b88d3f 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -39,11 +39,8 @@ def _initialize(): [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={}, + app_labels=OrderedDict(), # Mapping of app_labels to a dictionary of model names to model code. # May contain apps that are not installed. @@ -154,9 +151,9 @@ def load_app(self, app_name, can_postpone=False): 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 + label = self._label_for(models) + if label not in self.app_labels: + self.app_labels[label] = models return models def app_cache_ready(self): @@ -174,17 +171,13 @@ def get_apps(self): """ self._populate() - apps = self.app_store.items() + # app_labels is an OrderedDict, which ensures that 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 = self.app_labels.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] + apps = [app for app in apps if app[0] in self.available_apps] + return [app[1] for app in apps] def _get_app_package(self, app): return '.'.join(app.__name__.split('.')[:-1]) @@ -282,8 +275,9 @@ def get_models(self, app_mod=None, 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())] + app_label = self._label_for(app_mod) + if app_label in self.app_labels: + app_list = [self.app_models.get(app_label, ModelDict())] else: app_list = [] else: diff --git a/tests/app_loading/tests.py b/tests/app_loading/tests.py index 4795ca41399cc..a8a5ba120fff0 100644 --- a/tests/app_loading/tests.py +++ b/tests/app_loading/tests.py @@ -20,12 +20,10 @@ def setUp(self): # need to be removed in order to prevent bad interactions # with the flush operation in other tests. self.old_app_models = copy.deepcopy(cache.app_models) - self.old_app_store = copy.deepcopy(cache.app_store) def tearDown(self): sys.path = self.old_path cache.app_models = self.old_app_models - cache.app_store = self.old_app_store def test_egg1(self): """Models module can be loaded from an app in an egg""" diff --git a/tests/invalid_models/tests.py b/tests/invalid_models/tests.py index 2f7815f96d667..46b3dc4c2ee5d 100644 --- a/tests/invalid_models/tests.py +++ b/tests/invalid_models/tests.py @@ -23,11 +23,9 @@ def setUp(self): # need to be removed in order to prevent bad interactions # with the flush operation in other tests. self.old_app_models = copy.deepcopy(cache.app_models) - self.old_app_store = copy.deepcopy(cache.app_store) def tearDown(self): cache.app_models = self.old_app_models - cache.app_store = self.old_app_store cache._get_models_cache = {} sys.stdout = self.old_stdout diff --git a/tests/managers_regress/tests.py b/tests/managers_regress/tests.py index 11ad52ce4450e..25717f7431bd8 100644 --- a/tests/managers_regress/tests.py +++ b/tests/managers_regress/tests.py @@ -115,7 +115,6 @@ def test_swappable_manager(self): # need to be removed in order to prevent bad interactions # with the flush operation in other tests. old_app_models = copy.deepcopy(cache.app_models) - old_app_store = copy.deepcopy(cache.app_store) class SwappableModel(models.Model): class Meta: @@ -131,7 +130,6 @@ class Meta: finally: cache.app_models = old_app_models - cache.app_store = old_app_store @override_settings(TEST_SWAPPABLE_MODEL='managers_regress.Parent') def test_custom_swappable_manager(self): @@ -140,7 +138,6 @@ def test_custom_swappable_manager(self): # need to be removed in order to prevent bad interactions # with the flush operation in other tests. old_app_models = copy.deepcopy(cache.app_models) - old_app_store = copy.deepcopy(cache.app_store) class SwappableModel(models.Model): @@ -160,7 +157,6 @@ class Meta: finally: cache.app_models = old_app_models - cache.app_store = old_app_store @override_settings(TEST_SWAPPABLE_MODEL='managers_regress.Parent') def test_explicit_swappable_manager(self): @@ -169,7 +165,6 @@ def test_explicit_swappable_manager(self): # need to be removed in order to prevent bad interactions # with the flush operation in other tests. old_app_models = copy.deepcopy(cache.app_models) - old_app_store = copy.deepcopy(cache.app_store) class SwappableModel(models.Model): @@ -189,7 +184,6 @@ class Meta: finally: cache.app_models = old_app_models - cache.app_store = old_app_store def test_regress_3871(self): related = RelatedModel.objects.create() diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index fa8a212533356..9d84d710bb2e9 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -133,11 +133,9 @@ def setUp(self): self.migration_dir = os.path.join(self.test_dir, 'migrations_%d' % self.creation_counter) self.migration_pkg = "migrations.migrations_%d" % self.creation_counter self._old_app_models = copy.deepcopy(cache.app_models) - self._old_app_store = copy.deepcopy(cache.app_store) def tearDown(self): cache.app_models = self._old_app_models - cache.app_store = self._old_app_store cache._get_models_cache = {} os.chdir(self.test_dir) diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index 11109c8186531..cc32a7d751241 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals -import copy import datetime import os @@ -116,8 +115,6 @@ def test_simple_migration(self): self.assertIn("Migration", result) def test_migration_path(self): - _old_app_store = copy.deepcopy(cache.app_store) - test_apps = [ 'migrations.migrations_test_apps.normal', 'migrations.migrations_test_apps.with_package_model', @@ -125,13 +122,10 @@ def test_migration_path(self): base_dir = os.path.dirname(os.path.dirname(__file__)) - try: - with override_settings(INSTALLED_APPS=test_apps): - for app in test_apps: - cache.load_app(app) - migration = migrations.Migration('0001_initial', app.split('.')[-1]) - expected_path = os.path.join(base_dir, *(app.split('.') + ['migrations', '0001_initial.py'])) - writer = MigrationWriter(migration) - self.assertEqual(writer.path, expected_path) - finally: - cache.app_store = _old_app_store + with override_settings(INSTALLED_APPS=test_apps): + for app in test_apps: + cache.load_app(app) + migration = migrations.Migration('0001_initial', app.split('.')[-1]) + expected_path = os.path.join(base_dir, *(app.split('.') + ['migrations', '0001_initial.py'])) + writer = MigrationWriter(migration) + self.assertEqual(writer.path, expected_path) diff --git a/tests/proxy_model_inheritance/tests.py b/tests/proxy_model_inheritance/tests.py index af22e7caedd63..0fad8c594fe6e 100644 --- a/tests/proxy_model_inheritance/tests.py +++ b/tests/proxy_model_inheritance/tests.py @@ -32,8 +32,6 @@ def setUp(self): def tearDown(self): sys.path = self.old_sys_path - del cache.app_store[cache.app_labels['app1']] - del cache.app_store[cache.app_labels['app2']] del cache.app_labels['app1'] del cache.app_labels['app2'] del cache.app_models['app1'] diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py index a900366744525..5b9433bbf7930 100644 --- a/tests/proxy_models/tests.py +++ b/tests/proxy_models/tests.py @@ -160,7 +160,6 @@ def test_swappable(self): # need to be removed in order to prevent bad interactions # with the flush operation in other tests. old_app_models = copy.deepcopy(cache.app_models) - old_app_store = copy.deepcopy(cache.app_store) class SwappableModel(models.Model): @@ -178,7 +177,6 @@ class Meta: proxy = True finally: cache.app_models = old_app_models - cache.app_store = old_app_store def test_myperson_manager(self): Person.objects.create(name="fred") diff --git a/tests/tablespaces/tests.py b/tests/tablespaces/tests.py index 0ee1b0e742f95..04240bcaa1d07 100644 --- a/tests/tablespaces/tests.py +++ b/tests/tablespaces/tests.py @@ -29,7 +29,6 @@ def setUp(self): # The unmanaged models need to be removed after the test in order to # prevent bad interactions with the flush operation in other tests. self.old_app_models = copy.deepcopy(cache.app_models) - self.old_app_store = copy.deepcopy(cache.app_store) for model in Article, Authors, Reviewers, Scientist: model._meta.managed = True @@ -39,7 +38,6 @@ def tearDown(self): model._meta.managed = False cache.app_models = self.old_app_models - cache.app_store = self.old_app_store cache._get_models_cache = {} def assertNumContains(self, haystack, needle, count): From 6c7c476a1e8d40d5448e1b657801222077a588e3 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 11 Dec 2013 23:23:10 +0100 Subject: [PATCH 03/22] Removed ModelDict. Its only difference with OrderedDict is that it didn't deepcopy its keys. However it wasn't used anywhere with models modules as keys, only as values. So this commit doesn't result in any change in functionality. --- django/apps/cache.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index 7410151b88d3f..8afe3dd4cf538 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -1,7 +1,6 @@ "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 @@ -19,16 +18,6 @@ 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 @@ -44,7 +33,7 @@ def _initialize(): # Mapping of app_labels to a dictionary of model names to model code. # May contain apps that are not installed. - app_models=ModelDict(), + app_models=OrderedDict(), # Mapping of app_labels to errors raised when trying to import the app. app_errors={}, @@ -277,12 +266,12 @@ def get_models(self, app_mod=None, if app_mod: app_label = self._label_for(app_mod) if app_label in self.app_labels: - app_list = [self.app_models.get(app_label, ModelDict())] + app_list = [self.app_models.get(app_label, OrderedDict())] else: app_list = [] else: if only_installed: - app_list = [self.app_models.get(app_label, ModelDict()) + app_list = [self.app_models.get(app_label, OrderedDict()) for app_label in six.iterkeys(self.app_labels)] else: app_list = six.itervalues(self.app_models) @@ -332,7 +321,7 @@ def register_models(self, app_label, *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()) + model_dict = self.app_models.setdefault(app_label, OrderedDict()) 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 From 2cecee41a84ff608ecf186b18ae7f5e7208db865 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 11 Dec 2013 23:31:34 +0100 Subject: [PATCH 04/22] Removed module-level functions for the app cache. Since the original ones in django.db.models.loading were kept only for backwards compatibility, there's no need to recreate them. However, many internals of Django still relied on them. They were also imported in django.db.models. They never appear in the documentation, except a quick mention of get_models and get_app in the 1.2 release notes to document an edge case in GIS. I don't think that makes them a public API. This commit doesn't change the overall amount of global state but clarifies that it's tied to the app_cache object instead of hiding it behind half a dozen functions. --- django/apps/__init__.py | 1 + django/apps/cache.py | 19 +---------- django/contrib/admin/validation.py | 3 +- django/contrib/admindocs/views.py | 7 ++-- django/contrib/auth/__init__.py | 4 +-- django/contrib/auth/management/__init__.py | 9 ++--- django/contrib/auth/tests/test_management.py | 8 ++--- django/contrib/comments/views/comments.py | 3 +- django/contrib/contenttypes/management.py | 9 ++--- django/contrib/contenttypes/models.py | 3 +- django/contrib/gis/sitemaps/kml.py | 3 +- django/contrib/gis/sitemaps/views.py | 4 +-- .../core/checks/compatibility/django_1_6_0.py | 3 +- django/core/management/base.py | 4 +-- django/core/management/commands/dumpdata.py | 20 +++++------ django/core/management/commands/flush.py | 5 +-- django/core/management/commands/loaddata.py | 4 +-- .../management/commands/makemigrations.py | 6 ++-- django/core/management/commands/migrate.py | 8 ++--- django/core/management/commands/shell.py | 4 +-- .../management/commands/sqlsequencereset.py | 5 +-- django/core/management/sql.py | 9 ++--- django/core/management/validation.py | 14 ++++---- django/core/serializers/base.py | 3 +- django/core/serializers/python.py | 5 +-- django/core/serializers/xml_serializer.py | 3 +- django/db/backends/__init__.py | 13 +++++--- django/db/migrations/loader.py | 9 ++--- django/db/migrations/questioner.py | 4 +-- django/db/migrations/writer.py | 14 ++++---- django/db/models/__init__.py | 3 -- django/db/models/base.py | 5 +-- django/db/models/fields/__init__.py | 4 +-- django/db/models/loading.py | 24 +++++++------- django/db/models/options.py | 8 ++--- django/db/models/signals.py | 4 +-- django/db/utils.py | 4 +-- django/test/simple.py | 8 ++--- django/test/testcases.py | 8 ++--- tests/app_cache/tests.py | 11 ++++--- tests/app_loading/tests.py | 33 ++++++++++--------- tests/commands_sql/tests.py | 15 +++++---- tests/defer_regress/tests.py | 14 ++++---- tests/empty/tests.py | 4 +-- tests/invalid_models/tests.py | 10 +++--- tests/managers_regress/tests.py | 14 ++++---- tests/migrations/test_commands.py | 12 +++---- tests/migrations/test_operations.py | 5 +-- tests/migrations/test_writer.py | 4 +-- tests/proxy_model_inheritance/tests.py | 16 ++++----- tests/proxy_models/tests.py | 6 ++-- tests/runtests.py | 10 +++--- tests/swappable_models/tests.py | 6 ++-- tests/tablespaces/tests.py | 8 ++--- tests/test_suite_override/tests.py | 4 +-- 55 files changed, 226 insertions(+), 220 deletions(-) diff --git a/django/apps/__init__.py b/django/apps/__init__.py index e69de29bb2d1d..0384b1257d2ff 100644 --- a/django/apps/__init__.py +++ b/django/apps/__init__.py @@ -0,0 +1 @@ +from .cache import app_cache, UnavailableApp # NOQA diff --git a/django/apps/cache.py b/django/apps/cache.py index 8afe3dd4cf538..b5fd1c37dac58 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -12,8 +12,6 @@ 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' @@ -361,19 +359,4 @@ 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 +app_cache = AppCache() diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index 9079678639cdf..c8bd4af1209cf 100644 --- a/django/contrib/admin/validation.py +++ b/django/contrib/admin/validation.py @@ -1,3 +1,4 @@ +from django.apps import app_cache from django.core.exceptions import ImproperlyConfigured from django.db import models from django.db.models.fields import FieldDoesNotExist @@ -17,7 +18,7 @@ class BaseValidator(object): def __init__(self): # Before we can introspect models, they need to be fully loaded so that # inter-relations are set up correctly. We force that here. - models.get_apps() + app_cache.get_apps() def validate(self, cls, model): for m in dir(self): diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index 482d33e76f75f..e1edf0aad8393 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -4,6 +4,7 @@ import re from django import template +from django.apps import app_cache from django.conf import settings from django.contrib import admin from django.contrib.admin.views.decorators import staff_member_required @@ -182,7 +183,7 @@ class ModelIndexView(BaseAdminDocsView): template_name = 'admin_doc/model_index.html' def get_context_data(self, **kwargs): - m_list = [m._meta for m in models.get_models()] + m_list = [m._meta for m in app_cache.get_models()] kwargs.update({'models': m_list}) return super(ModelIndexView, self).get_context_data(**kwargs) @@ -193,11 +194,11 @@ class ModelDetailView(BaseAdminDocsView): def get_context_data(self, **kwargs): # Get the model class. try: - app_mod = models.get_app(self.kwargs['app_label']) + app_mod = app_cache.get_app(self.kwargs['app_label']) except ImproperlyConfigured: raise Http404(_("App %r not found") % self.kwargs['app_label']) model = None - for m in models.get_models(app_mod): + for m in app_cache.get_models(app_mod): if m._meta.model_name == self.kwargs['model_name']: model = m break diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index 83aa445bac631..3c5a40c18485c 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -123,13 +123,13 @@ def get_user_model(): """ Returns the User model that is active in this project. """ - from django.db.models import get_model + from django.apps import app_cache try: app_label, model_name = settings.AUTH_USER_MODEL.split('.') except ValueError: raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'") - user_model = get_model(app_label, model_name) + user_model = app_cache.get_model(app_label, model_name) if user_model is None: raise ImproperlyConfigured("AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL) return user_model diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index 8fd08ac57cf6c..5f24bf069e6bb 100644 --- a/django/contrib/auth/management/__init__.py +++ b/django/contrib/auth/management/__init__.py @@ -6,12 +6,13 @@ import getpass import unicodedata +from django.apps import app_cache, UnavailableApp from django.contrib.auth import (models as auth_app, get_permission_codename, get_user_model) from django.core import exceptions from django.core.management.base import CommandError from django.db import DEFAULT_DB_ALIAS, router -from django.db.models import get_model, get_models, signals, UnavailableApp +from django.db.models import signals from django.utils.encoding import DEFAULT_LOCALE_ENCODING from django.utils import six from django.utils.six.moves import input @@ -61,7 +62,7 @@ def _check_permission_clashing(custom, builtin, ctype): def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kwargs): try: - get_model('auth', 'Permission') + app_cache.get_model('auth', 'Permission') except UnavailableApp: return @@ -70,7 +71,7 @@ def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kw from django.contrib.contenttypes.models import ContentType - app_models = get_models(app) + app_models = app_cache.get_models(app) # This will hold the permissions we're looking for as # (content_type, (codename, name)) @@ -119,7 +120,7 @@ def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kw def create_superuser(app, created_models, verbosity, db, **kwargs): try: - get_model('auth', 'Permission') + app_cache.get_model('auth', 'Permission') UserModel = get_user_model() except UnavailableApp: return diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py index 698947d57c107..43ad15dcabc36 100644 --- a/django/contrib/auth/tests/test_management.py +++ b/django/contrib/auth/tests/test_management.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from datetime import date -from django.apps.cache import get_app +from django.apps import app_cache from django.contrib.auth import models, management from django.contrib.auth.management import create_permissions from django.contrib.auth.management.commands import changepassword @@ -184,21 +184,21 @@ class CustomUserModelValidationTestCase(TestCase): def test_required_fields_is_list(self): "REQUIRED_FIELDS should be a list." new_io = StringIO() - get_validation_errors(new_io, get_app('auth')) + get_validation_errors(new_io, app_cache.get_app('auth')) self.assertIn("The REQUIRED_FIELDS must be a list or tuple.", new_io.getvalue()) @override_settings(AUTH_USER_MODEL='auth.CustomUserBadRequiredFields') def test_username_not_in_required_fields(self): "USERNAME_FIELD should not appear in REQUIRED_FIELDS." new_io = StringIO() - get_validation_errors(new_io, get_app('auth')) + get_validation_errors(new_io, app_cache.get_app('auth')) self.assertIn("The field named as the USERNAME_FIELD should not be included in REQUIRED_FIELDS on a swappable User model.", new_io.getvalue()) @override_settings(AUTH_USER_MODEL='auth.CustomUserNonUniqueUsername') def test_username_non_unique(self): "A non-unique USERNAME_FIELD should raise a model validation error." new_io = StringIO() - get_validation_errors(new_io, get_app('auth')) + get_validation_errors(new_io, app_cache.get_app('auth')) self.assertIn("The USERNAME_FIELD must be unique. Add unique=True to the field parameters.", new_io.getvalue()) diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py index a2cbe33c0efc4..5d7c543adb8cc 100644 --- a/django/contrib/comments/views/comments.py +++ b/django/contrib/comments/views/comments.py @@ -1,4 +1,5 @@ from django import http +from django.apps import app_cache from django.conf import settings from django.contrib import comments from django.contrib.comments import signals @@ -48,7 +49,7 @@ def post_comment(request, next=None, using=None): if ctype is None or object_pk is None: return CommentPostBadRequest("Missing content_type or object_pk field.") try: - model = models.get_model(*ctype.split(".", 1)) + model = app_cache.get_model(*ctype.split(".", 1)) target = model._default_manager.using(using).get(pk=object_pk) except TypeError: return CommentPostBadRequest( diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index 3d2fc4b95ec2c..477464c37e1ef 100644 --- a/django/contrib/contenttypes/management.py +++ b/django/contrib/contenttypes/management.py @@ -1,6 +1,7 @@ +from django.apps import app_cache, UnavailableApp from django.contrib.contenttypes.models import ContentType from django.db import DEFAULT_DB_ALIAS, router -from django.db.models import get_apps, get_model, get_models, signals, UnavailableApp +from django.db.models import signals from django.utils.encoding import smart_text from django.utils import six from django.utils.six.moves import input @@ -12,7 +13,7 @@ def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, * entries that no longer have a matching model class. """ try: - get_model('contenttypes', 'ContentType') + app_cache.get_model('contenttypes', 'ContentType') except UnavailableApp: return @@ -20,7 +21,7 @@ def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, * return ContentType.objects.clear_cache() - app_models = get_models(app) + app_models = app_cache.get_models(app) if not app_models: return # They all have the same app_label, get the first one. @@ -85,7 +86,7 @@ def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, * def update_all_contenttypes(verbosity=2, **kwargs): - for app in get_apps(): + for app in app_cache.get_apps(): update_contenttypes(app, None, verbosity, **kwargs) signals.post_migrate.connect(update_contenttypes) diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index aec15d6664896..90dea5b8111a5 100644 --- a/django/contrib/contenttypes/models.py +++ b/django/contrib/contenttypes/models.py @@ -1,3 +1,4 @@ +from django.apps import app_cache from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_text, force_text @@ -156,7 +157,7 @@ def __str__(self): def model_class(self): "Returns the Python model class for this type of content." - return models.get_model(self.app_label, self.model, + return app_cache.get_model(self.app_label, self.model, only_installed=False) def get_object_for_this_type(self, **kwargs): diff --git a/django/contrib/gis/sitemaps/kml.py b/django/contrib/gis/sitemaps/kml.py index 5e74c6c47dc0c..1e4fc82550f8c 100644 --- a/django/contrib/gis/sitemaps/kml.py +++ b/django/contrib/gis/sitemaps/kml.py @@ -1,3 +1,4 @@ +from django.apps import app_cache from django.core import urlresolvers from django.contrib.sitemaps import Sitemap from django.contrib.gis.db.models.fields import GeometryField @@ -25,7 +26,7 @@ def _build_kml_sources(self, sources): """ kml_sources = [] if sources is None: - sources = models.get_models() + sources = app_cache.get_models() for source in sources: if isinstance(source, models.base.ModelBase): for field in source._meta.fields: diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index 0672b800cc2ec..e68523981e36a 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -2,6 +2,7 @@ import warnings +from django.apps import app_cache from django.http import HttpResponse, Http404 from django.template import loader from django.contrib.sites.models import get_current_site @@ -9,7 +10,6 @@ from django.core.paginator import EmptyPage, PageNotAnInteger from django.contrib.gis.db.models.fields import GeometryField from django.db import connections, DEFAULT_DB_ALIAS -from django.db.models import get_model from django.db.models.fields import FieldDoesNotExist from django.utils import six from django.utils.translation import ugettext as _ @@ -81,7 +81,7 @@ def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB must be that of a geographic field. """ placemarks = [] - klass = get_model(label, model) + klass = app_cache.get_model(label, model) if not klass: raise Http404('You must supply a valid app label and module name. Got "%s.%s"' % (label, model)) diff --git a/django/core/checks/compatibility/django_1_6_0.py b/django/core/checks/compatibility/django_1_6_0.py index a00196f2c0f9f..a42249de60581 100644 --- a/django/core/checks/compatibility/django_1_6_0.py +++ b/django/core/checks/compatibility/django_1_6_0.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +from django.apps import app_cache from django.db import models @@ -31,7 +32,7 @@ def check_boolean_field_default_value(): warns the user that the default has changed from False to Null. """ fields = [] - for cls in models.get_models(): + for cls in app_cache.get_models(): opts = cls._meta for f in opts.local_fields: if isinstance(f, models.BooleanField) and not f.has_default(): diff --git a/django/core/management/base.py b/django/core/management/base.py index ef967d021fd15..d749bc2e8e52b 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -342,11 +342,11 @@ class AppCommand(BaseCommand): args = '' def handle(self, *app_labels, **options): - from django.db import models + from django.apps import app_cache if not app_labels: raise CommandError('Enter at least one appname.') try: - app_list = [models.get_app(app_label) for app_label in app_labels] + app_list = [app_cache.get_app(app_label) for app_label in app_labels] except (ImproperlyConfigured, ImportError) as e: raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e) output = [] diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 85817e9194896..4f87b0400339b 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -38,7 +38,7 @@ class Command(BaseCommand): args = '[appname appname.ModelName ...]' def handle(self, *app_labels, **options): - from django.db.models import get_app, get_apps, get_model + from django.apps import app_cache format = options.get('format') indent = options.get('indent') @@ -64,13 +64,13 @@ def handle(self, *app_labels, **options): for exclude in excludes: if '.' in exclude: app_label, model_name = exclude.split('.', 1) - model_obj = get_model(app_label, model_name) + model_obj = app_cache.get_model(app_label, model_name) if not model_obj: raise CommandError('Unknown model in excludes: %s' % exclude) excluded_models.add(model_obj) else: try: - app_obj = get_app(exclude) + app_obj = app_cache.get_app(exclude) excluded_apps.add(app_obj) except ImproperlyConfigured: raise CommandError('Unknown app in excludes: %s' % exclude) @@ -78,7 +78,7 @@ def handle(self, *app_labels, **options): if len(app_labels) == 0: if primary_keys: raise CommandError("You can only use --pks option with one model") - app_list = OrderedDict((app, None) for app in get_apps() if app not in excluded_apps) + app_list = OrderedDict((app, None) for app in app_cache.get_apps() if app not in excluded_apps) else: if len(app_labels) > 1 and primary_keys: raise CommandError("You can only use --pks option with one model") @@ -87,12 +87,12 @@ def handle(self, *app_labels, **options): try: app_label, model_label = label.split('.') try: - app = get_app(app_label) + app = app_cache.get_app(app_label) except ImproperlyConfigured: raise CommandError("Unknown application: %s" % app_label) if app in excluded_apps: continue - model = get_model(app_label, model_label) + model = app_cache.get_model(app_label, model_label) if model is None: raise CommandError("Unknown model: %s.%s" % (app_label, model_label)) @@ -107,7 +107,7 @@ def handle(self, *app_labels, **options): # This is just an app - no model qualifier app_label = label try: - app = get_app(app_label) + app = app_cache.get_app(app_label) except ImproperlyConfigured: raise CommandError("Unknown application: %s" % app_label) if app in excluded_apps: @@ -160,13 +160,13 @@ def sort_dependencies(app_list): is serialized before a normal model, and any model with a natural key dependency has it's dependencies serialized first. """ - from django.db.models import get_model, get_models + from django.apps import app_cache # Process the list of models, and get the list of dependencies model_dependencies = [] models = set() for app, model_list in app_list: if model_list is None: - model_list = get_models(app) + model_list = app_cache.get_models(app) for model in model_list: models.add(model) @@ -174,7 +174,7 @@ def sort_dependencies(app_list): if hasattr(model, 'natural_key'): deps = getattr(model.natural_key, 'dependencies', []) if deps: - deps = [get_model(*d.split('.')) for d in deps] + deps = [app_cache.get_model(*d.split('.')) for d in deps] else: deps = [] diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 130338a55ac90..f4b221a32c817 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -2,8 +2,9 @@ from importlib import import_module from optparse import make_option +from django.apps import app_cache from django.conf import settings -from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS +from django.db import connections, router, transaction, DEFAULT_DB_ALIAS from django.core.management import call_command from django.core.management.base import NoArgsCommand, CommandError from django.core.management.color import no_style @@ -93,6 +94,6 @@ def emit_post_migrate(verbosity, interactive, database): # Emit the post migrate signal. This allows individual applications to # respond as if the database had been migrated from scratch. all_models = [] - for app in models.get_apps(): + for app in app_cache.get_apps(): all_models.extend(router.get_migratable_models(app, database, include_auto_created=True)) emit_post_migrate_signal(set(all_models), verbosity, interactive, database) diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 59c1343271b9e..64a57fbc7940a 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -7,13 +7,13 @@ import zipfile from optparse import make_option +from django.apps import app_cache from django.conf import settings from django.core import serializers from django.core.management.base import BaseCommand, CommandError from django.core.management.color import no_style from django.db import (connections, router, transaction, DEFAULT_DB_ALIAS, IntegrityError, DatabaseError) -from django.db.models import get_app_paths from django.utils import lru_cache from django.utils.encoding import force_text from django.utils.functional import cached_property @@ -230,7 +230,7 @@ def fixture_dirs(self): current directory. """ dirs = [] - for path in get_app_paths(): + for path in app_cache.get_app_paths(): d = os.path.join(path, 'fixtures') if os.path.isdir(d): dirs.append(d) diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py index 7c4bb4f7cf495..cd891c404d14c 100644 --- a/django/core/management/commands/makemigrations.py +++ b/django/core/management/commands/makemigrations.py @@ -3,7 +3,7 @@ import operator from optparse import make_option -from django.apps.cache import cache +from django.apps import app_cache from django.core.management.base import BaseCommand, CommandError from django.core.exceptions import ImproperlyConfigured from django.db import connections, DEFAULT_DB_ALIAS, migrations @@ -38,7 +38,7 @@ def handle(self, *app_labels, **options): bad_app_labels = set() for app_label in app_labels: try: - cache.get_app(app_label) + app_cache.get_app(app_label) except ImproperlyConfigured: bad_app_labels.add(app_label) if bad_app_labels: @@ -73,7 +73,7 @@ def handle(self, *app_labels, **options): # Detect changes autodetector = MigrationAutodetector( loader.graph.project_state(), - ProjectState.from_app_cache(cache), + ProjectState.from_app_cache(app_cache), InteractiveMigrationQuestioner(specified_apps=app_labels), ) changes = autodetector.changes(graph=loader.graph, trim_to_apps=app_labels or None) diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index 196622304239a..39a2d9a78495b 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -6,13 +6,13 @@ import itertools import traceback -from django.apps.cache import cache +from django.apps import app_cache from django.conf import settings from django.core.management import call_command from django.core.management.base import BaseCommand, CommandError from django.core.management.color import no_style from django.core.management.sql import custom_sql_for_model, emit_post_migrate_signal, emit_pre_migrate_signal -from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS +from django.db import connections, router, transaction, DEFAULT_DB_ALIAS from django.db.migrations.executor import MigrationExecutor from django.db.migrations.loader import MigrationLoader, AmbiguityError from django.db.migrations.state import ProjectState @@ -136,7 +136,7 @@ def handle(self, *args, **options): # If there's changes that aren't in migrations yet, tell them how to fix it. autodetector = MigrationAutodetector( executor.loader.graph.project_state(), - ProjectState.from_app_cache(cache), + ProjectState.from_app_cache(app_cache), ) changes = autodetector.changes(graph=executor.loader.graph) if changes: @@ -182,7 +182,7 @@ def sync_apps(self, connection, apps): all_models = [ (app.__name__.split('.')[-2], router.get_migratable_models(app, connection.alias, include_auto_created=True)) - for app in models.get_apps() if app.__name__.split('.')[-2] in apps + for app in app_cache.get_apps() if app.__name__.split('.')[-2] in apps ] def model_installed(model): diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index 0d84c9b2ece43..12af814161165 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -66,8 +66,8 @@ def run_shell(self, shell=None): def handle_noargs(self, **options): # XXX: (Temporary) workaround for ticket #1796: force early loading of all # models from installed apps. - from django.apps.cache import get_models - get_models() + from django.apps import app_cache + app_cache.get_models() use_plain = options.get('plain', False) no_startup = options.get('no_startup', False) diff --git a/django/core/management/commands/sqlsequencereset.py b/django/core/management/commands/sqlsequencereset.py index 8f4ea823c7c10..24ec25e8ed564 100644 --- a/django/core/management/commands/sqlsequencereset.py +++ b/django/core/management/commands/sqlsequencereset.py @@ -2,8 +2,9 @@ from optparse import make_option +from django.apps import app_cache from django.core.management.base import AppCommand -from django.db import connections, models, DEFAULT_DB_ALIAS +from django.db import connections, DEFAULT_DB_ALIAS class Command(AppCommand): @@ -20,4 +21,4 @@ class Command(AppCommand): def handle_app(self, app, **options): connection = connections[options.get('database')] - return '\n'.join(connection.ops.sequence_reset_sql(self.style, models.get_models(app, include_auto_created=True))) + return '\n'.join(connection.ops.sequence_reset_sql(self.style, app_cache.get_models(app, include_auto_created=True))) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 6f60d73ae925f..bcd4769dde11a 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -5,6 +5,7 @@ import re import warnings +from django.apps import app_cache from django.conf import settings from django.core.management.base import CommandError from django.db import models, router @@ -24,7 +25,7 @@ def sql_create(app, style, connection): # We trim models from the current app so that the sqlreset command does not # generate invalid SQL (leaving models out of known_models is harmless, so # we can be conservative). - app_models = models.get_models(app, include_auto_created=True) + app_models = app_cache.get_models(app, include_auto_created=True) final_output = [] tables = connection.introspection.table_names() known_models = set(model for model in connection.introspection.installed_models(tables) if model not in app_models) @@ -168,7 +169,7 @@ def _split_statements(content): def custom_sql_for_model(model, style, connection): opts = model._meta app_dirs = [] - app_dir = models.get_app_path(model._meta.app_label) + app_dir = app_cache.get_app_path(model._meta.app_label) app_dirs.append(os.path.normpath(os.path.join(app_dir, 'sql'))) # Deprecated location -- remove in Django 1.9 @@ -206,7 +207,7 @@ def custom_sql_for_model(model, style, connection): def emit_pre_migrate_signal(create_models, verbosity, interactive, db): # Emit the pre_migrate signal for every application. - for app in models.get_apps(): + for app in app_cache.get_apps(): app_name = app.__name__.split('.')[-2] if verbosity >= 2: print("Running pre-migrate handlers for application %s" % app_name) @@ -219,7 +220,7 @@ def emit_pre_migrate_signal(create_models, verbosity, interactive, db): def emit_post_migrate_signal(created_models, verbosity, interactive, db): # Emit the post_migrate signal for every application. - for app in models.get_apps(): + for app in app_cache.get_apps(): app_name = app.__name__.split('.')[-2] if verbosity >= 2: print("Running post-migrate handlers for application %s" % app_name) diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 459923a0884e6..89aa5ad068c1f 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -26,16 +26,16 @@ 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.apps import app_cache from django.db import connection, models from django.db.models.deletion import SET_NULL, SET_DEFAULT e = ModelErrorCollection(outfile) - for (app_name, error) in get_app_errors().items(): + for (app_name, error) in app_cache.get_app_errors().items(): e.add(app_name, error) - for cls in models.get_models(app, include_swapped=True): + for cls in app_cache.get_models(app, include_swapped=True): opts = cls._meta # Check swappable attribute. @@ -45,7 +45,7 @@ def get_validation_errors(outfile, app=None): except ValueError: e.add(opts, "%s is not of the form 'app_label.app_name'." % opts.swappable) continue - if not models.get_model(app_label, model_name): + if not app_cache.get_model(app_label, model_name): e.add(opts, "Model has been swapped out for '%s' which has not been installed or is abstract." % opts.swapped) # No need to perform any other validation checks on a swapped model. continue @@ -155,7 +155,7 @@ def get_validation_errors(outfile, app=None): # Check to see if the related field will clash with any existing # fields, m2m fields, m2m related objects or related objects if f.rel: - if f.rel.to not in models.get_models(): + if f.rel.to not in app_cache.get_models(): # If the related model is swapped, provide a hint; # otherwise, the model just hasn't been installed. if not isinstance(f.rel.to, six.string_types) and f.rel.to._meta.swapped: @@ -210,7 +210,7 @@ def get_validation_errors(outfile, app=None): # Check to see if the related m2m field will clash with any # existing fields, m2m fields, m2m related objects or related # objects - if f.rel.to not in models.get_models(): + if f.rel.to not in app_cache.get_models(): # If the related model is swapped, provide a hint; # otherwise, the model just hasn't been installed. if not isinstance(f.rel.to, six.string_types) and f.rel.to._meta.swapped: @@ -268,7 +268,7 @@ def get_validation_errors(outfile, app=None): ) else: seen_to = True - if f.rel.through not in models.get_models(include_auto_created=True): + if f.rel.through not in app_cache.get_models(include_auto_created=True): e.add(opts, "'%s' specifies an m2m relation through model " "%s, which has not been installed." % (f.name, f.rel.through)) signature = (f.rel.to, cls, f.rel.through) diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index b7d3e28a0d338..e575c1d1c252e 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -3,6 +3,7 @@ """ import warnings +from django.apps import app_cache from django.db import models from django.utils import six @@ -139,7 +140,7 @@ def __init__(self, stream_or_string, **options): # hack to make sure that the models have all been loaded before # deserialization starts (otherwise subclass calls to get_model() # and friends might fail...) - models.get_apps() + app_cache.get_apps() def __iter__(self): return self diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 4ac7cc4cf1009..07f857a198deb 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -5,6 +5,7 @@ """ from __future__ import unicode_literals +from django.apps import app_cache from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS @@ -87,7 +88,7 @@ def Deserializer(object_list, **options): db = options.pop('using', DEFAULT_DB_ALIAS) ignore = options.pop('ignorenonexistent', False) - models.get_apps() + app_cache.get_apps() for d in object_list: # Look up the model and starting build a dict of data for it. Model = _get_model(d["model"]) @@ -153,7 +154,7 @@ def _get_model(model_identifier): Helper to look up a model from an "app_label.model_name" string. """ try: - Model = models.get_model(*model_identifier.split(".")) + Model = app_cache.get_model(*model_identifier.split(".")) except TypeError: Model = None if Model is None: diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 6870ac7d445ac..e9bea84bb1f7a 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals +from django.apps import app_cache from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS @@ -276,7 +277,7 @@ def _get_model_from_node(self, node, attr): "<%s> node is missing the required '%s' attribute" % (node.nodeName, attr)) try: - Model = models.get_model(*model_identifier.split(".")) + Model = app_cache.get_model(*model_identifier.split(".")) except TypeError: Model = None if Model is None: diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 3faaeffb219e9..edbca0e5572d0 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -1268,9 +1268,10 @@ def django_table_names(self, only_existing=False): If only_existing is True, the resulting list will only include the tables that actually exist in the database. """ - from django.db import models, router + from django.apps import app_cache + from django.db import router tables = set() - for app in models.get_apps(): + for app in app_cache.get_apps(): for model in router.get_migratable_models(app, self.connection.alias): if not model._meta.managed: continue @@ -1288,9 +1289,10 @@ def django_table_names(self, only_existing=False): def installed_models(self, tables): "Returns a set of all models represented by the provided list of table names." - from django.db import models, router + from django.apps import app_cache + from django.db import router all_models = [] - for app in models.get_apps(): + for app in app_cache.get_apps(): all_models.extend(router.get_migratable_models(app, self.connection.alias)) tables = list(map(self.table_name_converter, tables)) return set([ @@ -1300,9 +1302,10 @@ def installed_models(self, tables): def sequence_list(self): "Returns a list of information about all DB sequences for all models in all apps." + from django.apps import app_cache from django.db import models, router - apps = models.get_apps() + apps = app_cache.get_apps() sequence_list = [] for app in apps: diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py index 101588c4a11b3..f4a71684416b4 100644 --- a/django/db/migrations/loader.py +++ b/django/db/migrations/loader.py @@ -1,7 +1,8 @@ +from importlib import import_module import os import sys -from importlib import import_module -from django.apps.cache import cache + +from django.apps import app_cache from django.db.migrations.recorder import MigrationRecorder from django.db.migrations.graph import MigrationGraph from django.utils import six @@ -45,7 +46,7 @@ def migrations_module(cls, app_label): if app_label in settings.MIGRATION_MODULES: return settings.MIGRATION_MODULES[app_label] else: - return '%s.migrations' % cache.get_app_package(app_label) + return '%s.migrations' % app_cache.get_app_package(app_label) def load_disk(self): """ @@ -54,7 +55,7 @@ def load_disk(self): self.disk_migrations = {} self.unmigrated_apps = set() self.migrated_apps = set() - for app in cache.get_apps(): + for app in app_cache.get_apps(): # Get the migrations module directory app_label = app.__name__.split(".")[-2] module_name = self.migrations_module(app_label) diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py index 7874f23ed0792..d9446f8d885c7 100644 --- a/django/db/migrations/questioner.py +++ b/django/db/migrations/questioner.py @@ -2,7 +2,7 @@ import os import sys -from django.apps.cache import cache +from django.apps import app_cache from django.utils import datetime_safe from django.utils.six.moves import input from django.core.exceptions import ImproperlyConfigured @@ -29,7 +29,7 @@ def ask_initial(self, app_label): # Apps from the new app template will have these; the python # file check will ensure we skip South ones. try: - models_module = cache.get_app(app_label) + models_module = app_cache.get_app(app_label) except ImproperlyConfigured: # It's a fake app return self.defaults.get("ask_initial", False) migrations_import_path = "%s.migrations" % models_module.__package__ diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py index 150455b09ce5a..9356c8d8aa1ef 100644 --- a/django/db/migrations/writer.py +++ b/django/db/migrations/writer.py @@ -1,9 +1,11 @@ from __future__ import unicode_literals + import datetime -import types -import os from importlib import import_module -from django.apps.cache import cache +import os +import types + +from django.apps import app_cache from django.db import models from django.db.migrations.loader import MigrationLoader from django.utils.encoding import force_text @@ -67,9 +69,9 @@ def path(self): migrations_module = import_module(migrations_package_name) basedir = os.path.dirname(migrations_module.__file__) except ImportError: - app = cache.get_app(self.migration.app_label) - app_path = cache._get_app_path(app) - app_package_name = cache._get_app_package(app) + app = app_cache.get_app(self.migration.app_label) + app_path = app_cache._get_app_path(app) + app_package_name = app_cache._get_app_package(app) migrations_package_basename = migrations_package_name.split(".")[-1] # Alright, see if it's a direct submodule of the app diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index ad76347494d3b..265550fcce0b8 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -1,8 +1,5 @@ from functools import wraps -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 diff --git a/django/db/models/base.py b/django/db/models/base.py index f22506aa92dfc..94d72341425c2 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -5,7 +5,8 @@ from functools import update_wrapper from django.utils.six.moves import zip -from django.apps.cache import get_model, MODELS_MODULE_NAME +from django.apps import app_cache +from django.apps.cache import 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, @@ -1066,7 +1067,7 @@ def model_unpickle(model_id, attrs, factory): Used to unpickle Model subclasses with deferred fields. """ if isinstance(model_id, tuple): - model = get_model(*model_id) + model = app_cache.get_model(*model_id) else: # Backwards compat - the model was cached directly in earlier versions. model = model_id diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index d69a4346e2a38..830ff2efa2961 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -9,7 +9,7 @@ from base64 import b64decode, b64encode from itertools import tee -from django.apps.cache import get_model +from django.apps import app_cache from django.db import connection from django.db.models.query_utils import QueryWrapper from django.conf import settings @@ -51,7 +51,7 @@ class NOT_PROVIDED: def _load_field(app_label, model_name, field_name): - return get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0] + return app_cache.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0] class FieldDoesNotExist(Exception): diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 795de130b5010..1371a15fdf5f2 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -1,6 +1,6 @@ import warnings -from django.apps.cache import cache +from django.apps import app_cache warnings.warn( "The utilities in django.db.models.loading are deprecated " @@ -12,14 +12,14 @@ # 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 +get_apps = app_cache.get_apps +get_app_package = app_cache.get_app_package +get_app_path = app_cache.get_app_path +get_app_paths = app_cache.get_app_paths +get_app = app_cache.get_app +get_app_errors = app_cache.get_app_errors +get_models = app_cache.get_models +get_model = app_cache.get_model +register_models = app_cache.register_models +load_app = app_cache.load_app +app_cache_ready = app_cache.app_cache_ready diff --git a/django/db/models/options.py b/django/db/models/options.py index 5d99066343b8b..74090cb921d06 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -5,7 +5,7 @@ from bisect import bisect import warnings -from django.apps.cache import app_cache_ready, cache +from django.apps import app_cache from django.conf import settings from django.db.models.fields.related import ManyToManyRel from django.db.models.fields import AutoField, FieldDoesNotExist @@ -89,7 +89,7 @@ def __init__(self, meta, app_label=None): self.related_fkey_lookups = [] # A custom AppCache to use, if you're making a separate model set. - self.app_cache = cache + self.app_cache = app_cache def contribute_to_class(self, cls, name): from django.db import connection @@ -432,7 +432,7 @@ def init_name_map(self): if hasattr(f, 'related'): cache[f.name] = cache[f.attname] = ( f.related, None if f.model == self.model else f.model, True, False) - if app_cache_ready(): + if app_cache.app_cache_ready(): self._name_map = cache return cache @@ -558,7 +558,7 @@ def _fill_related_many_to_many_cache(self): and not isinstance(f.rel.to, six.string_types) and self == f.rel.to._meta): cache[f.related] = None - if app_cache_ready(): + if app_cache.app_cache_ready(): self._related_many_to_many_cache = cache return cache diff --git a/django/db/models/signals.py b/django/db/models/signals.py index 2543cf5f4af81..8c835e5f5f478 100644 --- a/django/db/models/signals.py +++ b/django/db/models/signals.py @@ -1,6 +1,6 @@ from collections import defaultdict -from django.apps.cache import get_model +from django.apps import app_cache from django.dispatch import Signal from django.utils import six @@ -41,7 +41,7 @@ def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): "Specified sender must either be a model or a " "model name of the 'app_label.ModelName' form." ) - sender = get_model(app_label, object_name, only_installed=False) + sender = app_cache.get_model(app_label, object_name, only_installed=False) if sender is None: reference = (app_label, object_name) self.unresolved_references[reference].append( diff --git a/django/db/utils.py b/django/db/utils.py index 43abaf9b5a52e..702b1b4ebc18a 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -282,6 +282,6 @@ def get_migratable_models(self, app, db, include_auto_created=False): """ Return app models allowed to be synchronized on provided db. """ - from .models import get_models - return [model for model in get_models(app, include_auto_created=include_auto_created) + from django.apps import app_cache + return [model for model in app_cache.get_models(app, include_auto_created=include_auto_created) if self.allow_migrate(db, model)] diff --git a/django/test/simple.py b/django/test/simple.py index 73deef917b04d..10f29061b3da4 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -9,7 +9,7 @@ import unittest as real_unittest import warnings -from django.db.models import get_app, get_apps +from django.apps import app_cache from django.test import _doctest as doctest from django.test import runner from django.test.utils import compare_xml, strip_quotes @@ -179,7 +179,7 @@ def build_test(label): # # First, look for TestCase instances with a name that matches # - app_module = get_app(parts[0]) + app_module = app_cache.get_app(parts[0]) test_module = get_tests(app_module) TestClass = getattr(app_module, parts[1], None) @@ -241,10 +241,10 @@ def build_suite(self, test_labels, extra_tests=None, **kwargs): if '.' in label: suite.addTest(build_test(label)) else: - app = get_app(label) + app = app_cache.get_app(label) suite.addTest(build_suite(app)) else: - for app in get_apps(): + for app in app_cache.get_apps(): suite.addTest(build_suite(app)) if extra_tests: diff --git a/django/test/testcases.py b/django/test/testcases.py index 52900ec47848e..1480889565a17 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -15,7 +15,7 @@ from unittest import skipIf # NOQA: Imported here for backward compatibility from unittest.util import safe_repr -from django.apps.cache import cache +from django.apps import app_cache from django.conf import settings from django.core import mail from django.core.exceptions import ValidationError, ImproperlyConfigured @@ -725,14 +725,14 @@ def _pre_setup(self): """ super(TransactionTestCase, self)._pre_setup() if self.available_apps is not None: - cache.set_available_apps(self.available_apps) + app_cache.set_available_apps(self.available_apps) for db_name in self._databases_names(include_mirrors=False): flush.Command.emit_post_migrate(verbosity=0, interactive=False, database=db_name) try: self._fixture_setup() except Exception: if self.available_apps is not None: - cache.unset_available_apps() + app_cache.unset_available_apps() raise def _databases_names(self, include_mirrors=True): @@ -786,7 +786,7 @@ def _post_teardown(self): for conn in connections.all(): conn.close() finally: - cache.unset_available_apps() + app_cache.unset_available_apps() def _fixture_teardown(self): # Allow TRUNCATE ... CASCADE and don't emit the post_migrate signal diff --git a/tests/app_cache/tests.py b/tests/app_cache/tests.py index 564f9f6f48a7d..cc3637367bb91 100644 --- a/tests/app_cache/tests.py +++ b/tests/app_cache/tests.py @@ -1,6 +1,7 @@ from __future__ import absolute_import -from django.apps.cache import cache, BaseAppCache +from django.apps import app_cache +from django.apps.cache import BaseAppCache from django.db import models from django.test import TestCase @@ -16,8 +17,8 @@ def test_models_py(self): """ Tests that the models in the models.py file were loaded correctly. """ - self.assertEqual(cache.get_model("app_cache", "TotallyNormal"), TotallyNormal) - self.assertEqual(cache.get_model("app_cache", "SoAlternative"), None) + self.assertEqual(app_cache.get_model("app_cache", "TotallyNormal"), TotallyNormal) + self.assertEqual(app_cache.get_model("app_cache", "SoAlternative"), None) self.assertEqual(new_app_cache.get_model("app_cache", "TotallyNormal"), None) self.assertEqual(new_app_cache.get_model("app_cache", "SoAlternative"), SoAlternative) @@ -26,7 +27,7 @@ def test_dynamic_load(self): """ Makes a new model at runtime and ensures it goes into the right place. """ - old_models = cache.get_models(cache.get_app("app_cache")) + old_models = app_cache.get_models(app_cache.get_app("app_cache")) # Construct a new model in a new app cache body = {} new_app_cache = BaseAppCache() @@ -41,6 +42,6 @@ def test_dynamic_load(self): # Make sure it appeared in the right place! self.assertEqual( old_models, - cache.get_models(cache.get_app("app_cache")), + app_cache.get_models(app_cache.get_app("app_cache")), ) self.assertEqual(new_app_cache.get_model("app_cache", "SouthPonies"), temp_model) diff --git a/tests/app_loading/tests.py b/tests/app_loading/tests.py index a8a5ba120fff0..b24522fab63ee 100644 --- a/tests/app_loading/tests.py +++ b/tests/app_loading/tests.py @@ -5,7 +5,8 @@ import sys from unittest import TestCase -from django.apps.cache import cache, load_app, get_model, get_models, AppCache +from django.apps import app_cache +from django.apps.cache import AppCache from django.test.utils import override_settings from django.utils._os import upath @@ -19,48 +20,48 @@ def setUp(self): # This test adds dummy applications to the app cache. These # need to be removed in order to prevent bad interactions # with the flush operation in other tests. - self.old_app_models = copy.deepcopy(cache.app_models) + self.old_app_models = copy.deepcopy(app_cache.app_models) def tearDown(self): sys.path = self.old_path - cache.app_models = self.old_app_models + app_cache.app_models = self.old_app_models def test_egg1(self): """Models module can be loaded from an app in an egg""" egg_name = '%s/modelapp.egg' % self.egg_dir sys.path.append(egg_name) - models = load_app('app_with_models') + models = app_cache.load_app('app_with_models') self.assertFalse(models is None) def test_egg2(self): """Loading an app from an egg that has no models returns no models (and no error)""" egg_name = '%s/nomodelapp.egg' % self.egg_dir sys.path.append(egg_name) - models = load_app('app_no_models') + models = app_cache.load_app('app_no_models') self.assertTrue(models is None) def test_egg3(self): """Models module can be loaded from an app located under an egg's top-level package""" egg_name = '%s/omelet.egg' % self.egg_dir sys.path.append(egg_name) - models = load_app('omelet.app_with_models') + models = app_cache.load_app('omelet.app_with_models') self.assertFalse(models is None) def test_egg4(self): """Loading an app with no models from under the top-level egg package generates no error""" egg_name = '%s/omelet.egg' % self.egg_dir sys.path.append(egg_name) - models = load_app('omelet.app_no_models') + models = app_cache.load_app('omelet.app_no_models') self.assertTrue(models is None) def test_egg5(self): """Loading an app from an egg that has an import error in its models module raises that error""" egg_name = '%s/brokenapp.egg' % self.egg_dir sys.path.append(egg_name) - self.assertRaises(ImportError, load_app, 'broken_app') + self.assertRaises(ImportError, app_cache.load_app, 'broken_app') raised = None try: - load_app('broken_app') + app_cache.load_app('broken_app') except ImportError as e: raised = e @@ -81,8 +82,8 @@ def test_missing_app(self): a.loaded = False try: with override_settings(INSTALLED_APPS=('notexists',)): - self.assertRaises(ImportError, get_model, 'notexists', 'nomodel', seed_cache=True) - self.assertRaises(ImportError, get_model, 'notexists', 'nomodel', seed_cache=True) + self.assertRaises(ImportError, app_cache.get_model, 'notexists', 'nomodel', seed_cache=True) + self.assertRaises(ImportError, app_cache.get_model, 'notexists', 'nomodel', seed_cache=True) finally: a.loaded = True @@ -94,26 +95,26 @@ def setUp(self): def test_get_model_only_returns_installed_models(self): self.assertEqual( - get_model("not_installed", "NotInstalledModel"), None) + app_cache.get_model("not_installed", "NotInstalledModel"), None) def test_get_model_with_not_installed(self): self.assertEqual( - get_model( + app_cache.get_model( "not_installed", "NotInstalledModel", only_installed=False), self.not_installed_module.NotInstalledModel) def test_get_models_only_returns_installed_models(self): self.assertFalse( "NotInstalledModel" in - [m.__name__ for m in get_models()]) + [m.__name__ for m in app_cache.get_models()]) def test_get_models_with_app_label_only_returns_installed_models(self): - self.assertEqual(get_models(self.not_installed_module), []) + self.assertEqual(app_cache.get_models(self.not_installed_module), []) def test_get_models_with_not_installed(self): self.assertTrue( "NotInstalledModel" in [ - m.__name__ for m in get_models(only_installed=False)]) + m.__name__ for m in app_cache.get_models(only_installed=False)]) class NotInstalledModelsTest(TestCase): diff --git a/tests/commands_sql/tests.py b/tests/commands_sql/tests.py index 3a0b5527f4343..82e55d9861627 100644 --- a/tests/commands_sql/tests.py +++ b/tests/commands_sql/tests.py @@ -1,9 +1,10 @@ from __future__ import unicode_literals +from django.apps import app_cache from django.core.management.color import no_style from django.core.management.sql import (sql_create, sql_delete, sql_indexes, sql_destroy_indexes, sql_all) -from django.db import connections, DEFAULT_DB_ALIAS, models, router +from django.db import connections, DEFAULT_DB_ALIAS, router from django.test import TestCase from django.utils import six @@ -16,7 +17,7 @@ def count_ddl(self, output, cmd): return len([o for o in output if o.startswith(cmd)]) def test_sql_create(self): - app = models.get_app('commands_sql') + app = app_cache.get_app('commands_sql') output = sql_create(app, no_style(), connections[DEFAULT_DB_ALIAS]) create_tables = [o for o in output if o.startswith('CREATE TABLE')] self.assertEqual(len(create_tables), 3) @@ -25,7 +26,7 @@ def test_sql_create(self): six.assertRegex(self, sql, r'^create table .commands_sql_book.*') def test_sql_delete(self): - app = models.get_app('commands_sql') + app = app_cache.get_app('commands_sql') output = sql_delete(app, no_style(), connections[DEFAULT_DB_ALIAS]) drop_tables = [o for o in output if o.startswith('DROP TABLE')] self.assertEqual(len(drop_tables), 3) @@ -34,19 +35,19 @@ def test_sql_delete(self): six.assertRegex(self, sql, r'^drop table .commands_sql_comment.*') def test_sql_indexes(self): - app = models.get_app('commands_sql') + app = app_cache.get_app('commands_sql') output = sql_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS]) # PostgreSQL creates one additional index for CharField self.assertIn(self.count_ddl(output, 'CREATE INDEX'), [3, 4]) def test_sql_destroy_indexes(self): - app = models.get_app('commands_sql') + app = app_cache.get_app('commands_sql') output = sql_destroy_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS]) # PostgreSQL creates one additional index for CharField self.assertIn(self.count_ddl(output, 'DROP INDEX'), [3, 4]) def test_sql_all(self): - app = models.get_app('commands_sql') + app = app_cache.get_app('commands_sql') output = sql_all(app, no_style(), connections[DEFAULT_DB_ALIAS]) self.assertEqual(self.count_ddl(output, 'CREATE TABLE'), 3) @@ -68,7 +69,7 @@ def tearDown(self): router.routers = self._old_routers def test_router_honored(self): - app = models.get_app('commands_sql') + app = app_cache.get_app('commands_sql') for sql_command in (sql_all, sql_create, sql_delete, sql_indexes, sql_destroy_indexes): output = sql_command(app, no_style(), connections[DEFAULT_DB_ALIAS]) self.assertEqual(len(output), 0, diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py index 0107e8167c229..794ea6e18e4ad 100644 --- a/tests/defer_regress/tests.py +++ b/tests/defer_regress/tests.py @@ -2,7 +2,7 @@ from operator import attrgetter -from django.apps.cache import cache +from django.apps import app_cache from django.contrib.contenttypes.models import ContentType from django.contrib.sessions.backends.db import SessionStore from django.db.models import Count @@ -103,7 +103,7 @@ def test_ticket_11936(self): klasses = set( map( attrgetter("__name__"), - cache.get_models(cache.get_app("defer_regress")) + app_cache.get_models(app_cache.get_app("defer_regress")) ) ) self.assertIn("Child", klasses) @@ -111,13 +111,13 @@ def test_ticket_11936(self): self.assertNotIn("Child_Deferred_value", klasses) self.assertNotIn("Item_Deferred_name", klasses) self.assertFalse(any( - k._deferred for k in cache.get_models(cache.get_app("defer_regress")))) + k._deferred for k in app_cache.get_models(app_cache.get_app("defer_regress")))) klasses_with_deferred = set( map( attrgetter("__name__"), - cache.get_models( - cache.get_app("defer_regress"), include_deferred=True + app_cache.get_models( + app_cache.get_app("defer_regress"), include_deferred=True ), ) ) @@ -126,8 +126,8 @@ def test_ticket_11936(self): self.assertIn("Child_Deferred_value", klasses_with_deferred) self.assertIn("Item_Deferred_name", klasses_with_deferred) self.assertTrue(any( - k._deferred for k in cache.get_models( - cache.get_app("defer_regress"), include_deferred=True)) + k._deferred for k in app_cache.get_models( + app_cache.get_app("defer_regress"), include_deferred=True)) ) @override_settings(SESSION_SERIALIZER='django.contrib.sessions.serializers.PickleSerializer') diff --git a/tests/empty/tests.py b/tests/empty/tests.py index 2a9f568aeaf9b..b8476fc73dd36 100644 --- a/tests/empty/tests.py +++ b/tests/empty/tests.py @@ -1,4 +1,4 @@ -from django.apps.cache import get_app +from django.apps import app_cache from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from django.test.utils import override_settings @@ -33,4 +33,4 @@ class NoModelTests(TestCase): def test_no_models(self): with six.assertRaisesRegex(self, ImproperlyConfigured, 'App with label no_models is missing a models.py module.'): - get_app('no_models') + app_cache.get_app('no_models') diff --git a/tests/invalid_models/tests.py b/tests/invalid_models/tests.py index 46b3dc4c2ee5d..abb7d9b33d954 100644 --- a/tests/invalid_models/tests.py +++ b/tests/invalid_models/tests.py @@ -2,7 +2,7 @@ import sys import unittest -from django.apps.cache import cache, load_app +from django.apps import app_cache from django.core.management.validation import get_validation_errors from django.test.utils import override_settings from django.utils.six import StringIO @@ -22,11 +22,11 @@ def setUp(self): # This test adds dummy applications to the app cache. These # need to be removed in order to prevent bad interactions # with the flush operation in other tests. - self.old_app_models = copy.deepcopy(cache.app_models) + self.old_app_models = copy.deepcopy(app_cache.app_models) def tearDown(self): - cache.app_models = self.old_app_models - cache._get_models_cache = {} + app_cache.app_models = self.old_app_models + app_cache._get_models_cache = {} sys.stdout = self.old_stdout # Technically, this isn't an override -- TEST_SWAPPED_MODEL must be @@ -40,7 +40,7 @@ def tearDown(self): ) def test_invalid_models(self): try: - module = load_app("invalid_models.invalid_models") + module = app_cache.load_app("invalid_models.invalid_models") except Exception: self.fail('Unable to load invalid model module') diff --git a/tests/managers_regress/tests.py b/tests/managers_regress/tests.py index 25717f7431bd8..de9f72c3330bc 100644 --- a/tests/managers_regress/tests.py +++ b/tests/managers_regress/tests.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import copy -from django.apps.cache import cache +from django.apps import app_cache from django.db import models from django.template import Context, Template from django.test import TestCase @@ -114,7 +114,7 @@ def test_swappable_manager(self): # This test adds dummy models to the app cache. These # need to be removed in order to prevent bad interactions # with the flush operation in other tests. - old_app_models = copy.deepcopy(cache.app_models) + old_app_models = copy.deepcopy(app_cache.app_models) class SwappableModel(models.Model): class Meta: @@ -129,7 +129,7 @@ class Meta: self.assertEqual(str(e), "Manager isn't available; SwappableModel has been swapped for 'managers_regress.Parent'") finally: - cache.app_models = old_app_models + app_cache.app_models = old_app_models @override_settings(TEST_SWAPPABLE_MODEL='managers_regress.Parent') def test_custom_swappable_manager(self): @@ -137,7 +137,7 @@ def test_custom_swappable_manager(self): # This test adds dummy models to the app cache. These # need to be removed in order to prevent bad interactions # with the flush operation in other tests. - old_app_models = copy.deepcopy(cache.app_models) + old_app_models = copy.deepcopy(app_cache.app_models) class SwappableModel(models.Model): @@ -156,7 +156,7 @@ class Meta: self.assertEqual(str(e), "Manager isn't available; SwappableModel has been swapped for 'managers_regress.Parent'") finally: - cache.app_models = old_app_models + app_cache.app_models = old_app_models @override_settings(TEST_SWAPPABLE_MODEL='managers_regress.Parent') def test_explicit_swappable_manager(self): @@ -164,7 +164,7 @@ def test_explicit_swappable_manager(self): # This test adds dummy models to the app cache. These # need to be removed in order to prevent bad interactions # with the flush operation in other tests. - old_app_models = copy.deepcopy(cache.app_models) + old_app_models = copy.deepcopy(app_cache.app_models) class SwappableModel(models.Model): @@ -183,7 +183,7 @@ class Meta: self.assertEqual(str(e), "Manager isn't available; SwappableModel has been swapped for 'managers_regress.Parent'") finally: - cache.app_models = old_app_models + app_cache.app_models = old_app_models def test_regress_3871(self): related = RelatedModel.objects.create() diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 9d84d710bb2e9..08b7371301e6c 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -6,7 +6,7 @@ import os import shutil -from django.apps.cache import cache +from django.apps import app_cache from django.core.management import call_command, CommandError from django.test.utils import override_settings from django.utils import six @@ -132,11 +132,11 @@ def setUp(self): self.test_dir = os.path.abspath(os.path.dirname(upath(__file__))) self.migration_dir = os.path.join(self.test_dir, 'migrations_%d' % self.creation_counter) self.migration_pkg = "migrations.migrations_%d" % self.creation_counter - self._old_app_models = copy.deepcopy(cache.app_models) + self._old_app_models = copy.deepcopy(app_cache.app_models) def tearDown(self): - cache.app_models = self._old_app_models - cache._get_models_cache = {} + app_cache.app_models = self._old_app_models + app_cache._get_models_cache = {} os.chdir(self.test_dir) try: @@ -152,7 +152,7 @@ def _rmrf(self, dname): def test_files_content(self): self.assertTableNotExists("migrations_unicodemodel") - cache.register_models('migrations', UnicodeModel) + app_cache.register_models('migrations', UnicodeModel) with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}): call_command("makemigrations", "migrations", verbosity=0) @@ -188,7 +188,7 @@ def test_files_content(self): def test_failing_migration(self): #21280 - If a migration fails to serialize, it shouldn't generate an empty file. - cache.register_models('migrations', UnserializableModel) + app_cache.register_models('migrations', UnserializableModel) with six.assertRaisesRegex(self, ValueError, r'Cannot serialize'): with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}): diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 20aff59de2313..d844d36c39feb 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -1,4 +1,5 @@ import unittest +from django.apps import app_cache from django.db import connection, models, migrations, router from django.db.models.fields import NOT_PROVIDED from django.db.transaction import atomic @@ -203,8 +204,8 @@ def test_add_field_m2m(self): self.assertColumnNotExists("test_adflmm_pony", "stables") # Make sure the M2M field actually works with atomic(): - app_cache = new_state.render() - Pony = app_cache.get_model("test_adflmm", "Pony") + new_app_cache = new_state.render() + Pony = new_app_cache.get_model("test_adflmm", "Pony") p = Pony.objects.create(pink=False, weight=4.55) p.stables.create() self.assertEqual(p.stables.count(), 1) diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index cc32a7d751241..960af90eaa000 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -5,7 +5,7 @@ import datetime import os -from django.apps.cache import cache +from django.apps import app_cache from django.core.validators import RegexValidator, EmailValidator from django.db import models, migrations from django.db.migrations.writer import MigrationWriter @@ -124,7 +124,7 @@ def test_migration_path(self): with override_settings(INSTALLED_APPS=test_apps): for app in test_apps: - cache.load_app(app) + app_cache.load_app(app) migration = migrations.Migration('0001_initial', app.split('.')[-1]) expected_path = os.path.join(base_dir, *(app.split('.') + ['migrations', '0001_initial.py'])) writer = MigrationWriter(migration) diff --git a/tests/proxy_model_inheritance/tests.py b/tests/proxy_model_inheritance/tests.py index 0fad8c594fe6e..23058d122b379 100644 --- a/tests/proxy_model_inheritance/tests.py +++ b/tests/proxy_model_inheritance/tests.py @@ -3,7 +3,7 @@ import os import sys -from django.apps.cache import cache, load_app +from django.apps import app_cache from django.conf import settings from django.core.management import call_command from django.test import TestCase, TransactionTestCase @@ -28,21 +28,21 @@ def setUp(self): self.old_sys_path = sys.path[:] sys.path.append(os.path.dirname(os.path.abspath(upath(__file__)))) for app in settings.INSTALLED_APPS: - load_app(app) + app_cache.load_app(app) def tearDown(self): sys.path = self.old_sys_path - del cache.app_labels['app1'] - del cache.app_labels['app2'] - del cache.app_models['app1'] - del cache.app_models['app2'] + del app_cache.app_labels['app1'] + del app_cache.app_labels['app2'] + del app_cache.app_models['app1'] + del app_cache.app_models['app2'] def test_table_exists(self): try: - cache.set_available_apps(settings.INSTALLED_APPS) + app_cache.set_available_apps(settings.INSTALLED_APPS) call_command('migrate', verbosity=0) finally: - cache.unset_available_apps() + app_cache.unset_available_apps() from .app1.models import ProxyModel from .app2.models import NiceModel self.assertEqual(NiceModel.objects.all().count(), 0) diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py index 5b9433bbf7930..cdd979a399ddb 100644 --- a/tests/proxy_models/tests.py +++ b/tests/proxy_models/tests.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import copy -from django.apps.cache import cache +from django.apps import app_cache from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.core import management @@ -159,7 +159,7 @@ def test_swappable(self): # This test adds dummy applications to the app cache. These # need to be removed in order to prevent bad interactions # with the flush operation in other tests. - old_app_models = copy.deepcopy(cache.app_models) + old_app_models = copy.deepcopy(app_cache.app_models) class SwappableModel(models.Model): @@ -176,7 +176,7 @@ class ProxyModel(SwappableModel): class Meta: proxy = True finally: - cache.app_models = old_app_models + app_cache.app_models = old_app_models def test_myperson_manager(self): Person.objects.create(name="fred") diff --git a/tests/runtests.py b/tests/runtests.py index 8bcb06e522e41..9ef81f8877a6d 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -80,13 +80,13 @@ def get_test_modules(): def get_installed(): - from django.apps.cache import get_apps - return [app.__name__.rsplit('.', 1)[0] for app in get_apps()] + from django.apps import app_cache + return [app.__name__.rsplit('.', 1)[0] for app in app_cache.get_apps()] def setup(verbosity, test_labels): import django - from django.apps.cache import get_apps, load_app + from django.apps import app_cache from django.conf import settings from django.test import TransactionTestCase, TestCase @@ -128,7 +128,7 @@ def no_available_apps(self): # Load all the ALWAYS_INSTALLED_APPS. with warnings.catch_warnings(): warnings.filterwarnings('ignore', 'django.contrib.comments is deprecated and will be removed before Django 1.8.', DeprecationWarning) - get_apps() + app_cache.get_apps() # Load all the test model apps. test_modules = get_test_modules() @@ -164,7 +164,7 @@ def no_available_apps(self): if module_found_in_labels: if verbosity >= 2: print("Importing application %s" % module_name) - mod = load_app(module_label) + mod = app_cache.load_app(module_label) if mod: if module_label not in settings.INSTALLED_APPS: settings.INSTALLED_APPS.append(module_label) diff --git a/tests/swappable_models/tests.py b/tests/swappable_models/tests.py index ae423667453d4..0beb95af3e1d5 100644 --- a/tests/swappable_models/tests.py +++ b/tests/swappable_models/tests.py @@ -2,7 +2,7 @@ from django.utils.six import StringIO -from django.apps.cache import cache +from django.apps import app_cache from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.core import management @@ -23,12 +23,12 @@ class SwappableModelTests(TestCase): def setUp(self): # This test modifies the installed apps, so we need to make sure # we're not dealing with a cached app list. - cache._get_models_cache.clear() + app_cache._get_models_cache.clear() def tearDown(self): # By fiddling with swappable models, we alter the installed models # cache, so flush it to make sure there are no side effects. - cache._get_models_cache.clear() + app_cache._get_models_cache.clear() @override_settings(TEST_ARTICLE_MODEL='swappable_models.AlternateArticle') def test_generated_data(self): diff --git a/tests/tablespaces/tests.py b/tests/tablespaces/tests.py index 04240bcaa1d07..9bcfed05ed69a 100644 --- a/tests/tablespaces/tests.py +++ b/tests/tablespaces/tests.py @@ -2,7 +2,7 @@ import copy -from django.apps.cache import cache +from django.apps import app_cache from django.conf import settings from django.db import connection from django.core.management.color import no_style @@ -28,7 +28,7 @@ class TablespacesTests(TestCase): def setUp(self): # The unmanaged models need to be removed after the test in order to # prevent bad interactions with the flush operation in other tests. - self.old_app_models = copy.deepcopy(cache.app_models) + self.old_app_models = copy.deepcopy(app_cache.app_models) for model in Article, Authors, Reviewers, Scientist: model._meta.managed = True @@ -37,8 +37,8 @@ def tearDown(self): for model in Article, Authors, Reviewers, Scientist: model._meta.managed = False - cache.app_models = self.old_app_models - cache._get_models_cache = {} + app_cache.app_models = self.old_app_models + app_cache._get_models_cache = {} def assertNumContains(self, haystack, needle, count): real_count = haystack.count(needle) diff --git a/tests/test_suite_override/tests.py b/tests/test_suite_override/tests.py index e69dab12bf421..ed336076e76a7 100644 --- a/tests/test_suite_override/tests.py +++ b/tests/test_suite_override/tests.py @@ -1,6 +1,6 @@ import unittest -from django.db.models import get_app +from django.apps import app_cache from django.test.utils import IgnoreAllDeprecationWarningsMixin @@ -20,7 +20,7 @@ def test_suite_override(self): """ from django.test.simple import build_suite - app = get_app("test_suite_override") + app = app_cache.get_app("test_suite_override") suite = build_suite(app) self.assertEqual(suite.countTestCases(), 1) From c2c50cc622994f5e511fba3bb0f7f701fb77ce5b Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 12 Dec 2013 11:28:04 +0100 Subject: [PATCH 05/22] Stored AppConfig objects instead of models modules in the app cache. This is a step towards allowing applications without a models module. --- django/apps/base.py | 11 ++++++++++ django/apps/cache.py | 29 ++++++++++++++------------ tests/proxy_model_inheritance/tests.py | 4 ++-- 3 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 django/apps/base.py diff --git a/django/apps/base.py b/django/apps/base.py new file mode 100644 index 0000000000000..37c16fb22fbcc --- /dev/null +++ b/django/apps/base.py @@ -0,0 +1,11 @@ +class AppConfig(object): + """ + Class representing a Django application and its configuration. + """ + + def __init__(self, label, models_module): + self.label = label + self.models_module = models_module + + def __repr__(self): + return '' % self.label diff --git a/django/apps/cache.py b/django/apps/cache.py index b5fd1c37dac58..3ef63eff4a3ad 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -12,6 +12,8 @@ from django.utils._os import upath from django.utils import six +from .base import AppConfig + MODELS_MODULE_NAME = 'models' @@ -26,8 +28,8 @@ def _initialize(): [shared] state of the app cache. """ return dict( - # Mapping of installed app_labels to model modules for that app. - app_labels=OrderedDict(), + # Mapping of labels to AppConfig instances for installed apps. + app_configs=OrderedDict(), # Mapping of app_labels to a dictionary of model names to model code. # May contain apps that are not installed. @@ -116,7 +118,7 @@ def load_app(self, app_name, can_postpone=False): self.handled.add(app_name) self.nesting_level += 1 try: - models = import_module('%s.%s' % (app_name, MODELS_MODULE_NAME)) + models_module = 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 @@ -138,10 +140,11 @@ def load_app(self, app_name, can_postpone=False): raise self.nesting_level -= 1 - label = self._label_for(models) - if label not in self.app_labels: - self.app_labels[label] = models - return models + label = self._label_for(models_module) + if label not in self.app_configs: + self.app_configs[label] = AppConfig( + label=label, models_module=models_module) + return models_module def app_cache_ready(self): """ @@ -158,13 +161,13 @@ def get_apps(self): """ self._populate() - # app_labels is an OrderedDict, which ensures that the returned list + # app_configs is an OrderedDict, which ensures that 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 = self.app_labels.items() + apps = self.app_configs.items() if self.available_apps is not None: apps = [app for app in apps if app[0] in self.available_apps] - return [app[1] for app in apps] + return [app[1].models_module for app in apps] def _get_app_package(self, app): return '.'.join(app.__name__.split('.')[:-1]) @@ -263,14 +266,14 @@ def get_models(self, app_mod=None, self._populate() if app_mod: app_label = self._label_for(app_mod) - if app_label in self.app_labels: + if app_label in self.app_configs: app_list = [self.app_models.get(app_label, OrderedDict())] else: app_list = [] else: if only_installed: app_list = [self.app_models.get(app_label, OrderedDict()) - for app_label in six.iterkeys(self.app_labels)] + for app_label in self.app_configs] else: app_list = six.itervalues(self.app_models) model_list = [] @@ -301,7 +304,7 @@ def get_model(self, app_label, model_name, only_installed = False if seed_cache: self._populate() - if only_installed and app_label not in self.app_labels: + if only_installed and app_label not in self.app_configs: return None if (self.available_apps is not None and only_installed and app_label not in self.available_apps): diff --git a/tests/proxy_model_inheritance/tests.py b/tests/proxy_model_inheritance/tests.py index 23058d122b379..f3cdba20a9318 100644 --- a/tests/proxy_model_inheritance/tests.py +++ b/tests/proxy_model_inheritance/tests.py @@ -32,8 +32,8 @@ def setUp(self): def tearDown(self): sys.path = self.old_sys_path - del app_cache.app_labels['app1'] - del app_cache.app_labels['app2'] + del app_cache.app_configs['app1'] + del app_cache.app_configs['app2'] del app_cache.app_models['app1'] del app_cache.app_models['app2'] From 54e57e8c75449c650f7c82242f2720b8ed337135 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 12 Dec 2013 11:36:40 +0100 Subject: [PATCH 06/22] Removed unused attribute app_errors of the app cache. get_app_errors() always returned an empty dictionary; this behavior is preserved in django.db.models.loading until that module is deprecated. --- django/apps/cache.py | 8 -------- django/core/management/validation.py | 3 --- django/db/models/loading.py | 12 +++++++++++- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index 3ef63eff4a3ad..34909deeace61 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -35,9 +35,6 @@ def _initialize(): # May contain apps that are not installed. app_models=OrderedDict(), - # Mapping of app_labels to errors raised when trying to import the app. - app_errors={}, - # Pending lookups for lazy relations pending_lookups={}, @@ -223,11 +220,6 @@ def get_app(self, app_label, emptyOK=False): 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): diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 89aa5ad068c1f..2b27a7edce3cb 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -32,9 +32,6 @@ def get_validation_errors(outfile, app=None): e = ModelErrorCollection(outfile) - for (app_name, error) in app_cache.get_app_errors().items(): - e.add(app_name, error) - for cls in app_cache.get_models(app, include_swapped=True): opts = cls._meta diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 1371a15fdf5f2..f814f949e4f49 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -17,9 +17,19 @@ get_app_path = app_cache.get_app_path get_app_paths = app_cache.get_app_paths get_app = app_cache.get_app -get_app_errors = app_cache.get_app_errors get_models = app_cache.get_models get_model = app_cache.get_model register_models = app_cache.register_models load_app = app_cache.load_app app_cache_ready = app_cache.app_cache_ready + + +# This method doesn't return anything interesting in Django 1.6. Maintain it +# just for backwards compatibility until this module is deprecated. +def get_app_errors(): + try: + return app_cache.app_errors + except AttributeError: + app_cache._populate() + app_cache.app_errors = {} + return app_cache.app_errors From b14151d2f55faa072878703c63bc2ab5e50530c0 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 12 Dec 2013 21:34:39 +0100 Subject: [PATCH 07/22] Moved list of models inside AppConfig instances. This commit is a refactoring with no change of functionality, according to the following invariants: - An app_label that was in app_configs and app_models stays in app_config and has its 'installed' attribute set to True. - An app_label that was in app_models but not in app_configs is added to app_configs and has its 'installed' attribute set to True. As a consequence, all the code that iterated on app_configs is modified to check for the 'installed' attribute. Code that iterated on app_models is rewritten in terms of app_configs. Many tests that stored and restored the state of the app cache were updated. In the long term, we should reconsider the usefulness of allowing importing models from non-installed applications. This doesn't sound particularly useful, can be a trap in some circumstances, and causes significant complexity in sensitive areas of Django. --- django/apps/base.py | 16 +++++++- django/apps/cache.py | 56 +++++++++++++++----------- tests/app_loading/tests.py | 12 +++--- tests/invalid_models/tests.py | 10 ++--- tests/managers_regress/tests.py | 37 +++++++++-------- tests/migrations/test_commands.py | 5 +-- tests/migrations/test_operations.py | 1 - tests/proxy_model_inheritance/tests.py | 2 - tests/proxy_models/tests.py | 13 +++--- tests/tablespaces/tests.py | 6 +-- 10 files changed, 86 insertions(+), 72 deletions(-) diff --git a/django/apps/base.py b/django/apps/base.py index 37c16fb22fbcc..588709be9f160 100644 --- a/django/apps/base.py +++ b/django/apps/base.py @@ -1,11 +1,25 @@ +from collections import OrderedDict + + class AppConfig(object): """ Class representing a Django application and its configuration. """ - def __init__(self, label, models_module): + def __init__(self, label, models_module=None, installed=True): + # Last component of the Python path to the application eg. 'admin'. self.label = label + + # Module containing models eg. . self.models_module = models_module + # Mapping of lower case model names to model classes. + self.models = OrderedDict() + + # Whether the app is in INSTALLED_APPS or was automatically created + # when one of its models was imported. + self.installed = installed + def __repr__(self): return '' % self.label diff --git a/django/apps/cache.py b/django/apps/cache.py index 34909deeace61..b3991e5e34dd4 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -31,10 +31,6 @@ def _initialize(): # Mapping of labels to AppConfig instances for installed apps. app_configs=OrderedDict(), - # Mapping of app_labels to a dictionary of model names to model code. - # May contain apps that are not installed. - app_models=OrderedDict(), - # Pending lookups for lazy relations pending_lookups={}, @@ -138,9 +134,15 @@ def load_app(self, app_name, can_postpone=False): self.nesting_level -= 1 label = self._label_for(models_module) - if label not in self.app_configs: + try: + app_config = self.app_configs[label] + except KeyError: self.app_configs[label] = AppConfig( label=label, models_module=models_module) + else: + if not app_config.installed: + app_config.models_module = models_module + app_config.installed = True return models_module def app_cache_ready(self): @@ -161,7 +163,7 @@ def get_apps(self): # app_configs is an OrderedDict, which ensures that 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 = self.app_configs.items() + apps = [app for app in self.app_configs.items() if app[1].installed] if self.available_apps is not None: apps = [app for app in apps if app[0] in self.available_apps] return [app[1].models_module for app in apps] @@ -258,20 +260,20 @@ def get_models(self, app_mod=None, self._populate() if app_mod: app_label = self._label_for(app_mod) - if app_label in self.app_configs: - app_list = [self.app_models.get(app_label, OrderedDict())] - else: + try: + app_config = self.app_configs[app_label] + except KeyError: app_list = [] + else: + app_list = [app_config] if app_config.installed else [] else: + app_list = six.itervalues(self.app_configs) if only_installed: - app_list = [self.app_models.get(app_label, OrderedDict()) - for app_label in self.app_configs] - else: - app_list = six.itervalues(self.app_models) + app_list = (app for app in app_list if app.installed) model_list = [] for app in app_list: model_list.extend( - model for model in app.values() + model for model in app.models.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)) @@ -296,13 +298,15 @@ def get_model(self, app_label, model_name, only_installed = False if seed_cache: self._populate() - if only_installed and app_label not in self.app_configs: - 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) + if only_installed: + app_config = self.app_configs.get(app_label) + if app_config is not None and not app_config.installed: + return None + 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) try: - return self.app_models[app_label][model_name.lower()] + return self.app_configs[app_label].models[model_name.lower()] except KeyError: return None @@ -310,11 +314,17 @@ def register_models(self, app_label, *models): """ Register a set of models as belonging to an app. """ + if models: + try: + app_config = self.app_configs[app_label] + except KeyError: + app_config = AppConfig( + label=app_label, installed=False) + self.app_configs[app_label] = app_config for model in models: - # Store as 'name: model' pair in a dictionary - # in the app_models dictionary + # Add the model to the app_config's models dictionary. model_name = model._meta.model_name - model_dict = self.app_models.setdefault(app_label, OrderedDict()) + model_dict = app_config.models 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 diff --git a/tests/app_loading/tests.py b/tests/app_loading/tests.py index b24522fab63ee..690956404035f 100644 --- a/tests/app_loading/tests.py +++ b/tests/app_loading/tests.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals -import copy import os import sys from unittest import TestCase @@ -17,14 +16,15 @@ def setUp(self): self.old_path = sys.path[:] self.egg_dir = '%s/eggs' % os.path.dirname(upath(__file__)) - # This test adds dummy applications to the app cache. These - # need to be removed in order to prevent bad interactions - # with the flush operation in other tests. - self.old_app_models = copy.deepcopy(app_cache.app_models) + # The models need to be removed after the test in order to prevent bad + # interactions with the flush operation in other tests. + self._old_models = app_cache.app_configs['app_loading'].models.copy() def tearDown(self): + app_cache.app_configs['app_loading'].models = self._old_models + app_cache._get_models_cache = {} + sys.path = self.old_path - app_cache.app_models = self.old_app_models def test_egg1(self): """Models module can be loaded from an app in an egg""" diff --git a/tests/invalid_models/tests.py b/tests/invalid_models/tests.py index abb7d9b33d954..860d5e23a6a67 100644 --- a/tests/invalid_models/tests.py +++ b/tests/invalid_models/tests.py @@ -1,4 +1,3 @@ -import copy import sys import unittest @@ -19,13 +18,12 @@ def setUp(self): self.stdout = StringIO() sys.stdout = self.stdout - # This test adds dummy applications to the app cache. These - # need to be removed in order to prevent bad interactions - # with the flush operation in other tests. - self.old_app_models = copy.deepcopy(app_cache.app_models) + # The models need to be removed after the test in order to prevent bad + # interactions with the flush operation in other tests. + self._old_models = app_cache.app_configs['invalid_models'].models.copy() def tearDown(self): - app_cache.app_models = self.old_app_models + app_cache.app_configs['invalid_models'].models = self._old_models app_cache._get_models_cache = {} sys.stdout = self.old_stdout diff --git a/tests/managers_regress/tests.py b/tests/managers_regress/tests.py index de9f72c3330bc..0c939bfd2ecdf 100644 --- a/tests/managers_regress/tests.py +++ b/tests/managers_regress/tests.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -import copy from django.apps import app_cache from django.db import models @@ -110,12 +109,11 @@ def test_explicit_abstract_manager(self): @override_settings(TEST_SWAPPABLE_MODEL='managers_regress.Parent') def test_swappable_manager(self): - try: - # This test adds dummy models to the app cache. These - # need to be removed in order to prevent bad interactions - # with the flush operation in other tests. - old_app_models = copy.deepcopy(app_cache.app_models) + # The models need to be removed after the test in order to prevent bad + # interactions with the flush operation in other tests. + _old_models = app_cache.app_configs['managers_regress'].models.copy() + try: class SwappableModel(models.Model): class Meta: swappable = 'TEST_SWAPPABLE_MODEL' @@ -129,16 +127,16 @@ class Meta: self.assertEqual(str(e), "Manager isn't available; SwappableModel has been swapped for 'managers_regress.Parent'") finally: - app_cache.app_models = old_app_models + app_cache.app_configs['managers_regress'].models = _old_models + app_cache._get_models_cache = {} @override_settings(TEST_SWAPPABLE_MODEL='managers_regress.Parent') def test_custom_swappable_manager(self): - try: - # This test adds dummy models to the app cache. These - # need to be removed in order to prevent bad interactions - # with the flush operation in other tests. - old_app_models = copy.deepcopy(app_cache.app_models) + # The models need to be removed after the test in order to prevent bad + # interactions with the flush operation in other tests. + _old_models = app_cache.app_configs['managers_regress'].models.copy() + try: class SwappableModel(models.Model): stuff = models.Manager() @@ -156,16 +154,16 @@ class Meta: self.assertEqual(str(e), "Manager isn't available; SwappableModel has been swapped for 'managers_regress.Parent'") finally: - app_cache.app_models = old_app_models + app_cache.app_configs['managers_regress'].models = _old_models + app_cache._get_models_cache = {} @override_settings(TEST_SWAPPABLE_MODEL='managers_regress.Parent') def test_explicit_swappable_manager(self): - try: - # This test adds dummy models to the app cache. These - # need to be removed in order to prevent bad interactions - # with the flush operation in other tests. - old_app_models = copy.deepcopy(app_cache.app_models) + # The models need to be removed after the test in order to prevent bad + # interactions with the flush operation in other tests. + _old_models = app_cache.app_configs['managers_regress'].models.copy() + try: class SwappableModel(models.Model): objects = models.Manager() @@ -183,7 +181,8 @@ class Meta: self.assertEqual(str(e), "Manager isn't available; SwappableModel has been swapped for 'managers_regress.Parent'") finally: - app_cache.app_models = old_app_models + app_cache.app_configs['managers_regress'].models = _old_models + app_cache._get_models_cache = {} def test_regress_3871(self): related = RelatedModel.objects.create() diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 08b7371301e6c..bad1b307dbaaa 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals import codecs -import copy import os import shutil @@ -132,10 +131,10 @@ def setUp(self): self.test_dir = os.path.abspath(os.path.dirname(upath(__file__))) self.migration_dir = os.path.join(self.test_dir, 'migrations_%d' % self.creation_counter) self.migration_pkg = "migrations.migrations_%d" % self.creation_counter - self._old_app_models = copy.deepcopy(app_cache.app_models) + self._old_models = app_cache.app_configs['migrations'].models.copy() def tearDown(self): - app_cache.app_models = self._old_app_models + app_cache.app_configs['migrations'].models = self._old_models app_cache._get_models_cache = {} os.chdir(self.test_dir) diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index d844d36c39feb..0ce030a66c0bd 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -1,5 +1,4 @@ import unittest -from django.apps import app_cache from django.db import connection, models, migrations, router from django.db.models.fields import NOT_PROVIDED from django.db.transaction import atomic diff --git a/tests/proxy_model_inheritance/tests.py b/tests/proxy_model_inheritance/tests.py index f3cdba20a9318..4c6e8c433ea88 100644 --- a/tests/proxy_model_inheritance/tests.py +++ b/tests/proxy_model_inheritance/tests.py @@ -34,8 +34,6 @@ def tearDown(self): sys.path = self.old_sys_path del app_cache.app_configs['app1'] del app_cache.app_configs['app2'] - del app_cache.app_models['app1'] - del app_cache.app_models['app2'] def test_table_exists(self): try: diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py index cdd979a399ddb..0c643991a7270 100644 --- a/tests/proxy_models/tests.py +++ b/tests/proxy_models/tests.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -import copy from django.apps import app_cache from django.contrib import admin @@ -155,12 +154,11 @@ class Meta: @override_settings(TEST_SWAPPABLE_MODEL='proxy_models.AlternateModel') def test_swappable(self): - try: - # This test adds dummy applications to the app cache. These - # need to be removed in order to prevent bad interactions - # with the flush operation in other tests. - old_app_models = copy.deepcopy(app_cache.app_models) + # The models need to be removed after the test in order to prevent bad + # interactions with the flush operation in other tests. + _old_models = app_cache.app_configs['proxy_models'].models.copy() + try: class SwappableModel(models.Model): class Meta: @@ -176,7 +174,8 @@ class ProxyModel(SwappableModel): class Meta: proxy = True finally: - app_cache.app_models = old_app_models + app_cache.app_configs['proxy_models'].models = _old_models + app_cache._get_models_cache = {} def test_myperson_manager(self): Person.objects.create(name="fred") diff --git a/tests/tablespaces/tests.py b/tests/tablespaces/tests.py index 9bcfed05ed69a..a1e2673f38c83 100644 --- a/tests/tablespaces/tests.py +++ b/tests/tablespaces/tests.py @@ -1,7 +1,5 @@ from __future__ import unicode_literals -import copy - from django.apps import app_cache from django.conf import settings from django.db import connection @@ -28,7 +26,7 @@ class TablespacesTests(TestCase): def setUp(self): # The unmanaged models need to be removed after the test in order to # prevent bad interactions with the flush operation in other tests. - self.old_app_models = copy.deepcopy(app_cache.app_models) + self._old_models = app_cache.app_configs['tablespaces'].models.copy() for model in Article, Authors, Reviewers, Scientist: model._meta.managed = True @@ -37,7 +35,7 @@ def tearDown(self): for model in Article, Authors, Reviewers, Scientist: model._meta.managed = False - app_cache.app_models = self.old_app_models + app_cache.app_configs['tablespaces'].models = self._old_models app_cache._get_models_cache = {} def assertNumContains(self, haystack, needle, count): From 321ce9ce22e8b7f012814dbef06805cc57589d66 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 12 Dec 2013 23:33:09 +0100 Subject: [PATCH 08/22] Fleshed out AppConfig objects. Marginally improved creation of AppConfig stubs for non-installed apps. --- django/apps/base.py | 27 ++++++++++++++++++++++++--- django/apps/cache.py | 22 ++++++++++------------ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/django/apps/base.py b/django/apps/base.py index 588709be9f160..cd8b008017cc6 100644 --- a/django/apps/base.py +++ b/django/apps/base.py @@ -1,25 +1,46 @@ from collections import OrderedDict +from django.utils._os import upath + class AppConfig(object): """ Class representing a Django application and its configuration. """ - def __init__(self, label, models_module=None, installed=True): + def __init__(self, name, app_module, models_module): + # Full Python path to the application eg. 'django.contrib.admin'. + # This is the value that appears in INSTALLED_APPS. + self.name = name + # Last component of the Python path to the application eg. 'admin'. - self.label = label + # This value must be unique across a Django project. + self.label = name.rpartition(".")[2] + + # Root module eg. . + self.app_module = app_module # Module containing models eg. . self.models_module = models_module # Mapping of lower case model names to model classes. + # Populated by AppCache.register_models(). self.models = OrderedDict() # Whether the app is in INSTALLED_APPS or was automatically created # when one of its models was imported. - self.installed = installed + self.installed = app_module is not None + + # Filesystem path to the application directory eg. + # u'/usr/lib/python2.7/dist-packages/django/contrib/admin'. + # This is a unicode object on Python 2 and a str on Python 3. + self.path = upath(app_module.__path__[0]) if app_module is not None else None + + @classmethod + def _stub(cls, label): + return cls(label, None, None) def __repr__(self): return '' % self.label diff --git a/django/apps/cache.py b/django/apps/cache.py index b3991e5e34dd4..182d89cc01a75 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -133,16 +133,15 @@ def load_app(self, app_name, can_postpone=False): raise self.nesting_level -= 1 - label = self._label_for(models_module) - try: - app_config = self.app_configs[label] - except KeyError: - self.app_configs[label] = AppConfig( - label=label, models_module=models_module) - else: - if not app_config.installed: - app_config.models_module = models_module - app_config.installed = True + + app_config = AppConfig( + name=app_name, app_module=app_module, models_module=models_module) + # If a stub config existed for this app, preserve models registry. + old_app_config = self.app_configs.get(app_config.label) + if old_app_config is not None: + app_config.models = old_app_config.models + self.app_configs[app_config.label] = app_config + return models_module def app_cache_ready(self): @@ -318,8 +317,7 @@ def register_models(self, app_label, *models): try: app_config = self.app_configs[app_label] except KeyError: - app_config = AppConfig( - label=app_label, installed=False) + app_config = AppConfig._stub(app_label) self.app_configs[app_label] = app_config for model in models: # Add the model to the app_config's models dictionary. From 8cd57fe32c576665a1fff71eea9b5ee97090fb2a Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 13 Dec 2013 12:02:54 +0100 Subject: [PATCH 09/22] Added get_app_config() to look up app configs by label. Refactored get_app() to rely on that method. get_app() starts by calling _populate(), which goes through INSTALLED_APPS and, for each app, imports the app module and attempts to import the models module. At this point, no further imports are necessary to return the models module for a given app. Therefore, the implementation of get_app() can be simplified and the safeguards for race conditions can be removed. Besides, the emptyOK parameter isn't used anywhere in Django. It was introduced in d6c95e93 but not actually used nor documented, and it has just been carried around since then. Since it's an obscure private API, it's acceptable to stop supporting it without a deprecation path. This branch aims at providing first-class support for applications without a models module eventually. For backwards-compatibility, get_app() still raises ImproperlyConfigured when an app isn't found, even though LookupError is technically more correct. I haven't gone as far as to preserve the exact error messages. I've adjusted a few tests instead. --- django/apps/cache.py | 41 ++++++++++++++++++++++-------------- tests/admin_scripts/tests.py | 16 +++++++------- tests/empty/tests.py | 2 +- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index 182d89cc01a75..893b1af2b4751 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -153,6 +153,26 @@ def app_cache_ready(self): """ return self.loaded + def get_app_config(self, app_label, only_installed=True): + """ + Returns the application configuration for the given app_label. + + Raises LookupError if no application exists with this app_label. + + Raises UnavailableApp when set_available_apps() disables the + application with this app_label. + + If only_installed is True (default), only applications explicitly + listed in INSTALLED_APPS are considered. + """ + self._populate() + app_config = self.app_configs.get(app_label) + if app_config is None or (only_installed and not app_config.installed): + raise LookupError("No app with label %r." % app_label) + if self.available_apps is not None and app_config.label not in self.available_apps: + raise UnavailableApp("App with label %r isn't available." % app_label) + return app_config + def get_apps(self): """ Returns a list of all installed modules that contain models. @@ -197,29 +217,18 @@ def get_app_paths(self): app_paths.append(self._get_app_path(app)) return app_paths - def get_app(self, app_label, emptyOK=False): + def get_app(self, app_label): """ 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() + return self.get_app_config(app_label).models_module + except LookupError as exc: + # Change the exception type for backwards compatibility. + raise ImproperlyConfigured(*exc.args) def get_models(self, app_mod=None, include_auto_created=False, include_deferred=False, diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 73a91b6e7b009..8f693bfd34336 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -379,14 +379,14 @@ def test_builtin_with_settings(self): args = ['sqlall', '--settings=test_project.settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) - self.assertOutput(err, 'App with label admin_scripts could not be found') + self.assertOutput(err, "No app with label 'admin_scripts'.") def test_builtin_with_environment(self): "minimal: django-admin builtin commands fail if settings are provided in the environment" args = ['sqlall', 'admin_scripts'] out, err = self.run_django_admin(args, 'test_project.settings') self.assertNoOutput(out) - self.assertOutput(err, 'App with label admin_scripts could not be found') + self.assertOutput(err, "No app with label 'admin_scripts'.") def test_builtin_with_bad_settings(self): "minimal: django-admin builtin commands fail if settings file (from argument) doesn't exist" @@ -815,21 +815,21 @@ def test_builtin_command(self): args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) - self.assertOutput(err, 'App with label admin_scripts could not be found') + self.assertOutput(err, "No app with label 'admin_scripts'.") def test_builtin_with_settings(self): "minimal: manage.py builtin commands fail if settings are provided as argument" args = ['sqlall', '--settings=test_project.settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) - self.assertOutput(err, 'App with label admin_scripts could not be found') + self.assertOutput(err, "No app with label 'admin_scripts'.") def test_builtin_with_environment(self): "minimal: manage.py builtin commands fail if settings are provided in the environment" args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args, 'test_project.settings') self.assertNoOutput(out) - self.assertOutput(err, 'App with label admin_scripts could not be found') + self.assertOutput(err, "No app with label 'admin_scripts'.") def test_builtin_with_bad_settings(self): "minimal: manage.py builtin commands fail if settings file (from argument) doesn't exist" @@ -964,7 +964,7 @@ def test_builtin_command(self): args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) - self.assertOutput(err, 'App with label admin_scripts could not be found.') + self.assertOutput(err, "No app with label 'admin_scripts'.") def test_builtin_with_settings(self): "multiple: manage.py builtin commands succeed if settings are provided as argument" @@ -1442,13 +1442,13 @@ def test_app_command_invalid_appname(self): "User AppCommands can execute when a single app name is provided" args = ['app_command', 'NOT_AN_APP'] out, err = self.run_manage(args) - self.assertOutput(err, "App with label NOT_AN_APP could not be found") + self.assertOutput(err, "No app with label 'NOT_AN_APP'.") def test_app_command_some_invalid_appnames(self): "User AppCommands can execute when some of the provided app names are invalid" args = ['app_command', 'auth', 'NOT_AN_APP'] out, err = self.run_manage(args) - self.assertOutput(err, "App with label NOT_AN_APP could not be found") + self.assertOutput(err, "No app with label 'NOT_AN_APP'.") def test_label_command(self): "User LabelCommands can execute when a label is provided" diff --git a/tests/empty/tests.py b/tests/empty/tests.py index b8476fc73dd36..824c1f16e7c9a 100644 --- a/tests/empty/tests.py +++ b/tests/empty/tests.py @@ -32,5 +32,5 @@ class NoModelTests(TestCase): @override_settings(INSTALLED_APPS=("empty.no_models",)) def test_no_models(self): with six.assertRaisesRegex(self, ImproperlyConfigured, - 'App with label no_models is missing a models.py module.'): + "No app with label 'no_models'."): app_cache.get_app('no_models') From c6ce08a0e5608a180042a1f7139f6cf667a27939 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 13 Dec 2013 20:22:21 +0100 Subject: [PATCH 10/22] Added get_app_configs() to iterate on app_config instances. Refactored get_apps() to rely on that method. This commit is fully backwards-compatible. --- django/apps/cache.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index 893b1af2b4751..034971c6241ce 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -153,6 +153,21 @@ def app_cache_ready(self): """ return self.loaded + def get_app_configs(self, only_installed=True): + """ + Return an iterable of application configurations. + + If only_installed is True (default), only applications explicitly + listed in INSTALLED_APPS are considered. + """ + self._populate() + for app_config in self.app_configs.values(): + if only_installed and not app_config.installed: + continue + if self.available_apps is not None and app_config.label not in self.available_apps: + continue + yield app_config + def get_app_config(self, app_label, only_installed=True): """ Returns the application configuration for the given app_label. @@ -177,15 +192,7 @@ def get_apps(self): """ Returns a list of all installed modules that contain models. """ - self._populate() - - # app_configs is an OrderedDict, which ensures that 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 = [app for app in self.app_configs.items() if app[1].installed] - if self.available_apps is not None: - apps = [app for app in apps if app[0] in self.available_apps] - return [app[1].models_module for app in apps] + return [app_config.models_module for app_config in self.get_app_configs()] def _get_app_package(self, app): return '.'.join(app.__name__.split('.')[:-1]) From 9c4c2bb7439c947600659e3178818bceba05cab9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 13 Dec 2013 19:28:42 +0100 Subject: [PATCH 11/22] Deprecated get_app_package, get_app_path and get_app_paths. --- django/apps/cache.py | 73 ++++++++++++--------- django/core/management/commands/loaddata.py | 4 +- django/core/management/sql.py | 2 +- django/db/migrations/loader.py | 2 +- django/db/migrations/writer.py | 8 +-- docs/internals/deprecation.txt | 3 +- 6 files changed, 52 insertions(+), 40 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index 034971c6241ce..6f673704ac4ea 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -5,6 +5,7 @@ from importlib import import_module import os import sys +import warnings from django.conf import settings from django.core.exceptions import ImproperlyConfigured @@ -194,36 +195,6 @@ def get_apps(self): """ return [app_config.models_module for app_config in self.get_app_configs()] - 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): """ Returns the module containing the models for the given app_label. @@ -363,6 +334,48 @@ def set_available_apps(self, available): def unset_available_apps(self): self.available_apps = None + ### DEPRECATED METHODS GO BELOW THIS LINE ### + + def _get_app_package(self, app): + return '.'.join(app.__name__.split('.')[:-1]) + + def get_app_package(self, app_label): + warnings.warn( + "get_app_config(label).name supersedes get_app_package(label).", + PendingDeprecationWarning, stacklevel=2) + 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): + warnings.warn( + "get_app_config(label).path supersedes get_app_path(label).", + PendingDeprecationWarning, stacklevel=2) + 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.) + """ + warnings.warn( + "[a.path for a in get_app_configs()] supersedes get_app_paths().", + PendingDeprecationWarning, stacklevel=2) + + self._populate() + + app_paths = [] + for app in self.get_apps(): + app_paths.append(self._get_app_path(app)) + return app_paths + class AppCache(BaseAppCache): """ diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 64a57fbc7940a..bfeba68aa61fe 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -230,8 +230,8 @@ def fixture_dirs(self): current directory. """ dirs = [] - for path in app_cache.get_app_paths(): - d = os.path.join(path, 'fixtures') + for app_config in app_cache.get_app_configs(): + d = os.path.join(app_config.path, 'fixtures') if os.path.isdir(d): dirs.append(d) dirs.extend(list(settings.FIXTURE_DIRS)) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index bcd4769dde11a..36ee5760e1eec 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -169,7 +169,7 @@ def _split_statements(content): def custom_sql_for_model(model, style, connection): opts = model._meta app_dirs = [] - app_dir = app_cache.get_app_path(model._meta.app_label) + app_dir = app_cache.get_app_config(model._meta.app_label).path app_dirs.append(os.path.normpath(os.path.join(app_dir, 'sql'))) # Deprecated location -- remove in Django 1.9 diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py index f4a71684416b4..859e57f3525ec 100644 --- a/django/db/migrations/loader.py +++ b/django/db/migrations/loader.py @@ -46,7 +46,7 @@ def migrations_module(cls, app_label): if app_label in settings.MIGRATION_MODULES: return settings.MIGRATION_MODULES[app_label] else: - return '%s.migrations' % app_cache.get_app_package(app_label) + return '%s.migrations' % app_cache.get_app_config(app_label).name def load_disk(self): """ diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py index 9356c8d8aa1ef..a23a9b12539b4 100644 --- a/django/db/migrations/writer.py +++ b/django/db/migrations/writer.py @@ -69,14 +69,12 @@ def path(self): migrations_module = import_module(migrations_package_name) basedir = os.path.dirname(migrations_module.__file__) except ImportError: - app = app_cache.get_app(self.migration.app_label) - app_path = app_cache._get_app_path(app) - app_package_name = app_cache._get_app_package(app) + app_config = app_cache.get_app_config(self.migration.app_label) migrations_package_basename = migrations_package_name.split(".")[-1] # Alright, see if it's a direct submodule of the app - if '%s.%s' % (app_package_name, migrations_package_basename) == migrations_package_name: - basedir = os.path.join(app_path, migrations_package_basename) + if '%s.%s' % (app_config.name, migrations_package_basename) == migrations_package_name: + basedir = os.path.join(app_config.path, migrations_package_basename) else: raise ImportError("Cannot open migrations module %s for app %s" % (migrations_package_name, self.migration.app_label)) return os.path.join(basedir, self.filename) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index d548f275fe6e1..c3b0cda95b982 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -223,7 +223,8 @@ these changes. 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. + loading APIs instead. Several undocumented methods of the ``AppCache`` class + will also be removed. 2.0 --- From c82bb4bb0b44dd3559e451ebb63dc6a355bc9001 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 13 Dec 2013 20:51:21 +0100 Subject: [PATCH 12/22] Simplified handling of available_apps slightly. It feels more natural for self.available_apps to contain app names (like INSTALLED_APPS) than app labels, and this is easy to implement now. --- django/apps/cache.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index 6f673704ac4ea..32c57371cb84e 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -35,7 +35,7 @@ def _initialize(): # Pending lookups for lazy relations pending_lookups={}, - # List of app_labels that allows restricting the set of apps. + # Set of app names. Allows restricting the set of installed apps. # Used by TransactionTestCase.available_apps for performance reasons. available_apps=None, @@ -165,7 +165,7 @@ def get_app_configs(self, only_installed=True): for app_config in self.app_configs.values(): if only_installed and not app_config.installed: continue - if self.available_apps is not None and app_config.label not in self.available_apps: + if self.available_apps is not None and app_config.name not in self.available_apps: continue yield app_config @@ -185,7 +185,7 @@ def get_app_config(self, app_label, only_installed=True): app_config = self.app_configs.get(app_label) if app_config is None or (only_installed and not app_config.installed): raise LookupError("No app with label %r." % app_label) - if self.available_apps is not None and app_config.label not in self.available_apps: + if self.available_apps is not None and app_config.name not in self.available_apps: raise UnavailableApp("App with label %r isn't available." % app_label) return app_config @@ -239,7 +239,10 @@ def get_models(self, app_mod=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] + model_list = [ + m for m in model_list + if self.app_configs[m._meta.app_label].name in self.available_apps + ] return model_list except KeyError: pass @@ -266,7 +269,10 @@ def get_models(self, app_mod=None, ) 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] + model_list = [ + m for m in model_list + if self.app_configs[m._meta.app_label].name in self.available_apps + ] return model_list def get_model(self, app_label, model_name, @@ -289,7 +295,7 @@ def get_model(self, app_label, model_name, if app_config is not None and not app_config.installed: return None if (self.available_apps is not None - and app_label not in self.available_apps): + and app_config.name not in self.available_apps): raise UnavailableApp("App with label %s isn't available." % app_label) try: return self.app_configs[app_label].models[model_name.lower()] @@ -325,11 +331,12 @@ def register_models(self, app_label, *models): 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) + available = set(available) + installed = set(settings.INSTALLED_APPS) + if not available.issubset(installed): 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) + "apps, extra apps: %s" % ", ".join(available - installed)) + self.available_apps = available def unset_available_apps(self): self.available_apps = None From 0deee8e78fc7319d66f6fa0374ef9e21aa39569e Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 13 Dec 2013 21:29:30 +0100 Subject: [PATCH 13/22] Simplified register_models. Since it's never called with more than one model at a time the current signature is needlessly complicated. --- django/apps/base.py | 2 +- django/apps/cache.py | 57 +++++++++++++++++-------------- django/db/models/base.py | 2 +- tests/migrations/test_commands.py | 4 +-- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/django/apps/base.py b/django/apps/base.py index cd8b008017cc6..778419fbef411 100644 --- a/django/apps/base.py +++ b/django/apps/base.py @@ -26,7 +26,7 @@ def __init__(self, name, app_module, models_module): self.models_module = models_module # Mapping of lower case model names to model classes. - # Populated by AppCache.register_models(). + # Populated by calls to AppCache.register_model(). self.models = OrderedDict() # Whether the app is in INSTALLED_APPS or was automatically created diff --git a/django/apps/cache.py b/django/apps/cache.py index 32c57371cb84e..b125d6b4c6f73 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -302,32 +302,27 @@ def get_model(self, app_label, model_name, except KeyError: return None - def register_models(self, app_label, *models): - """ - Register a set of models as belonging to an app. - """ - if models: - try: - app_config = self.app_configs[app_label] - except KeyError: - app_config = AppConfig._stub(app_label) - self.app_configs[app_label] = app_config - for model in models: - # Add the model to the app_config's models dictionary. - model_name = model._meta.model_name - model_dict = app_config.models - 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 + def register_model(self, app_label, model): + try: + app_config = self.app_configs[app_label] + except KeyError: + app_config = AppConfig._stub(app_label) + self.app_configs[app_label] = app_config + # Add the model to the app_config's models dictionary. + model_name = model._meta.model_name + model_dict = app_config.models + 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]: + return + model_dict[model_name] = model self._get_models_cache.clear() def set_available_apps(self, available): @@ -383,6 +378,16 @@ def get_app_paths(self): app_paths.append(self._get_app_path(app)) return app_paths + def register_models(self, app_label, *models): + """ + Register a set of models as belonging to an app. + """ + warnings.warn( + "register_models(app_label, models) is deprecated.", + PendingDeprecationWarning, stacklevel=2) + for model in models: + self.register_model(app_label, model) + class AppCache(BaseAppCache): """ diff --git a/django/db/models/base.py b/django/db/models/base.py index 94d72341425c2..1d514ae1e26d9 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -274,7 +274,7 @@ def __new__(cls, name, bases, attrs): new_class._prepare() - new_class._meta.app_cache.register_models(new_class._meta.app_label, new_class) + new_class._meta.app_cache.register_model(new_class._meta.app_label, new_class) # Because of the way imports happen (recursively), we may or may not be # the first time this model tries to register with the framework. There # should only be one class for each model, so we always return the diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index bad1b307dbaaa..a9503e94a874d 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -151,7 +151,7 @@ def _rmrf(self, dname): def test_files_content(self): self.assertTableNotExists("migrations_unicodemodel") - app_cache.register_models('migrations', UnicodeModel) + app_cache.register_model('migrations', UnicodeModel) with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}): call_command("makemigrations", "migrations", verbosity=0) @@ -187,7 +187,7 @@ def test_files_content(self): def test_failing_migration(self): #21280 - If a migration fails to serialize, it shouldn't generate an empty file. - app_cache.register_models('migrations', UnserializableModel) + app_cache.register_model('migrations', UnserializableModel) with six.assertRaisesRegex(self, ValueError, r'Cannot serialize'): with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}): From 1f1795baa5d59d9f727945a85fc5442f5ecfa046 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 14 Dec 2013 09:50:28 +0100 Subject: [PATCH 14/22] Removed the _-prefix for populate(). Several parts of Django call get_apps() with a comment along this lines of "this has the side effect of calling _populate()". I fail to see how this is better than just calling populate()! --- django/apps/cache.py | 14 +++++++------- django/contrib/admin/validation.py | 6 +++--- django/core/serializers/base.py | 7 +++---- django/core/serializers/python.py | 3 ++- django/db/models/loading.py | 2 +- tests/runtests.py | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index b125d6b4c6f73..9d762a58ec40a 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -60,11 +60,11 @@ class BaseAppCache(object): def __init__(self): self.__dict__ = _initialize() - # This stops _populate loading from INSTALLED_APPS and ignores the + # This stops populate loading from INSTALLED_APPS and ignores the # only_installed arguments to get_model[s] self.loads_installed = False - def _populate(self): + 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 @@ -161,7 +161,7 @@ def get_app_configs(self, only_installed=True): If only_installed is True (default), only applications explicitly listed in INSTALLED_APPS are considered. """ - self._populate() + self.populate() for app_config in self.app_configs.values(): if only_installed and not app_config.installed: continue @@ -181,7 +181,7 @@ def get_app_config(self, app_label, only_installed=True): If only_installed is True (default), only applications explicitly listed in INSTALLED_APPS are considered. """ - self._populate() + self.populate() app_config = self.app_configs.get(app_label) if app_config is None or (only_installed and not app_config.installed): raise LookupError("No app with label %r." % app_label) @@ -246,7 +246,7 @@ def get_models(self, app_mod=None, return model_list except KeyError: pass - self._populate() + self.populate() if app_mod: app_label = self._label_for(app_mod) try: @@ -289,7 +289,7 @@ def get_model(self, app_label, model_name, if not self.loads_installed: only_installed = False if seed_cache: - self._populate() + self.populate() if only_installed: app_config = self.app_configs.get(app_label) if app_config is not None and not app_config.installed: @@ -371,7 +371,7 @@ def get_app_paths(self): "[a.path for a in get_app_configs()] supersedes get_app_paths().", PendingDeprecationWarning, stacklevel=2) - self._populate() + self.populate() app_paths = [] for app in self.get_apps(): diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index c8bd4af1209cf..79fa9c672c600 100644 --- a/django/contrib/admin/validation.py +++ b/django/contrib/admin/validation.py @@ -16,9 +16,9 @@ class BaseValidator(object): def __init__(self): - # Before we can introspect models, they need to be fully loaded so that - # inter-relations are set up correctly. We force that here. - app_cache.get_apps() + # Before we can introspect models, they need the app cache to be fully + # loaded so that inter-relations are set up correctly. + app_cache.populate() def validate(self, cls, model): for m in dir(self): diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index e575c1d1c252e..98fbb081c56e3 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -137,10 +137,9 @@ def __init__(self, stream_or_string, **options): self.stream = six.StringIO(stream_or_string) else: self.stream = stream_or_string - # hack to make sure that the models have all been loaded before - # deserialization starts (otherwise subclass calls to get_model() - # and friends might fail...) - app_cache.get_apps() + # Make sure the app cache is loaded before deserialization starts + # (otherwise subclass calls to get_model() and friends might fail...) + app_cache.populate() def __iter__(self): return self diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 07f857a198deb..47edc802173dd 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -88,7 +88,8 @@ def Deserializer(object_list, **options): db = options.pop('using', DEFAULT_DB_ALIAS) ignore = options.pop('ignorenonexistent', False) - app_cache.get_apps() + app_cache.populate() + for d in object_list: # Look up the model and starting build a dict of data for it. Model = _get_model(d["model"]) diff --git a/django/db/models/loading.py b/django/db/models/loading.py index f814f949e4f49..ad267d8462ebc 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -30,6 +30,6 @@ def get_app_errors(): try: return app_cache.app_errors except AttributeError: - app_cache._populate() + app_cache.populate() app_cache.app_errors = {} return app_cache.app_errors diff --git a/tests/runtests.py b/tests/runtests.py index 9ef81f8877a6d..c294664c31050 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -128,7 +128,7 @@ def no_available_apps(self): # Load all the ALWAYS_INSTALLED_APPS. with warnings.catch_warnings(): warnings.filterwarnings('ignore', 'django.contrib.comments is deprecated and will be removed before Django 1.8.', DeprecationWarning) - app_cache.get_apps() + app_cache.populate() # Load all the test model apps. test_modules = get_test_modules() From 849143f4611a01e97b78818704ce0c84272d8731 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 14 Dec 2013 10:08:44 +0100 Subject: [PATCH 15/22] Deprecated get_apps(). --- django/apps/cache.py | 15 ++++++---- django/contrib/contenttypes/management.py | 4 +-- django/core/management/commands/dumpdata.py | 4 ++- django/core/management/commands/flush.py | 4 +-- django/core/management/commands/migrate.py | 7 +++-- django/core/management/sql.py | 32 ++++++++++++--------- django/db/backends/__init__.py | 13 ++++----- django/db/migrations/loader.py | 15 +++++----- django/test/simple.py | 4 +-- tests/runtests.py | 2 +- 10 files changed, 54 insertions(+), 46 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index 9d762a58ec40a..c0648a9015abd 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -189,12 +189,6 @@ def get_app_config(self, app_label, only_installed=True): raise UnavailableApp("App with label %r isn't available." % app_label) return app_config - def get_apps(self): - """ - Returns a list of all installed modules that contain models. - """ - return [app_config.models_module for app_config in self.get_app_configs()] - def get_app(self, app_label): """ Returns the module containing the models for the given app_label. @@ -338,6 +332,15 @@ def unset_available_apps(self): ### DEPRECATED METHODS GO BELOW THIS LINE ### + def get_apps(self): + """ + Returns a list of all installed modules that contain models. + """ + warnings.warn( + "[a.models_module for a in get_app_configs()] supersedes get_apps().", + PendingDeprecationWarning, stacklevel=2) + return [app_config.models_module for app_config in self.get_app_configs()] + def _get_app_package(self, app): return '.'.join(app.__name__.split('.')[:-1]) diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index 477464c37e1ef..dfe5bb3c65bf5 100644 --- a/django/contrib/contenttypes/management.py +++ b/django/contrib/contenttypes/management.py @@ -86,8 +86,8 @@ def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, * def update_all_contenttypes(verbosity=2, **kwargs): - for app in app_cache.get_apps(): - update_contenttypes(app, None, verbosity, **kwargs) + for app_config in app_cache.get_app_configs(): + update_contenttypes(app_config.models_module, None, verbosity, **kwargs) signals.post_migrate.connect(update_contenttypes) diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 4f87b0400339b..ebef8ba75ce6c 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -78,7 +78,9 @@ def handle(self, *app_labels, **options): if len(app_labels) == 0: if primary_keys: raise CommandError("You can only use --pks option with one model") - app_list = OrderedDict((app, None) for app in app_cache.get_apps() if app not in excluded_apps) + app_list = OrderedDict((app_config.models_module, None) + for app_config in app_cache.get_app_configs() + if app_config.models_module not in excluded_apps) else: if len(app_labels) > 1 and primary_keys: raise CommandError("You can only use --pks option with one model") diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index f4b221a32c817..0701bf82a9b02 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -94,6 +94,6 @@ def emit_post_migrate(verbosity, interactive, database): # Emit the post migrate signal. This allows individual applications to # respond as if the database had been migrated from scratch. all_models = [] - for app in app_cache.get_apps(): - all_models.extend(router.get_migratable_models(app, database, include_auto_created=True)) + for app_config in app_cache.get_app_configs(): + all_models.extend(router.get_migratable_models(app_config.models_module, database, include_auto_created=True)) emit_post_migrate_signal(set(all_models), verbosity, interactive, database) diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index 39a2d9a78495b..ce5ffae4a5a18 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -180,9 +180,10 @@ def sync_apps(self, connection, apps): # Build the manifest of apps and models that are to be synchronized all_models = [ - (app.__name__.split('.')[-2], - router.get_migratable_models(app, connection.alias, include_auto_created=True)) - for app in app_cache.get_apps() if app.__name__.split('.')[-2] in apps + (app_config.label, + router.get_migratable_models(app_config.models_module, connection.alias, include_auto_created=True)) + for app_config in app_cache.get_app_configs() + if app_config.label in apps ] def model_installed(model): diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 36ee5760e1eec..909faf011714d 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -207,23 +207,27 @@ def custom_sql_for_model(model, style, connection): def emit_pre_migrate_signal(create_models, verbosity, interactive, db): # Emit the pre_migrate signal for every application. - for app in app_cache.get_apps(): - app_name = app.__name__.split('.')[-2] + for app_config in app_cache.get_app_configs(): if verbosity >= 2: - print("Running pre-migrate handlers for application %s" % app_name) - models.signals.pre_migrate.send(sender=app, app=app, - create_models=create_models, - verbosity=verbosity, - interactive=interactive, - db=db) + print("Running pre-migrate handlers for application %s" % app_config.label) + models.signals.pre_migrate.send( + sender=app_config.models_module, + app=app_config.models_module, + create_models=create_models, + verbosity=verbosity, + interactive=interactive, + db=db) def emit_post_migrate_signal(created_models, verbosity, interactive, db): # Emit the post_migrate signal for every application. - for app in app_cache.get_apps(): - app_name = app.__name__.split('.')[-2] + for app_config in app_cache.get_app_configs(): if verbosity >= 2: - print("Running post-migrate handlers for application %s" % app_name) - models.signals.post_migrate.send(sender=app, app=app, - created_models=created_models, verbosity=verbosity, - interactive=interactive, db=db) + print("Running post-migrate handlers for application %s" % app_config.label) + models.signals.post_migrate.send( + sender=app_config.models_module, + app=app_config.models_module, + created_models=created_models, + verbosity=verbosity, + interactive=interactive, + db=db) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index edbca0e5572d0..6e5d03ff96f23 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -1271,8 +1271,8 @@ def django_table_names(self, only_existing=False): from django.apps import app_cache from django.db import router tables = set() - for app in app_cache.get_apps(): - for model in router.get_migratable_models(app, self.connection.alias): + for app_config in app_cache.get_app_configs(): + for model in router.get_migratable_models(app_config.models_module, self.connection.alias): if not model._meta.managed: continue tables.add(model._meta.db_table) @@ -1292,8 +1292,8 @@ def installed_models(self, tables): from django.apps import app_cache from django.db import router all_models = [] - for app in app_cache.get_apps(): - all_models.extend(router.get_migratable_models(app, self.connection.alias)) + for app_config in app_cache.get_app_configs(): + all_models.extend(router.get_migratable_models(app_config.models_module, self.connection.alias)) tables = list(map(self.table_name_converter, tables)) return set([ m for m in all_models @@ -1305,11 +1305,10 @@ def sequence_list(self): from django.apps import app_cache from django.db import models, router - apps = app_cache.get_apps() sequence_list = [] - for app in apps: - for model in router.get_migratable_models(app, self.connection.alias): + for app_config in app_cache.get_app_configs(): + for model in router.get_migratable_models(app_config.models_module, self.connection.alias): if not model._meta.managed: continue if model._meta.swapped: diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py index 859e57f3525ec..af311fdf02901 100644 --- a/django/db/migrations/loader.py +++ b/django/db/migrations/loader.py @@ -55,10 +55,9 @@ def load_disk(self): self.disk_migrations = {} self.unmigrated_apps = set() self.migrated_apps = set() - for app in app_cache.get_apps(): + for app_config in app_cache.get_app_configs(): # Get the migrations module directory - app_label = app.__name__.split(".")[-2] - module_name = self.migrations_module(app_label) + module_name = self.migrations_module(app_config.label) was_loaded = module_name in sys.modules try: module = import_module(module_name) @@ -66,7 +65,7 @@ def load_disk(self): # I hate doing this, but I don't want to squash other import errors. # Might be better to try a directory check directly. if "No module named" in str(e) and "migrations" in str(e): - self.unmigrated_apps.add(app_label) + self.unmigrated_apps.add(app_config.label) continue raise else: @@ -79,7 +78,7 @@ def load_disk(self): # Force a reload if it's already loaded (tests need this) if was_loaded: six.moves.reload_module(module) - self.migrated_apps.add(app_label) + self.migrated_apps.add(app_config.label) directory = os.path.dirname(module.__file__) # Scan for .py[c|o] files migration_names = set() @@ -100,14 +99,14 @@ def load_disk(self): break raise if not hasattr(migration_module, "Migration"): - raise BadMigrationError("Migration %s in app %s has no Migration class" % (migration_name, app_label)) + raise BadMigrationError("Migration %s in app %s has no Migration class" % (migration_name, app_config.label)) # Ignore South-style migrations if hasattr(migration_module.Migration, "forwards"): south_style_migrations = True break - self.disk_migrations[app_label, migration_name] = migration_module.Migration(migration_name, app_label) + self.disk_migrations[app_config.label, migration_name] = migration_module.Migration(migration_name, app_config.label) if south_style_migrations: - self.unmigrated_apps.add(app_label) + self.unmigrated_apps.add(app_config.label) def get_migration(self, app_label, name_prefix): "Gets the migration exactly named, or raises KeyError" diff --git a/django/test/simple.py b/django/test/simple.py index 10f29061b3da4..d00b95f0087b4 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -244,8 +244,8 @@ def build_suite(self, test_labels, extra_tests=None, **kwargs): app = app_cache.get_app(label) suite.addTest(build_suite(app)) else: - for app in app_cache.get_apps(): - suite.addTest(build_suite(app)) + for app_config in app_cache.get_app_configs(): + suite.addTest(build_suite(app_config.models_module)) if extra_tests: for test in extra_tests: diff --git a/tests/runtests.py b/tests/runtests.py index c294664c31050..bec2d22feaefd 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -81,7 +81,7 @@ def get_test_modules(): def get_installed(): from django.apps import app_cache - return [app.__name__.rsplit('.', 1)[0] for app in app_cache.get_apps()] + return [app_config.name for app_config in app_cache.get_app_configs()] def setup(verbosity, test_labels): From 82607872b5629cafed009d1e2cbf8377def91a31 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 14 Dec 2013 11:11:52 +0100 Subject: [PATCH 16/22] Deprecated get_app(). --- django/apps/cache.py | 29 ++++++++++--------- django/contrib/admindocs/views.py | 17 ++++------- django/contrib/auth/tests/test_management.py | 6 ++-- django/core/management/base.py | 5 ++-- django/core/management/commands/dumpdata.py | 13 ++++----- .../management/commands/makemigrations.py | 5 ++-- django/db/migrations/questioner.py | 7 ++--- django/test/simple.py | 4 +-- tests/app_cache/tests.py | 4 +-- tests/commands_sql/tests.py | 12 ++++---- tests/defer_regress/tests.py | 8 ++--- tests/empty/tests.py | 5 ++-- tests/test_suite_override/tests.py | 2 +- 13 files changed, 55 insertions(+), 62 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index c0648a9015abd..76e580eb408f6 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -189,19 +189,6 @@ def get_app_config(self, app_label, only_installed=True): raise UnavailableApp("App with label %r isn't available." % app_label) return app_config - def get_app(self, app_label): - """ - Returns the module containing the models for the given app_label. - - Raises UnavailableApp when set_available_apps() in in effect and - doesn't include app_label. - """ - try: - return self.get_app_config(app_label).models_module - except LookupError as exc: - # Change the exception type for backwards compatibility. - raise ImproperlyConfigured(*exc.args) - def get_models(self, app_mod=None, include_auto_created=False, include_deferred=False, only_installed=True, include_swapped=False): @@ -332,6 +319,22 @@ def unset_available_apps(self): ### DEPRECATED METHODS GO BELOW THIS LINE ### + def get_app(self, app_label): + """ + Returns the module containing the models for the given app_label. + + Raises UnavailableApp when set_available_apps() in in effect and + doesn't include app_label. + """ + warnings.warn( + "get_app_config(app_label).models_module supersedes get_app(app_label).", + PendingDeprecationWarning, stacklevel=2) + try: + return self.get_app_config(app_label).models_module + except LookupError as exc: + # Change the exception type for backwards compatibility. + raise ImproperlyConfigured(*exc.args) + def get_apps(self): """ Returns a list of all installed modules that contain models. diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index e1edf0aad8393..c52e0f0d7f57d 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -9,7 +9,7 @@ from django.contrib import admin from django.contrib.admin.views.decorators import staff_member_required from django.db import models -from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist +from django.core.exceptions import ViewDoesNotExist from django.http import Http404 from django.core import urlresolvers from django.contrib.admindocs import utils @@ -194,17 +194,12 @@ class ModelDetailView(BaseAdminDocsView): def get_context_data(self, **kwargs): # Get the model class. try: - app_mod = app_cache.get_app(self.kwargs['app_label']) - except ImproperlyConfigured: - raise Http404(_("App %r not found") % self.kwargs['app_label']) - model = None - for m in app_cache.get_models(app_mod): - if m._meta.model_name == self.kwargs['model_name']: - model = m - break + app_cache.get_app_config(self.kwargs['app_label']) + except LookupError: + raise Http404(_("App %(app_label)r not found") % self.kwargs) + model = app_cache.get_model(self.kwargs['app_label'], self.kwargs['model_name']) if model is None: - raise Http404(_("Model %(model_name)r not found in app %(app_label)r") % { - 'model_name': self.kwargs['model_name'], 'app_label': self.kwargs['app_label']}) + raise Http404(_("Model %(model_name)r not found in app %(app_label)r") % self.kwargs) opts = model._meta diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py index 43ad15dcabc36..c005e74442eef 100644 --- a/django/contrib/auth/tests/test_management.py +++ b/django/contrib/auth/tests/test_management.py @@ -184,21 +184,21 @@ class CustomUserModelValidationTestCase(TestCase): def test_required_fields_is_list(self): "REQUIRED_FIELDS should be a list." new_io = StringIO() - get_validation_errors(new_io, app_cache.get_app('auth')) + get_validation_errors(new_io, app_cache.get_app_config('auth').models_module) self.assertIn("The REQUIRED_FIELDS must be a list or tuple.", new_io.getvalue()) @override_settings(AUTH_USER_MODEL='auth.CustomUserBadRequiredFields') def test_username_not_in_required_fields(self): "USERNAME_FIELD should not appear in REQUIRED_FIELDS." new_io = StringIO() - get_validation_errors(new_io, app_cache.get_app('auth')) + get_validation_errors(new_io, app_cache.get_app_config('auth').models_module) self.assertIn("The field named as the USERNAME_FIELD should not be included in REQUIRED_FIELDS on a swappable User model.", new_io.getvalue()) @override_settings(AUTH_USER_MODEL='auth.CustomUserNonUniqueUsername') def test_username_non_unique(self): "A non-unique USERNAME_FIELD should raise a model validation error." new_io = StringIO() - get_validation_errors(new_io, app_cache.get_app('auth')) + get_validation_errors(new_io, app_cache.get_app_config('auth').models_module) self.assertIn("The USERNAME_FIELD must be unique. Add unique=True to the field parameters.", new_io.getvalue()) diff --git a/django/core/management/base.py b/django/core/management/base.py index d749bc2e8e52b..c7d9c939e6026 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -11,7 +11,6 @@ from optparse import make_option, OptionParser import django -from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style, no_style from django.utils.encoding import force_str from django.utils.six import StringIO @@ -346,8 +345,8 @@ def handle(self, *app_labels, **options): if not app_labels: raise CommandError('Enter at least one appname.') try: - app_list = [app_cache.get_app(app_label) for app_label in app_labels] - except (ImproperlyConfigured, ImportError) as e: + app_list = [app_cache.get_app_config(app_label).models_module for app_label in app_labels] + except (LookupError, ImportError) as e: raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e) output = [] for app in app_list: diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index ebef8ba75ce6c..af8da18322ab3 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -3,7 +3,6 @@ from collections import OrderedDict from optparse import make_option -from django.core.exceptions import ImproperlyConfigured from django.core.management.base import BaseCommand, CommandError from django.core import serializers from django.db import router, DEFAULT_DB_ALIAS @@ -70,9 +69,9 @@ def handle(self, *app_labels, **options): excluded_models.add(model_obj) else: try: - app_obj = app_cache.get_app(exclude) + app_obj = app_cache.get_app_config(exclude).models_module excluded_apps.add(app_obj) - except ImproperlyConfigured: + except LookupError: raise CommandError('Unknown app in excludes: %s' % exclude) if len(app_labels) == 0: @@ -89,8 +88,8 @@ def handle(self, *app_labels, **options): try: app_label, model_label = label.split('.') try: - app = app_cache.get_app(app_label) - except ImproperlyConfigured: + app = app_cache.get_app_config(app_label).models_module + except LookupError: raise CommandError("Unknown application: %s" % app_label) if app in excluded_apps: continue @@ -109,8 +108,8 @@ def handle(self, *app_labels, **options): # This is just an app - no model qualifier app_label = label try: - app = app_cache.get_app(app_label) - except ImproperlyConfigured: + app = app_cache.get_app_config(app_label).models_module + except LookupError: raise CommandError("Unknown application: %s" % app_label) if app in excluded_apps: continue diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py index cd891c404d14c..aaf02708401f6 100644 --- a/django/core/management/commands/makemigrations.py +++ b/django/core/management/commands/makemigrations.py @@ -5,7 +5,6 @@ from django.apps import app_cache from django.core.management.base import BaseCommand, CommandError -from django.core.exceptions import ImproperlyConfigured from django.db import connections, DEFAULT_DB_ALIAS, migrations from django.db.migrations.loader import MigrationLoader from django.db.migrations.autodetector import MigrationAutodetector @@ -38,8 +37,8 @@ def handle(self, *app_labels, **options): bad_app_labels = set() for app_label in app_labels: try: - app_cache.get_app(app_label) - except ImproperlyConfigured: + app_cache.get_app_config(app_label) + except LookupError: bad_app_labels.add(app_label) if bad_app_labels: for app_label in bad_app_labels: diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py index d9446f8d885c7..4a860dcf7e0eb 100644 --- a/django/db/migrations/questioner.py +++ b/django/db/migrations/questioner.py @@ -5,7 +5,6 @@ from django.apps import app_cache from django.utils import datetime_safe from django.utils.six.moves import input -from django.core.exceptions import ImproperlyConfigured class MigrationQuestioner(object): @@ -29,10 +28,10 @@ def ask_initial(self, app_label): # Apps from the new app template will have these; the python # file check will ensure we skip South ones. try: - models_module = app_cache.get_app(app_label) - except ImproperlyConfigured: # It's a fake app + app_config = app_cache.get_app_config(app_label) + except LookupError: # It's a fake app. return self.defaults.get("ask_initial", False) - migrations_import_path = "%s.migrations" % models_module.__package__ + migrations_import_path = "%s.migrations" % app_config.name try: migrations_module = importlib.import_module(migrations_import_path) except ImportError: diff --git a/django/test/simple.py b/django/test/simple.py index d00b95f0087b4..8ea1d6ac539c9 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -179,7 +179,7 @@ def build_test(label): # # First, look for TestCase instances with a name that matches # - app_module = app_cache.get_app(parts[0]) + app_module = app_cache.get_app_config(parts[0]).models_module test_module = get_tests(app_module) TestClass = getattr(app_module, parts[1], None) @@ -241,7 +241,7 @@ def build_suite(self, test_labels, extra_tests=None, **kwargs): if '.' in label: suite.addTest(build_test(label)) else: - app = app_cache.get_app(label) + app = app_cache.get_app_config(label).models_module suite.addTest(build_suite(app)) else: for app_config in app_cache.get_app_configs(): diff --git a/tests/app_cache/tests.py b/tests/app_cache/tests.py index cc3637367bb91..484998232e8c7 100644 --- a/tests/app_cache/tests.py +++ b/tests/app_cache/tests.py @@ -27,7 +27,7 @@ def test_dynamic_load(self): """ Makes a new model at runtime and ensures it goes into the right place. """ - old_models = app_cache.get_models(app_cache.get_app("app_cache")) + old_models = app_cache.get_models(app_cache.get_app_config("app_cache").models_module) # Construct a new model in a new app cache body = {} new_app_cache = BaseAppCache() @@ -42,6 +42,6 @@ def test_dynamic_load(self): # Make sure it appeared in the right place! self.assertEqual( old_models, - app_cache.get_models(app_cache.get_app("app_cache")), + app_cache.get_models(app_cache.get_app_config("app_cache").models_module), ) self.assertEqual(new_app_cache.get_model("app_cache", "SouthPonies"), temp_model) diff --git a/tests/commands_sql/tests.py b/tests/commands_sql/tests.py index 82e55d9861627..1c2a7f0ffa19c 100644 --- a/tests/commands_sql/tests.py +++ b/tests/commands_sql/tests.py @@ -17,7 +17,7 @@ def count_ddl(self, output, cmd): return len([o for o in output if o.startswith(cmd)]) def test_sql_create(self): - app = app_cache.get_app('commands_sql') + app = app_cache.get_app_config('commands_sql').models_module output = sql_create(app, no_style(), connections[DEFAULT_DB_ALIAS]) create_tables = [o for o in output if o.startswith('CREATE TABLE')] self.assertEqual(len(create_tables), 3) @@ -26,7 +26,7 @@ def test_sql_create(self): six.assertRegex(self, sql, r'^create table .commands_sql_book.*') def test_sql_delete(self): - app = app_cache.get_app('commands_sql') + app = app_cache.get_app_config('commands_sql').models_module output = sql_delete(app, no_style(), connections[DEFAULT_DB_ALIAS]) drop_tables = [o for o in output if o.startswith('DROP TABLE')] self.assertEqual(len(drop_tables), 3) @@ -35,19 +35,19 @@ def test_sql_delete(self): six.assertRegex(self, sql, r'^drop table .commands_sql_comment.*') def test_sql_indexes(self): - app = app_cache.get_app('commands_sql') + app = app_cache.get_app_config('commands_sql').models_module output = sql_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS]) # PostgreSQL creates one additional index for CharField self.assertIn(self.count_ddl(output, 'CREATE INDEX'), [3, 4]) def test_sql_destroy_indexes(self): - app = app_cache.get_app('commands_sql') + app = app_cache.get_app_config('commands_sql').models_module output = sql_destroy_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS]) # PostgreSQL creates one additional index for CharField self.assertIn(self.count_ddl(output, 'DROP INDEX'), [3, 4]) def test_sql_all(self): - app = app_cache.get_app('commands_sql') + app = app_cache.get_app_config('commands_sql').models_module output = sql_all(app, no_style(), connections[DEFAULT_DB_ALIAS]) self.assertEqual(self.count_ddl(output, 'CREATE TABLE'), 3) @@ -69,7 +69,7 @@ def tearDown(self): router.routers = self._old_routers def test_router_honored(self): - app = app_cache.get_app('commands_sql') + app = app_cache.get_app_config('commands_sql').models_module for sql_command in (sql_all, sql_create, sql_delete, sql_indexes, sql_destroy_indexes): output = sql_command(app, no_style(), connections[DEFAULT_DB_ALIAS]) self.assertEqual(len(output), 0, diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py index 794ea6e18e4ad..442af9f7cf7d8 100644 --- a/tests/defer_regress/tests.py +++ b/tests/defer_regress/tests.py @@ -103,7 +103,7 @@ def test_ticket_11936(self): klasses = set( map( attrgetter("__name__"), - app_cache.get_models(app_cache.get_app("defer_regress")) + app_cache.get_models(app_cache.get_app_config("defer_regress").models_module) ) ) self.assertIn("Child", klasses) @@ -111,13 +111,13 @@ def test_ticket_11936(self): self.assertNotIn("Child_Deferred_value", klasses) self.assertNotIn("Item_Deferred_name", klasses) self.assertFalse(any( - k._deferred for k in app_cache.get_models(app_cache.get_app("defer_regress")))) + k._deferred for k in app_cache.get_models(app_cache.get_app_config("defer_regress").models_module))) klasses_with_deferred = set( map( attrgetter("__name__"), app_cache.get_models( - app_cache.get_app("defer_regress"), include_deferred=True + app_cache.get_app_config("defer_regress").models_module, include_deferred=True ), ) ) @@ -127,7 +127,7 @@ def test_ticket_11936(self): self.assertIn("Item_Deferred_name", klasses_with_deferred) self.assertTrue(any( k._deferred for k in app_cache.get_models( - app_cache.get_app("defer_regress"), include_deferred=True)) + app_cache.get_app_config("defer_regress").models_module, include_deferred=True)) ) @override_settings(SESSION_SERIALIZER='django.contrib.sessions.serializers.PickleSerializer') diff --git a/tests/empty/tests.py b/tests/empty/tests.py index 824c1f16e7c9a..9fcd7a978ee3c 100644 --- a/tests/empty/tests.py +++ b/tests/empty/tests.py @@ -1,5 +1,4 @@ from django.apps import app_cache -from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from django.test.utils import override_settings from django.utils import six @@ -31,6 +30,6 @@ class NoModelTests(TestCase): """ @override_settings(INSTALLED_APPS=("empty.no_models",)) def test_no_models(self): - with six.assertRaisesRegex(self, ImproperlyConfigured, + with six.assertRaisesRegex(self, LookupError, "No app with label 'no_models'."): - app_cache.get_app('no_models') + app_cache.get_app_config('no_models') diff --git a/tests/test_suite_override/tests.py b/tests/test_suite_override/tests.py index ed336076e76a7..5c5e433dbc339 100644 --- a/tests/test_suite_override/tests.py +++ b/tests/test_suite_override/tests.py @@ -20,7 +20,7 @@ def test_suite_override(self): """ from django.test.simple import build_suite - app = app_cache.get_app("test_suite_override") + app = app_cache.get_app_config("test_suite_override").models_module suite = build_suite(app) self.assertEqual(suite.countTestCases(), 1) From 2eb00ad301332345467a920137c2fd065363385b Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 14 Dec 2013 18:51:58 +0100 Subject: [PATCH 17/22] Made it possible to create apps without a models module. This commit reverts f44c4a5d0f and 39bbd165. django.test.simple will be updated in a separate commit as it requires invasive changes. --- django/apps/base.py | 3 ++- django/apps/cache.py | 28 +++++++++++++++------ django/contrib/contenttypes/management.py | 2 +- django/core/management/base.py | 10 +++++--- django/core/management/commands/dumpdata.py | 9 ++++--- django/core/management/commands/flush.py | 2 +- django/core/management/commands/migrate.py | 2 +- django/core/management/sql.py | 4 +-- django/db/backends/__init__.py | 6 ++--- django/db/migrations/loader.py | 2 +- tests/empty/no_models/tests.py | 6 ----- tests/empty/tests.py | 20 --------------- tests/no_models/__init__.py | 0 tests/no_models/tests.py | 10 ++++++++ 14 files changed, 53 insertions(+), 51 deletions(-) delete mode 100644 tests/empty/no_models/tests.py create mode 100644 tests/no_models/__init__.py create mode 100644 tests/no_models/tests.py diff --git a/django/apps/base.py b/django/apps/base.py index 778419fbef411..860777bb0301b 100644 --- a/django/apps/base.py +++ b/django/apps/base.py @@ -22,7 +22,8 @@ def __init__(self, name, app_module, models_module): self.app_module = app_module # Module containing models eg. . + # from 'django/contrib/admin/models.pyc'>. None if the application + # doesn't have a models module. self.models_module = models_module # Mapping of lower case model names to model classes. diff --git a/django/apps/cache.py b/django/apps/cache.py index 76e580eb408f6..4d1c75dd5bc06 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -88,7 +88,7 @@ def populate(self): for app_name in settings.INSTALLED_APPS: if app_name in self.handled: continue - self.load_app(app_name, True) + self.load_app(app_name, can_postpone=True) if not self.nesting_level: for app_name in self.postponed: self.load_app(app_name) @@ -115,10 +115,10 @@ def load_app(self, app_name, can_postpone=False): models_module = 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 the app doesn't have a models module, we can just swallow the + # ImportError and return no models for this app. if not module_has_submodule(app_module, MODELS_MODULE_NAME): - return None + models_module = 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 @@ -129,7 +129,7 @@ def load_app(self, app_name, can_postpone=False): else: if can_postpone: self.postponed.append(app_name) - return None + return else: raise @@ -154,22 +154,27 @@ def app_cache_ready(self): """ return self.loaded - def get_app_configs(self, only_installed=True): + def get_app_configs(self, only_installed=True, only_with_models_module=False): """ Return an iterable of application configurations. If only_installed is True (default), only applications explicitly listed in INSTALLED_APPS are considered. + + If only_with_models_module in True (non-default), only applications + containing a models module are considered. """ self.populate() for app_config in self.app_configs.values(): if only_installed and not app_config.installed: continue + if only_with_models_module and app_config.models_module is None: + continue if self.available_apps is not None and app_config.name not in self.available_apps: continue yield app_config - def get_app_config(self, app_label, only_installed=True): + def get_app_config(self, app_label, only_installed=True, only_with_models_module=False): """ Returns the application configuration for the given app_label. @@ -180,11 +185,18 @@ def get_app_config(self, app_label, only_installed=True): If only_installed is True (default), only applications explicitly listed in INSTALLED_APPS are considered. + + If only_with_models_module in True (non-default), only applications + containing a models module are considered. """ self.populate() app_config = self.app_configs.get(app_label) - if app_config is None or (only_installed and not app_config.installed): + if app_config is None: raise LookupError("No app with label %r." % app_label) + if only_installed and not app_config.installed: + raise LookupError("App with label %r isn't in INSTALLED_APPS." % app_label) + if only_with_models_module and app_config.models_module is None: + raise LookupError("App with label %r doesn't have a models module." % app_label) if self.available_apps is not None and app_config.name not in self.available_apps: raise UnavailableApp("App with label %r isn't available." % app_label) return app_config diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index dfe5bb3c65bf5..35a8a7fdc876c 100644 --- a/django/contrib/contenttypes/management.py +++ b/django/contrib/contenttypes/management.py @@ -86,7 +86,7 @@ def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, * def update_all_contenttypes(verbosity=2, **kwargs): - for app_config in app_cache.get_app_configs(): + for app_config in app_cache.get_app_configs(only_with_models_module=True): update_contenttypes(app_config.models_module, None, verbosity, **kwargs) signals.post_migrate.connect(update_contenttypes) diff --git a/django/core/management/base.py b/django/core/management/base.py index c7d9c939e6026..4e95ba82985be 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -345,12 +345,16 @@ def handle(self, *app_labels, **options): if not app_labels: raise CommandError('Enter at least one appname.') try: - app_list = [app_cache.get_app_config(app_label).models_module for app_label in app_labels] + app_configs = [app_cache.get_app_config(app_label) for app_label in app_labels] except (LookupError, ImportError) as e: raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e) output = [] - for app in app_list: - app_output = self.handle_app(app, **options) + for app_config in app_configs: + if app_config.models_module is None: + raise CommandError( + "AppCommand cannot handle app %r because it doesn't have " + "a models module." % app_config.label) + app_output = self.handle_app(app_config.models_module, **options) if app_output: output.append(app_output) return '\n'.join(output) diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index af8da18322ab3..9aebb6c7d634a 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -70,7 +70,8 @@ def handle(self, *app_labels, **options): else: try: app_obj = app_cache.get_app_config(exclude).models_module - excluded_apps.add(app_obj) + if app_obj is not None: + excluded_apps.add(app_obj) except LookupError: raise CommandError('Unknown app in excludes: %s' % exclude) @@ -78,7 +79,7 @@ def handle(self, *app_labels, **options): if primary_keys: raise CommandError("You can only use --pks option with one model") app_list = OrderedDict((app_config.models_module, None) - for app_config in app_cache.get_app_configs() + for app_config in app_cache.get_app_configs(only_with_models_module=True) if app_config.models_module not in excluded_apps) else: if len(app_labels) > 1 and primary_keys: @@ -91,7 +92,7 @@ def handle(self, *app_labels, **options): app = app_cache.get_app_config(app_label).models_module except LookupError: raise CommandError("Unknown application: %s" % app_label) - if app in excluded_apps: + if app is None or app in excluded_apps: continue model = app_cache.get_model(app_label, model_label) if model is None: @@ -111,7 +112,7 @@ def handle(self, *app_labels, **options): app = app_cache.get_app_config(app_label).models_module except LookupError: raise CommandError("Unknown application: %s" % app_label) - if app in excluded_apps: + if app is None or app in excluded_apps: continue app_list[app] = None diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 0701bf82a9b02..d35a1efa5a06a 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -94,6 +94,6 @@ def emit_post_migrate(verbosity, interactive, database): # Emit the post migrate signal. This allows individual applications to # respond as if the database had been migrated from scratch. all_models = [] - for app_config in app_cache.get_app_configs(): + for app_config in app_cache.get_app_configs(only_with_models_module=True): all_models.extend(router.get_migratable_models(app_config.models_module, database, include_auto_created=True)) emit_post_migrate_signal(set(all_models), verbosity, interactive, database) diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index ce5ffae4a5a18..a6b9677d2e7c4 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -182,7 +182,7 @@ def sync_apps(self, connection, apps): all_models = [ (app_config.label, router.get_migratable_models(app_config.models_module, connection.alias, include_auto_created=True)) - for app_config in app_cache.get_app_configs() + for app_config in app_cache.get_app_configs(only_with_models_module=True) if app_config.label in apps ] diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 909faf011714d..b2500d3787df3 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -207,7 +207,7 @@ def custom_sql_for_model(model, style, connection): def emit_pre_migrate_signal(create_models, verbosity, interactive, db): # Emit the pre_migrate signal for every application. - for app_config in app_cache.get_app_configs(): + for app_config in app_cache.get_app_configs(only_with_models_module=True): if verbosity >= 2: print("Running pre-migrate handlers for application %s" % app_config.label) models.signals.pre_migrate.send( @@ -221,7 +221,7 @@ def emit_pre_migrate_signal(create_models, verbosity, interactive, db): def emit_post_migrate_signal(created_models, verbosity, interactive, db): # Emit the post_migrate signal for every application. - for app_config in app_cache.get_app_configs(): + for app_config in app_cache.get_app_configs(only_with_models_module=True): if verbosity >= 2: print("Running post-migrate handlers for application %s" % app_config.label) models.signals.post_migrate.send( diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 6e5d03ff96f23..86905afe77e2d 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -1271,7 +1271,7 @@ def django_table_names(self, only_existing=False): from django.apps import app_cache from django.db import router tables = set() - for app_config in app_cache.get_app_configs(): + for app_config in app_cache.get_app_configs(only_with_models_module=True): for model in router.get_migratable_models(app_config.models_module, self.connection.alias): if not model._meta.managed: continue @@ -1292,7 +1292,7 @@ def installed_models(self, tables): from django.apps import app_cache from django.db import router all_models = [] - for app_config in app_cache.get_app_configs(): + for app_config in app_cache.get_app_configs(only_with_models_module=True): all_models.extend(router.get_migratable_models(app_config.models_module, self.connection.alias)) tables = list(map(self.table_name_converter, tables)) return set([ @@ -1307,7 +1307,7 @@ def sequence_list(self): sequence_list = [] - for app_config in app_cache.get_app_configs(): + for app_config in app_cache.get_app_configs(only_with_models_module=True): for model in router.get_migratable_models(app_config.models_module, self.connection.alias): if not model._meta.managed: continue diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py index af311fdf02901..9a54f14e75cf8 100644 --- a/django/db/migrations/loader.py +++ b/django/db/migrations/loader.py @@ -55,7 +55,7 @@ def load_disk(self): self.disk_migrations = {} self.unmigrated_apps = set() self.migrated_apps = set() - for app_config in app_cache.get_app_configs(): + for app_config in app_cache.get_app_configs(only_with_models_module=True): # Get the migrations module directory module_name = self.migrations_module(app_config.label) was_loaded = module_name in sys.modules diff --git a/tests/empty/no_models/tests.py b/tests/empty/no_models/tests.py deleted file mode 100644 index 8b8db1af39e3f..0000000000000 --- a/tests/empty/no_models/tests.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.test import TestCase - - -class NoModelTests(TestCase): - """ A placeholder test case. See empty.tests for more info. """ - pass diff --git a/tests/empty/tests.py b/tests/empty/tests.py index 9fcd7a978ee3c..7cebb87c2a368 100644 --- a/tests/empty/tests.py +++ b/tests/empty/tests.py @@ -1,7 +1,4 @@ -from django.apps import app_cache from django.test import TestCase -from django.test.utils import override_settings -from django.utils import six from .models import Empty @@ -16,20 +13,3 @@ def test_empty(self): self.assertTrue(m.id is not None) existing = Empty(m.id) existing.save() - - -class NoModelTests(TestCase): - """ - Test for #7198 to ensure that the proper error message is raised - when attempting to load an app with no models.py file. - - Because the test runner won't currently load a test module with no - models.py file, this TestCase instead lives in this module. - - It seemed like an appropriate home for it. - """ - @override_settings(INSTALLED_APPS=("empty.no_models",)) - def test_no_models(self): - with six.assertRaisesRegex(self, LookupError, - "No app with label 'no_models'."): - app_cache.get_app_config('no_models') diff --git a/tests/no_models/__init__.py b/tests/no_models/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/no_models/tests.py b/tests/no_models/tests.py new file mode 100644 index 0000000000000..f9ff80485e60d --- /dev/null +++ b/tests/no_models/tests.py @@ -0,0 +1,10 @@ +from django.apps import app_cache +from django.test import TestCase + + +class NoModelTests(TestCase): + + def test_no_models(self): + """Test that it's possible to load an app with no models.py file.""" + app_config = app_cache.get_app_config('no_models') + self.assertIsNone(app_config.models_module) From 581faf79f4a1c49eec141de1a8e18f9076f9c212 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 14 Dec 2013 20:36:22 +0100 Subject: [PATCH 18/22] Refactored old test runner to handle apps without a models module. --- django/test/simple.py | 86 ++++++++++++++---------------- tests/test_runner/tests.py | 23 ++++---- tests/test_suite_override/tests.py | 4 +- 3 files changed, 57 insertions(+), 56 deletions(-) diff --git a/django/test/simple.py b/django/test/simple.py index 8ea1d6ac539c9..f2d26376dff3e 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -96,22 +96,13 @@ def __init__(self, *args, **kwargs): doctestOutputChecker = OutputChecker() -def get_tests(app_module): - parts = app_module.__name__.split('.') - prefix, last = parts[:-1], parts[-1] +def get_tests(app_config): try: - test_module = import_module('.'.join(prefix + [TEST_MODULE])) + test_module = import_module('%s.%s' % (app_config.name, TEST_MODULE)) except ImportError: # Couldn't import tests.py. Was it due to a missing file, or # due to an import error in a tests.py that actually exists? - # app_module either points to a models.py file, or models/__init__.py - # Tests are therefore either in same directory, or one level up - if last == 'models': - app_root = import_module('.'.join(prefix)) - else: - app_root = app_module - - if not module_has_submodule(app_root, TEST_MODULE): + if not module_has_submodule(app_config.app_module, TEST_MODULE): test_module = None else: # The module exists, so there must be an import error in the test @@ -126,7 +117,7 @@ def make_doctest(module): runner=DocTestRunner) -def build_suite(app_module): +def build_suite(app_config): """ Create a complete Django test suite for the provided application module. """ @@ -134,30 +125,32 @@ def build_suite(app_module): # Load unit and doctests in the models.py module. If module has # a suite() method, use it. Otherwise build the test suite ourselves. - if hasattr(app_module, 'suite'): - suite.addTest(app_module.suite()) - else: - suite.addTest(unittest.defaultTestLoader.loadTestsFromModule( - app_module)) - try: - suite.addTest(make_doctest(app_module)) - except ValueError: - # No doc tests in models.py - pass + models_module = app_config.models_module + if models_module: + if hasattr(models_module, 'suite'): + suite.addTest(models_module.suite()) + else: + suite.addTest(unittest.defaultTestLoader.loadTestsFromModule( + models_module)) + try: + suite.addTest(make_doctest(models_module)) + except ValueError: + # No doc tests in models.py + pass # Check to see if a separate 'tests' module exists parallel to the # models module - test_module = get_tests(app_module) - if test_module: + tests_module = get_tests(app_config) + if tests_module: # Load unit and doctests in the tests.py module. If module has # a suite() method, use it. Otherwise build the test suite ourselves. - if hasattr(test_module, 'suite'): - suite.addTest(test_module.suite()) + if hasattr(tests_module, 'suite'): + suite.addTest(tests_module.suite()) else: suite.addTest(unittest.defaultTestLoader.loadTestsFromModule( - test_module)) + tests_module)) try: - suite.addTest(make_doctest(test_module)) + suite.addTest(make_doctest(tests_module)) except ValueError: # No doc tests in tests.py pass @@ -167,26 +160,29 @@ def build_suite(app_module): def build_test(label): """ Construct a test case with the specified label. Label should be of the - form model.TestClass or model.TestClass.test_method. Returns an + form app_label.TestClass or app_label.TestClass.test_method. Returns an instantiated test or test suite corresponding to the label provided. - """ parts = label.split('.') if len(parts) < 2 or len(parts) > 3: raise ValueError("Test label '%s' should be of the form app.TestCase " "or app.TestCase.test_method" % label) - # - # First, look for TestCase instances with a name that matches - # - app_module = app_cache.get_app_config(parts[0]).models_module - test_module = get_tests(app_module) - TestClass = getattr(app_module, parts[1], None) + app_config = app_cache.get_app_config(parts[0]) + models_module = app_config.models_module + tests_module = get_tests(app_config) + + test_modules = [] + if models_module: + test_modules.append(models_module) + if tests_module: + test_modules.append(tests_module) - # Couldn't find the test class in models.py; look in tests.py - if TestClass is None: - if test_module: - TestClass = getattr(test_module, parts[1], None) + TestClass = None + for module in test_modules: + TestClass = getattr(models_module, parts[1], None) + if TestClass is not None: + break try: if issubclass(TestClass, (unittest.TestCase, real_unittest.TestCase)): @@ -208,7 +204,7 @@ def build_test(label): # If there isn't a TestCase, look for a doctest that matches # tests = [] - for module in app_module, test_module: + for module in test_modules: try: doctests = make_doctest(module) # Now iterate over the suite, looking for doctests whose name @@ -241,11 +237,11 @@ def build_suite(self, test_labels, extra_tests=None, **kwargs): if '.' in label: suite.addTest(build_test(label)) else: - app = app_cache.get_app_config(label).models_module - suite.addTest(build_suite(app)) + app_config = app_cache.get_app_config(label) + suite.addTest(build_suite(app_config)) else: for app_config in app_cache.get_app_configs(): - suite.addTest(build_suite(app_config.models_module)) + suite.addTest(build_suite(app_config)) if extra_tests: for test in extra_tests: diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py index 8b9dbe3faec20..03cde80aab78c 100644 --- a/tests/test_runner/tests.py +++ b/tests/test_runner/tests.py @@ -5,6 +5,7 @@ from importlib import import_module from optparse import make_option +import types import unittest from django.core.exceptions import ImproperlyConfigured @@ -18,10 +19,6 @@ from .models import Person -TEST_APP_OK = 'test_runner.valid_app.models' -TEST_APP_ERROR = 'test_runner_invalid_app.models' - - class DependencyOrderingTests(unittest.TestCase): def test_simple_dependencies(self): @@ -228,16 +225,24 @@ class ModulesTestsPackages(IgnoreAllDeprecationWarningsMixin, unittest.TestCase) def test_get_tests(self): "Check that the get_tests helper function can find tests in a directory" + from django.apps.base import AppConfig from django.test.simple import get_tests - module = import_module(TEST_APP_OK) - tests = get_tests(module) - self.assertIsInstance(tests, type(module)) + app_config = AppConfig( + 'test_runner.valid_app', + import_module('test_runner.valid_app'), + import_module('test_runner.valid_app.models')) + tests = get_tests(app_config) + self.assertIsInstance(tests, types.ModuleType) def test_import_error(self): "Test for #12658 - Tests with ImportError's shouldn't fail silently" + from django.apps.base import AppConfig from django.test.simple import get_tests - module = import_module(TEST_APP_ERROR) - self.assertRaises(ImportError, get_tests, module) + app_config = AppConfig( + 'test_runner_invalid_app', + import_module('test_runner_invalid_app'), + import_module('test_runner_invalid_app.models')) + self.assertRaises(ImportError, get_tests, app_config) class Sqlite3InMemoryTestDbs(TestCase): diff --git a/tests/test_suite_override/tests.py b/tests/test_suite_override/tests.py index 5c5e433dbc339..9666df1c19477 100644 --- a/tests/test_suite_override/tests.py +++ b/tests/test_suite_override/tests.py @@ -20,8 +20,8 @@ def test_suite_override(self): """ from django.test.simple import build_suite - app = app_cache.get_app_config("test_suite_override").models_module - suite = build_suite(app) + app_config = app_cache.get_app_config("test_suite_override") + suite = build_suite(app_config) self.assertEqual(suite.countTestCases(), 1) From 20fc3fe391c65424d046abd1971cfdf80f679da4 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 15 Dec 2013 11:29:24 +0100 Subject: [PATCH 19/22] Fixed incorrect decrementation of nesting_level. Thanks Florian for catching this mistake. --- django/apps/cache.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index 4d1c75dd5bc06..e91804d6ac650 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -114,7 +114,6 @@ def load_app(self, app_name, can_postpone=False): try: models_module = 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 swallow the # ImportError and return no models for this app. if not module_has_submodule(app_module, MODELS_MODULE_NAME): @@ -132,8 +131,8 @@ def load_app(self, app_name, can_postpone=False): return else: raise - - self.nesting_level -= 1 + finally: + self.nesting_level -= 1 app_config = AppConfig( name=app_name, app_module=app_module, models_module=models_module) From 9cee160c6107e654f0f583e365aec34b196b2781 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 15 Dec 2013 20:46:46 +0100 Subject: [PATCH 20/22] Inlined trivial method that was used only once. --- django/apps/cache.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/django/apps/cache.py b/django/apps/cache.py index e91804d6ac650..9b847cc3091bd 100644 --- a/django/apps/cache.py +++ b/django/apps/cache.py @@ -96,13 +96,6 @@ def populate(self): 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 @@ -240,7 +233,7 @@ def get_models(self, app_mod=None, pass self.populate() if app_mod: - app_label = self._label_for(app_mod) + app_label = app_mod.__name__.split('.')[-2] try: app_config = self.app_configs[app_label] except KeyError: From c561545d5d3bd9649ebc2e667db5b677e3bc182f Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 16 Dec 2013 11:34:10 +0100 Subject: [PATCH 21/22] Restored deprecated aliases for functions in django.db.models. Thanks Marc Tamlyn for the suggestion. --- django/db/models/__init__.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 265550fcce0b8..1459cb318d01c 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -1,4 +1,6 @@ from functools import wraps +import sys +import warnings from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured # NOQA from django.db.models.query import Q, QuerySet, Prefetch # NOQA @@ -35,3 +37,26 @@ def inner(*args, **kwargs): bits = func(*args, **kwargs) return reverse(bits[0], None, *bits[1:3]) return inner + + +# Deprecated aliases for functions were exposed in this module. + +this_module = sys.modules['django.db.models'] + +def make_alias(function_name): + # Close function_name. + def alias(*args, **kwargs): + warnings.warn( + "django.db.models.%s is deprecated." % function_name, + PendingDeprecationWarning, stacklevel=2) + # This raises a second warning. + from . import loading + return getattr(loading, function_name)(*args, **kwargs) + alias.__name__ = function_name + return alias + +for function_name in ('get_apps', 'get_app_path', 'get_app_paths', 'get_app', + 'get_models', 'get_model', 'register_models'): + setattr(this_module, function_name, make_alias(function_name)) + +del this_module, make_alias, function_name From 0ccc7196f570f7e542c05b3d30963f77dbf7f210 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 16 Dec 2013 11:52:05 +0100 Subject: [PATCH 22/22] Moved the new app cache inside core. --- django/contrib/admin/validation.py | 2 +- django/contrib/admindocs/views.py | 2 +- django/contrib/auth/__init__.py | 2 +- django/contrib/auth/management/__init__.py | 2 +- django/contrib/auth/tests/test_management.py | 2 +- django/contrib/comments/views/comments.py | 2 +- django/contrib/contenttypes/management.py | 2 +- django/contrib/contenttypes/models.py | 2 +- django/contrib/gis/sitemaps/kml.py | 2 +- django/contrib/gis/sitemaps/views.py | 2 +- django/{ => core}/apps/__init__.py | 0 django/{ => core}/apps/base.py | 0 django/{ => core}/apps/cache.py | 0 django/core/checks/compatibility/django_1_6_0.py | 2 +- django/core/management/base.py | 2 +- django/core/management/commands/dumpdata.py | 4 ++-- django/core/management/commands/flush.py | 2 +- django/core/management/commands/loaddata.py | 2 +- django/core/management/commands/makemigrations.py | 2 +- django/core/management/commands/migrate.py | 2 +- django/core/management/commands/shell.py | 2 +- django/core/management/commands/sqlsequencereset.py | 2 +- django/core/management/sql.py | 2 +- django/core/management/validation.py | 2 +- django/core/serializers/base.py | 2 +- django/core/serializers/python.py | 2 +- django/core/serializers/xml_serializer.py | 2 +- django/db/backends/__init__.py | 6 +++--- django/db/backends/sqlite3/schema.py | 2 +- django/db/migrations/loader.py | 2 +- django/db/migrations/questioner.py | 2 +- django/db/migrations/recorder.py | 2 +- django/db/migrations/state.py | 2 +- django/db/migrations/writer.py | 2 +- django/db/models/base.py | 4 ++-- django/db/models/fields/__init__.py | 2 +- django/db/models/loading.py | 2 +- django/db/models/options.py | 2 +- django/db/models/signals.py | 2 +- django/db/utils.py | 2 +- django/test/simple.py | 2 +- django/test/testcases.py | 2 +- tests/app_cache/models.py | 2 +- tests/app_cache/tests.py | 4 ++-- tests/app_loading/tests.py | 4 ++-- tests/commands_sql/tests.py | 2 +- tests/contenttypes_tests/tests.py | 2 +- tests/defer_regress/tests.py | 2 +- tests/invalid_models/tests.py | 2 +- tests/managers_regress/tests.py | 2 +- tests/migrations/models.py | 2 +- tests/migrations/test_commands.py | 2 +- tests/migrations/test_state.py | 2 +- tests/migrations/test_writer.py | 2 +- tests/no_models/tests.py | 2 +- tests/proxy_model_inheritance/tests.py | 2 +- tests/proxy_models/tests.py | 2 +- tests/runtests.py | 4 ++-- tests/schema/models.py | 2 +- tests/swappable_models/tests.py | 2 +- tests/tablespaces/tests.py | 2 +- tests/test_runner/tests.py | 4 ++-- tests/test_suite_override/tests.py | 2 +- tests/validation/test_unique.py | 2 +- 64 files changed, 69 insertions(+), 69 deletions(-) rename django/{ => core}/apps/__init__.py (100%) rename django/{ => core}/apps/base.py (100%) rename django/{ => core}/apps/cache.py (100%) diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index 79fa9c672c600..a15967e024a2a 100644 --- a/django/contrib/admin/validation.py +++ b/django/contrib/admin/validation.py @@ -1,4 +1,4 @@ -from django.apps import app_cache +from django.core.apps import app_cache from django.core.exceptions import ImproperlyConfigured from django.db import models from django.db.models.fields import FieldDoesNotExist diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index c52e0f0d7f57d..dee75fa678bde 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -4,10 +4,10 @@ import re from django import template -from django.apps import app_cache from django.conf import settings from django.contrib import admin from django.contrib.admin.views.decorators import staff_member_required +from django.core.apps import app_cache from django.db import models from django.core.exceptions import ViewDoesNotExist from django.http import Http404 diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index 3c5a40c18485c..d5fc14dd2a60d 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -123,7 +123,7 @@ def get_user_model(): """ Returns the User model that is active in this project. """ - from django.apps import app_cache + from django.core.apps import app_cache try: app_label, model_name = settings.AUTH_USER_MODEL.split('.') diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index 5f24bf069e6bb..ec963becc47a1 100644 --- a/django/contrib/auth/management/__init__.py +++ b/django/contrib/auth/management/__init__.py @@ -6,9 +6,9 @@ import getpass import unicodedata -from django.apps import app_cache, UnavailableApp from django.contrib.auth import (models as auth_app, get_permission_codename, get_user_model) +from django.core.apps import app_cache, UnavailableApp from django.core import exceptions from django.core.management.base import CommandError from django.db import DEFAULT_DB_ALIAS, router diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py index c005e74442eef..d9de5a2ad0984 100644 --- a/django/contrib/auth/tests/test_management.py +++ b/django/contrib/auth/tests/test_management.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals from datetime import date -from django.apps import app_cache from django.contrib.auth import models, management from django.contrib.auth.management import create_permissions from django.contrib.auth.management.commands import changepassword @@ -9,6 +8,7 @@ from django.contrib.auth.tests.custom_user import CustomUser from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.contenttypes.models import ContentType +from django.core.apps import app_cache from django.core import exceptions from django.core.management import call_command from django.core.management.base import CommandError diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py index 5d7c543adb8cc..294c7c8e42b72 100644 --- a/django/contrib/comments/views/comments.py +++ b/django/contrib/comments/views/comments.py @@ -1,9 +1,9 @@ from django import http -from django.apps import app_cache from django.conf import settings from django.contrib import comments from django.contrib.comments import signals from django.contrib.comments.views.utils import next_redirect, confirmation_view +from django.core.apps import app_cache from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import models from django.shortcuts import render_to_response diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index 35a8a7fdc876c..7ff08b70f8902 100644 --- a/django/contrib/contenttypes/management.py +++ b/django/contrib/contenttypes/management.py @@ -1,5 +1,5 @@ -from django.apps import app_cache, UnavailableApp from django.contrib.contenttypes.models import ContentType +from django.core.apps import app_cache, UnavailableApp from django.db import DEFAULT_DB_ALIAS, router from django.db.models import signals from django.utils.encoding import smart_text diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index 90dea5b8111a5..98eae0219c390 100644 --- a/django/contrib/contenttypes/models.py +++ b/django/contrib/contenttypes/models.py @@ -1,4 +1,4 @@ -from django.apps import app_cache +from django.core.apps import app_cache from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_text, force_text diff --git a/django/contrib/gis/sitemaps/kml.py b/django/contrib/gis/sitemaps/kml.py index 1e4fc82550f8c..aa9ae69f5d35e 100644 --- a/django/contrib/gis/sitemaps/kml.py +++ b/django/contrib/gis/sitemaps/kml.py @@ -1,4 +1,4 @@ -from django.apps import app_cache +from django.core.apps import app_cache from django.core import urlresolvers from django.contrib.sitemaps import Sitemap from django.contrib.gis.db.models.fields import GeometryField diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index e68523981e36a..e55a3716723e3 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -2,7 +2,7 @@ import warnings -from django.apps import app_cache +from django.core.apps import app_cache from django.http import HttpResponse, Http404 from django.template import loader from django.contrib.sites.models import get_current_site diff --git a/django/apps/__init__.py b/django/core/apps/__init__.py similarity index 100% rename from django/apps/__init__.py rename to django/core/apps/__init__.py diff --git a/django/apps/base.py b/django/core/apps/base.py similarity index 100% rename from django/apps/base.py rename to django/core/apps/base.py diff --git a/django/apps/cache.py b/django/core/apps/cache.py similarity index 100% rename from django/apps/cache.py rename to django/core/apps/cache.py diff --git a/django/core/checks/compatibility/django_1_6_0.py b/django/core/checks/compatibility/django_1_6_0.py index a42249de60581..fef8bc3a518be 100644 --- a/django/core/checks/compatibility/django_1_6_0.py +++ b/django/core/checks/compatibility/django_1_6_0.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from django.apps import app_cache +from django.core.apps import app_cache from django.db import models diff --git a/django/core/management/base.py b/django/core/management/base.py index 4e95ba82985be..aeb27e1f0bd9f 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -341,7 +341,7 @@ class AppCommand(BaseCommand): args = '' def handle(self, *app_labels, **options): - from django.apps import app_cache + from django.core.apps import app_cache if not app_labels: raise CommandError('Enter at least one appname.') try: diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 9aebb6c7d634a..fa657bcd72366 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -37,7 +37,7 @@ class Command(BaseCommand): args = '[appname appname.ModelName ...]' def handle(self, *app_labels, **options): - from django.apps import app_cache + from django.core.apps import app_cache format = options.get('format') indent = options.get('indent') @@ -162,7 +162,7 @@ def sort_dependencies(app_list): is serialized before a normal model, and any model with a natural key dependency has it's dependencies serialized first. """ - from django.apps import app_cache + from django.core.apps import app_cache # Process the list of models, and get the list of dependencies model_dependencies = [] models = set() diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index d35a1efa5a06a..562147e403812 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -2,8 +2,8 @@ from importlib import import_module from optparse import make_option -from django.apps import app_cache from django.conf import settings +from django.core.apps import app_cache from django.db import connections, router, transaction, DEFAULT_DB_ALIAS from django.core.management import call_command from django.core.management.base import NoArgsCommand, CommandError diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index bfeba68aa61fe..ee88232d9f010 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -7,8 +7,8 @@ import zipfile from optparse import make_option -from django.apps import app_cache from django.conf import settings +from django.core.apps import app_cache from django.core import serializers from django.core.management.base import BaseCommand, CommandError from django.core.management.color import no_style diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py index aaf02708401f6..3bd7ad42beb79 100644 --- a/django/core/management/commands/makemigrations.py +++ b/django/core/management/commands/makemigrations.py @@ -3,7 +3,7 @@ import operator from optparse import make_option -from django.apps import app_cache +from django.core.apps import app_cache from django.core.management.base import BaseCommand, CommandError from django.db import connections, DEFAULT_DB_ALIAS, migrations from django.db.migrations.loader import MigrationLoader diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index a6b9677d2e7c4..b607e8ee4383b 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -6,8 +6,8 @@ import itertools import traceback -from django.apps import app_cache from django.conf import settings +from django.core.apps import app_cache from django.core.management import call_command from django.core.management.base import BaseCommand, CommandError from django.core.management.color import no_style diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index 12af814161165..5c189ac980747 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -66,7 +66,7 @@ def run_shell(self, shell=None): def handle_noargs(self, **options): # XXX: (Temporary) workaround for ticket #1796: force early loading of all # models from installed apps. - from django.apps import app_cache + from django.core.apps import app_cache app_cache.get_models() use_plain = options.get('plain', False) diff --git a/django/core/management/commands/sqlsequencereset.py b/django/core/management/commands/sqlsequencereset.py index 24ec25e8ed564..cc35030a91c4e 100644 --- a/django/core/management/commands/sqlsequencereset.py +++ b/django/core/management/commands/sqlsequencereset.py @@ -2,7 +2,7 @@ from optparse import make_option -from django.apps import app_cache +from django.core.apps import app_cache from django.core.management.base import AppCommand from django.db import connections, DEFAULT_DB_ALIAS diff --git a/django/core/management/sql.py b/django/core/management/sql.py index b2500d3787df3..0b6e38124ea50 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -5,8 +5,8 @@ import re import warnings -from django.apps import app_cache from django.conf import settings +from django.core.apps import app_cache from django.core.management.base import CommandError from django.db import models, router diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 2b27a7edce3cb..1cedcb9925a04 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -26,7 +26,7 @@ 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 import app_cache + from django.core.apps import app_cache from django.db import connection, models from django.db.models.deletion import SET_NULL, SET_DEFAULT diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index 98fbb081c56e3..db4b79a0201b1 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -3,7 +3,7 @@ """ import warnings -from django.apps import app_cache +from django.core.apps import app_cache from django.db import models from django.utils import six diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 47edc802173dd..13d6f01a4e54d 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -5,8 +5,8 @@ """ from __future__ import unicode_literals -from django.apps import app_cache from django.conf import settings +from django.core.apps import app_cache from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS from django.utils.encoding import smart_text, is_protected_type diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index e9bea84bb1f7a..90ad2cf398f9f 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -4,8 +4,8 @@ from __future__ import unicode_literals -from django.apps import app_cache from django.conf import settings +from django.core.apps import app_cache from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS from django.utils.xmlutils import SimplerXMLGenerator diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 86905afe77e2d..adf3b236ac883 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -1268,7 +1268,7 @@ def django_table_names(self, only_existing=False): If only_existing is True, the resulting list will only include the tables that actually exist in the database. """ - from django.apps import app_cache + from django.core.apps import app_cache from django.db import router tables = set() for app_config in app_cache.get_app_configs(only_with_models_module=True): @@ -1289,7 +1289,7 @@ def django_table_names(self, only_existing=False): def installed_models(self, tables): "Returns a set of all models represented by the provided list of table names." - from django.apps import app_cache + from django.core.apps import app_cache from django.db import router all_models = [] for app_config in app_cache.get_app_configs(only_with_models_module=True): @@ -1302,7 +1302,7 @@ def installed_models(self, tables): def sequence_list(self): "Returns a list of information about all DB sequences for all models in all apps." - from django.apps import app_cache + from django.core.apps import app_cache from django.db import models, router sequence_list = [] diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py index 69e3b61b874a5..8314533bbaa19 100644 --- a/django/db/backends/sqlite3/schema.py +++ b/django/db/backends/sqlite3/schema.py @@ -1,4 +1,4 @@ -from django.apps.cache import BaseAppCache +from django.core.apps.cache import BaseAppCache from django.db.backends.schema import BaseDatabaseSchemaEditor from django.db.models.fields.related import ManyToManyField diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py index 9a54f14e75cf8..4c12e05add478 100644 --- a/django/db/migrations/loader.py +++ b/django/db/migrations/loader.py @@ -2,7 +2,7 @@ import os import sys -from django.apps import app_cache +from django.core.apps import app_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 4a860dcf7e0eb..8a11559993dce 100644 --- a/django/db/migrations/questioner.py +++ b/django/db/migrations/questioner.py @@ -2,7 +2,7 @@ import os import sys -from django.apps import app_cache +from django.core.apps import app_cache from django.utils import datetime_safe from django.utils.six.moves import input diff --git a/django/db/migrations/recorder.py b/django/db/migrations/recorder.py index bf1cd225c232a..1c714ca272128 100644 --- a/django/db/migrations/recorder.py +++ b/django/db/migrations/recorder.py @@ -1,4 +1,4 @@ -from django.apps.cache import BaseAppCache +from django.core.apps.cache import BaseAppCache from django.db import models from django.utils.timezone import now diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index 6aa44ba10832a..2102773b71893 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -1,4 +1,4 @@ -from django.apps.cache import BaseAppCache +from django.core.apps.cache import BaseAppCache from django.db import models from django.db.models.options import DEFAULT_NAMES, normalize_unique_together from django.utils import six diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py index a23a9b12539b4..04e00501f9bb6 100644 --- a/django/db/migrations/writer.py +++ b/django/db/migrations/writer.py @@ -5,7 +5,7 @@ import os import types -from django.apps import app_cache +from django.core.apps import app_cache from django.db import models from django.db.migrations.loader import MigrationLoader from django.utils.encoding import force_text diff --git a/django/db/models/base.py b/django/db/models/base.py index 1d514ae1e26d9..56973d4e382c0 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -5,8 +5,8 @@ from functools import update_wrapper from django.utils.six.moves import zip -from django.apps import app_cache -from django.apps.cache import MODELS_MODULE_NAME +from django.core.apps import app_cache +from django.core.apps.cache import 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, diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 830ff2efa2961..b24323d1cfd45 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -9,7 +9,7 @@ from base64 import b64decode, b64encode from itertools import tee -from django.apps import app_cache +from django.core.apps import app_cache from django.db import connection from django.db.models.query_utils import QueryWrapper from django.conf import settings diff --git a/django/db/models/loading.py b/django/db/models/loading.py index ad267d8462ebc..5745dbed4d4f5 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -1,6 +1,6 @@ import warnings -from django.apps import app_cache +from django.core.apps import app_cache warnings.warn( "The utilities in django.db.models.loading are deprecated " diff --git a/django/db/models/options.py b/django/db/models/options.py index 74090cb921d06..386e90b057eea 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -5,8 +5,8 @@ from bisect import bisect import warnings -from django.apps import app_cache from django.conf import settings +from django.core.apps import app_cache from django.db.models.fields.related import ManyToManyRel from django.db.models.fields import AutoField, FieldDoesNotExist from django.db.models.fields.proxy import OrderWrt diff --git a/django/db/models/signals.py b/django/db/models/signals.py index 8c835e5f5f478..a6822309a3987 100644 --- a/django/db/models/signals.py +++ b/django/db/models/signals.py @@ -1,6 +1,6 @@ from collections import defaultdict -from django.apps import app_cache +from django.core.apps import app_cache from django.dispatch import Signal from django.utils import six diff --git a/django/db/utils.py b/django/db/utils.py index 702b1b4ebc18a..4d53d252bfc13 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -282,6 +282,6 @@ def get_migratable_models(self, app, db, include_auto_created=False): """ Return app models allowed to be synchronized on provided db. """ - from django.apps import app_cache + from django.core.apps import app_cache return [model for model in app_cache.get_models(app, include_auto_created=include_auto_created) if self.allow_migrate(db, model)] diff --git a/django/test/simple.py b/django/test/simple.py index f2d26376dff3e..6129ce1305e8b 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -9,7 +9,7 @@ import unittest as real_unittest import warnings -from django.apps import app_cache +from django.core.apps import app_cache from django.test import _doctest as doctest from django.test import runner from django.test.utils import compare_xml, strip_quotes diff --git a/django/test/testcases.py b/django/test/testcases.py index 1480889565a17..13f56fa6cc135 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -15,9 +15,9 @@ from unittest import skipIf # NOQA: Imported here for backward compatibility from unittest.util import safe_repr -from django.apps import app_cache from django.conf import settings from django.core import mail +from django.core.apps import app_cache from django.core.exceptions import ValidationError, ImproperlyConfigured from django.core.handlers.wsgi import get_path_info, WSGIHandler from django.core.management import call_command diff --git a/tests/app_cache/models.py b/tests/app_cache/models.py index cc092390abd68..99f9f57b67d8a 100644 --- a/tests/app_cache/models.py +++ b/tests/app_cache/models.py @@ -1,4 +1,4 @@ -from django.apps.cache import BaseAppCache +from django.core.apps.cache import BaseAppCache from django.db import models # 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 484998232e8c7..29d5315de02b7 100644 --- a/tests/app_cache/tests.py +++ b/tests/app_cache/tests.py @@ -1,7 +1,7 @@ from __future__ import absolute_import -from django.apps import app_cache -from django.apps.cache import BaseAppCache +from django.core.apps import app_cache +from django.core.apps.cache import BaseAppCache from django.db import models from django.test import TestCase diff --git a/tests/app_loading/tests.py b/tests/app_loading/tests.py index 690956404035f..3cc0ffb36805a 100644 --- a/tests/app_loading/tests.py +++ b/tests/app_loading/tests.py @@ -4,8 +4,8 @@ import sys from unittest import TestCase -from django.apps import app_cache -from django.apps.cache import AppCache +from django.core.apps import app_cache +from django.core.apps.cache import AppCache from django.test.utils import override_settings from django.utils._os import upath diff --git a/tests/commands_sql/tests.py b/tests/commands_sql/tests.py index 1c2a7f0ffa19c..3088ea4d48975 100644 --- a/tests/commands_sql/tests.py +++ b/tests/commands_sql/tests.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from django.apps import app_cache +from django.core.apps import app_cache from django.core.management.color import no_style from django.core.management.sql import (sql_create, sql_delete, sql_indexes, sql_destroy_indexes, sql_all) diff --git a/tests/contenttypes_tests/tests.py b/tests/contenttypes_tests/tests.py index a56d19693308a..ea61e5e89362b 100644 --- a/tests/contenttypes_tests/tests.py +++ b/tests/contenttypes_tests/tests.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -from django.apps.cache import BaseAppCache from django.contrib.contenttypes.models import ContentType +from django.core.apps.cache import BaseAppCache from django.db import models from django.test import TestCase diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py index 442af9f7cf7d8..3e9651b2d283e 100644 --- a/tests/defer_regress/tests.py +++ b/tests/defer_regress/tests.py @@ -2,9 +2,9 @@ from operator import attrgetter -from django.apps import app_cache from django.contrib.contenttypes.models import ContentType from django.contrib.sessions.backends.db import SessionStore +from django.core.apps import app_cache from django.db.models import Count from django.test import TestCase from django.test.utils import override_settings diff --git a/tests/invalid_models/tests.py b/tests/invalid_models/tests.py index 860d5e23a6a67..712484c6112b4 100644 --- a/tests/invalid_models/tests.py +++ b/tests/invalid_models/tests.py @@ -1,7 +1,7 @@ import sys import unittest -from django.apps import app_cache +from django.core.apps import app_cache from django.core.management.validation import get_validation_errors 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 0c939bfd2ecdf..f2897862ec530 100644 --- a/tests/managers_regress/tests.py +++ b/tests/managers_regress/tests.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from django.apps import app_cache +from django.core.apps import app_cache from django.db import models from django.template import Context, Template from django.test import TestCase diff --git a/tests/migrations/models.py b/tests/migrations/models.py index 9726e4457a780..03390b3a06671 100644 --- a/tests/migrations/models.py +++ b/tests/migrations/models.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.apps.cache import BaseAppCache +from django.core.apps.cache import BaseAppCache from django.db import models from django.utils.encoding import python_2_unicode_compatible diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index a9503e94a874d..59cd56e78bf33 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -5,7 +5,7 @@ import os import shutil -from django.apps import app_cache +from django.core.apps import app_cache from django.core.management import call_command, CommandError from django.test.utils import override_settings from django.utils import six diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py index 13e575862dc37..206bf78b352b3 100644 --- a/tests/migrations/test_state.py +++ b/tests/migrations/test_state.py @@ -1,4 +1,4 @@ -from django.apps.cache import BaseAppCache +from django.core.apps.cache import BaseAppCache from django.db import models from django.db.migrations.state import ProjectState, ModelState, InvalidBasesError from django.test import TestCase diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index 960af90eaa000..76fe5b5a9e7d6 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -5,7 +5,7 @@ import datetime import os -from django.apps import app_cache +from django.core.apps import app_cache from django.core.validators import RegexValidator, EmailValidator from django.db import models, migrations from django.db.migrations.writer import MigrationWriter diff --git a/tests/no_models/tests.py b/tests/no_models/tests.py index f9ff80485e60d..34ef7244465b0 100644 --- a/tests/no_models/tests.py +++ b/tests/no_models/tests.py @@ -1,4 +1,4 @@ -from django.apps import app_cache +from django.core.apps import app_cache from django.test import TestCase diff --git a/tests/proxy_model_inheritance/tests.py b/tests/proxy_model_inheritance/tests.py index 4c6e8c433ea88..861ab4af170cc 100644 --- a/tests/proxy_model_inheritance/tests.py +++ b/tests/proxy_model_inheritance/tests.py @@ -3,8 +3,8 @@ import os import sys -from django.apps import app_cache from django.conf import settings +from django.core.apps import app_cache from django.core.management import call_command from django.test import TestCase, TransactionTestCase from django.test.utils import override_settings diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py index 0c643991a7270..3389f3597fd1d 100644 --- a/tests/proxy_models/tests.py +++ b/tests/proxy_models/tests.py @@ -1,9 +1,9 @@ from __future__ import unicode_literals -from django.apps import app_cache from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.core import management +from django.core.apps import app_cache from django.core.exceptions import FieldError from django.db import models, DEFAULT_DB_ALIAS from django.db.models import signals diff --git a/tests/runtests.py b/tests/runtests.py index bec2d22feaefd..d5b94064d5fd2 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -80,14 +80,14 @@ def get_test_modules(): def get_installed(): - from django.apps import app_cache + from django.core.apps import app_cache return [app_config.name for app_config in app_cache.get_app_configs()] def setup(verbosity, test_labels): import django - from django.apps import app_cache from django.conf import settings + from django.core.apps import app_cache 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 2df97b935cced..e1369bff05178 100644 --- a/tests/schema/models.py +++ b/tests/schema/models.py @@ -1,4 +1,4 @@ -from django.apps.cache import BaseAppCache +from django.core.apps.cache import BaseAppCache from django.db import models # Because we want to test creation and deletion of these as separate things, diff --git a/tests/swappable_models/tests.py b/tests/swappable_models/tests.py index 0beb95af3e1d5..fec9db5af8c10 100644 --- a/tests/swappable_models/tests.py +++ b/tests/swappable_models/tests.py @@ -2,9 +2,9 @@ from django.utils.six import StringIO -from django.apps import app_cache from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType +from django.core.apps import app_cache from django.core import management 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 a1e2673f38c83..4a62ad5a45590 100644 --- a/tests/tablespaces/tests.py +++ b/tests/tablespaces/tests.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -from django.apps import app_cache from django.conf import settings +from django.core.apps import app_cache from django.db import connection from django.core.management.color import no_style from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py index 03cde80aab78c..04fa3b8784da6 100644 --- a/tests/test_runner/tests.py +++ b/tests/test_runner/tests.py @@ -225,7 +225,7 @@ class ModulesTestsPackages(IgnoreAllDeprecationWarningsMixin, unittest.TestCase) def test_get_tests(self): "Check that the get_tests helper function can find tests in a directory" - from django.apps.base import AppConfig + from django.core.apps.base import AppConfig from django.test.simple import get_tests app_config = AppConfig( 'test_runner.valid_app', @@ -236,7 +236,7 @@ def test_get_tests(self): def test_import_error(self): "Test for #12658 - Tests with ImportError's shouldn't fail silently" - from django.apps.base import AppConfig + from django.core.apps.base import AppConfig from django.test.simple import get_tests app_config = AppConfig( 'test_runner_invalid_app', diff --git a/tests/test_suite_override/tests.py b/tests/test_suite_override/tests.py index 9666df1c19477..b464659275b27 100644 --- a/tests/test_suite_override/tests.py +++ b/tests/test_suite_override/tests.py @@ -1,6 +1,6 @@ import unittest -from django.apps import app_cache +from django.core.apps import app_cache from django.test.utils import IgnoreAllDeprecationWarningsMixin diff --git a/tests/validation/test_unique.py b/tests/validation/test_unique.py index 995949fed75a5..83863b19c6fd8 100644 --- a/tests/validation/test_unique.py +++ b/tests/validation/test_unique.py @@ -3,7 +3,7 @@ import datetime import unittest -from django.apps.cache import BaseAppCache +from django.core.apps.cache import BaseAppCache from django.core.exceptions import ValidationError from django.db import models from django.test import TestCase