From 65cd74be8e99d06c7861edc5050e34d6444e4d56 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 19 Dec 2013 15:57:23 +0100 Subject: [PATCH] Stopped iterating on INSTALLED_APPS. Used the app cache's get_app_configs() method instead. --- django/contrib/staticfiles/finders.py | 6 +++-- django/core/management/__init__.py | 20 +++++++++++----- django/core/management/commands/flush.py | 5 ++-- django/core/management/commands/migrate.py | 7 +++--- django/template/base.py | 8 +++++-- django/template/loaders/app_directories.py | 11 +++------ django/template/loaders/eggs.py | 7 +++--- django/utils/autoreload.py | 9 +++----- django/utils/module_loading.py | 9 ++++---- django/utils/translation/trans_real.py | 8 +++---- django/views/i18n.py | 6 ++++- tests/bash_completion/tests.py | 8 ++++--- tests/i18n/tests.py | 26 +++++++++++++++++---- tests/staticfiles_tests/test_liveserver.py | 3 +++ tests/template_tests/test_loaders.py | 27 +++++++++++----------- tests/template_tests/tests.py | 13 +++++++---- tests/utils_tests/test_module_loading.py | 9 ++++++-- tests/view_tests/tests/test_i18n.py | 16 ++++++------- 18 files changed, 116 insertions(+), 82 deletions(-) diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py index cf5eb4f85b..cadc8bffa7 100644 --- a/django/contrib/staticfiles/finders.py +++ b/django/contrib/staticfiles/finders.py @@ -2,6 +2,7 @@ from collections import OrderedDict import os from django.conf import settings +from django.core.apps import app_cache from django.core.exceptions import ImproperlyConfigured from django.core.files.storage import default_storage, Storage, FileSystemStorage from django.utils.functional import empty, LazyObject @@ -116,10 +117,11 @@ class AppDirectoriesFinder(BaseFinder): def __init__(self, apps=None, *args, **kwargs): # The list of apps that are handled self.apps = [] - # Mapping of app module paths to storage instances + # Mapping of app names to storage instances self.storages = OrderedDict() if apps is None: - apps = settings.INSTALLED_APPS + app_configs = app_cache.get_app_configs() + apps = [app_config.name for app_config in app_configs] for app in apps: app_storage = self.storage_class(app) if os.path.isdir(app_storage.location): diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 0b47e179db..a7d074e149 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -5,6 +5,7 @@ from optparse import OptionParser, NO_DEFAULT import os import sys +from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.management.base import BaseCommand, CommandError, handle_default_options from django.core.management.color import color_style @@ -106,13 +107,19 @@ def get_commands(): _commands = dict((name, 'django.core') for name in find_commands(__path__[0])) # Find the installed apps - from django.conf import settings try: - apps = settings.INSTALLED_APPS + settings.INSTALLED_APPS except ImproperlyConfigured: - # Still useful for commands that do not require functional settings, - # like startproject or help + # Still useful for commands that do not require functional + # settings, like startproject or help. apps = [] + else: + # Populate the app cache outside of the try/except block to avoid + # catching ImproperlyConfigured errors that aren't caused by the + # absence of a settings module. + from django.core.apps import app_cache + app_configs = app_cache.get_app_configs() + apps = [app_config.name for app_config in app_configs] # Find and load the management module for each installed app. for app_name in apps: @@ -339,9 +346,10 @@ class ManagementUtility(object): elif cwords[0] in ('dumpdata', 'sql', 'sqlall', 'sqlclear', 'sqlcustom', 'sqlindexes', 'sqlsequencereset', 'test'): try: - from django.conf import settings + from django.core.apps import app_cache + app_configs = app_cache.get_app_configs() # Get the last part of the dotted path as the app name. - options += [(a.split('.')[-1], 0) for a in settings.INSTALLED_APPS] + options += [(app_config.label, 0) for app_config in app_configs] except ImportError: # Fail silently if DJANGO_SETTINGS_MODULE isn't set. The # user will find out once they execute the command. diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 562147e403..f13e948ae5 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -2,7 +2,6 @@ import sys from importlib import import_module from optparse import make_option -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 @@ -42,9 +41,9 @@ class Command(NoArgsCommand): # Import the 'management' module within each installed app, to register # dispatcher events. - for app_name in settings.INSTALLED_APPS: + for app_config in app_cache.get_app_configs(): try: - import_module('.management', app_name) + import_module('.management', app_config.name) except ImportError: pass diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index b607e8ee43..c99f26aa0c 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -6,7 +6,6 @@ from importlib import import_module import itertools import traceback -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 @@ -47,9 +46,9 @@ class Command(BaseCommand): # Import the 'management' module within each installed app, to register # dispatcher events. - for app_name in settings.INSTALLED_APPS: - if module_has_submodule(import_module(app_name), "management"): - import_module('.management', app_name) + for app_config in app_cache.get_app_configs(): + if module_has_submodule(app_config.app_module, "management"): + import_module('.management', app_config.name) # Get the database we're operating from db = options.get('database') diff --git a/django/template/base.py b/django/template/base.py index c314637c42..7098ea60f8 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -6,6 +6,7 @@ from importlib import import_module from inspect import getargspec, getcallargs from django.conf import settings +from django.core.apps import app_cache from django.template.context import (BaseContext, Context, RequestContext, # NOQA: imported for backwards compatibility ContextPopException) from django.utils.itercompat import is_iterable @@ -1302,9 +1303,12 @@ def get_templatetags_modules(): # Populate list once per process. Mutate the local list first, and # then assign it to the global name to ensure there are no cases where # two threads try to populate it simultaneously. - for app_module in ['django'] + list(settings.INSTALLED_APPS): + + templatetags_modules_candidates = ['django.templatetags'] + templatetags_modules_candidates += ['%s.templatetags' % app_config.name + for app_config in app_cache.get_app_configs()] + for templatetag_module in templatetags_modules_candidates: try: - templatetag_module = '%s.templatetags' % app_module import_module(templatetag_module) _templatetags_modules.append(templatetag_module) except ImportError: diff --git a/django/template/loaders/app_directories.py b/django/template/loaders/app_directories.py index 4f8ddfccac..90baa34c7a 100644 --- a/django/template/loaders/app_directories.py +++ b/django/template/loaders/app_directories.py @@ -3,12 +3,11 @@ Wrapper for loading templates from "templates" directories in INSTALLED_APPS packages. """ -from importlib import import_module import os import sys from django.conf import settings -from django.core.exceptions import ImproperlyConfigured +from django.core.apps import app_cache from django.template.base import TemplateDoesNotExist from django.template.loader import BaseLoader from django.utils._os import safe_join @@ -18,12 +17,8 @@ from django.utils import six if six.PY2: fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() app_template_dirs = [] -for app in settings.INSTALLED_APPS: - try: - mod = import_module(app) - except ImportError as e: - raise ImproperlyConfigured('ImportError %s: %s' % (app, e.args[0])) - template_dir = os.path.join(os.path.dirname(mod.__file__), 'templates') +for app_config in app_cache.get_app_configs(): + template_dir = os.path.join(app_config.path, 'templates') if os.path.isdir(template_dir): if six.PY2: template_dir = template_dir.decode(fs_encoding) diff --git a/django/template/loaders/eggs.py b/django/template/loaders/eggs.py index 04e4f73b7b..e9b46b15de 100644 --- a/django/template/loaders/eggs.py +++ b/django/template/loaders/eggs.py @@ -7,6 +7,7 @@ except ImportError: resource_string = None from django.conf import settings +from django.core.apps import app_cache from django.template.base import TemplateDoesNotExist from django.template.loader import BaseLoader from django.utils import six @@ -23,12 +24,12 @@ class Loader(BaseLoader): """ if resource_string is not None: pkg_name = 'templates/' + template_name - for app in settings.INSTALLED_APPS: + for app_config in app_cache.get_app_configs(): try: - resource = resource_string(app, pkg_name) + resource = resource_string(app_config.name, pkg_name) except Exception: continue if six.PY2: resource = resource.decode(settings.FILE_CHARSET) - return (resource, 'egg:%s:%s' % (app, pkg_name)) + return (resource, 'egg:%s:%s' % (app_config.name, pkg_name)) raise TemplateDoesNotExist(template_name) diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index 1913317822..05ffb12f74 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -37,9 +37,8 @@ import time import traceback from django.conf import settings +from django.core.apps import app_cache from django.core.signals import request_finished -from django.utils._os import upath -from importlib import import_module try: from django.utils.six.moves import _thread as thread except ImportError: @@ -91,10 +90,8 @@ def gen_filenames(): basedirs = [os.path.join(os.path.dirname(os.path.dirname(__file__)), 'conf', 'locale'), 'locale'] - for appname in reversed(settings.INSTALLED_APPS): - app = import_module(appname) - basedirs.append(os.path.join(os.path.dirname(upath(app.__file__)), - 'locale')) + for app_config in reversed(list(app_cache.get_app_configs())): + basedirs.append(os.path.join(app_config.path, 'locale')) basedirs.extend(settings.LOCALE_PATHS) basedirs = [os.path.abspath(basedir) for basedir in basedirs if os.path.isdir(basedir)] diff --git a/django/utils/module_loading.py b/django/utils/module_loading.py index 0c6bad2a7b..8d95fc00a3 100644 --- a/django/utils/module_loading.py +++ b/django/utils/module_loading.py @@ -58,18 +58,17 @@ def autodiscover_modules(*args, **kwargs): registry. This register_to object must have a _registry instance variable to access it. """ - from django.conf import settings + from django.core.apps import app_cache register_to = kwargs.get('register_to') - for app in settings.INSTALLED_APPS: - mod = import_module(app) + for app_config in app_cache.get_app_configs(): # Attempt to import the app's module. try: if register_to: before_import_registry = copy.copy(register_to._registry) for module_to_search in args: - import_module('%s.%s' % (app, module_to_search)) + import_module('%s.%s' % (app_config.name, module_to_search)) except: # Reset the model registry to the state before the last import as # this import will have to reoccur on the next request and this @@ -81,7 +80,7 @@ def autodiscover_modules(*args, **kwargs): # Decide whether to bubble up this error. If the app just # doesn't have an admin module, we can ignore the error # attempting to import it, otherwise we want it to bubble up. - if module_has_submodule(mod, module_to_search): + if module_has_submodule(app_config.app_module, module_to_search): raise diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index e547800b92..3ed6371966 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -7,10 +7,10 @@ import os import re import sys import gettext as gettext_module -from importlib import import_module from threading import local import warnings +from django.core.apps import app_cache from django.dispatch import receiver from django.test.signals import setting_changed from django.utils.encoding import force_str, force_text @@ -179,10 +179,8 @@ def translation(language): res.merge(t) return res - for appname in reversed(settings.INSTALLED_APPS): - app = import_module(appname) - apppath = os.path.join(os.path.dirname(upath(app.__file__)), 'locale') - + for app_config in reversed(list(app_cache.get_app_configs())): + apppath = os.path.join(app_config.path, 'locale') if os.path.isdir(apppath): res = _merge(apppath) diff --git a/django/views/i18n.py b/django/views/i18n.py index 751905efaf..2f0c89a037 100644 --- a/django/views/i18n.py +++ b/django/views/i18n.py @@ -5,6 +5,7 @@ import gettext as gettext_module from django import http from django.conf import settings +from django.core.apps import app_cache from django.template import Context, Template from django.utils.translation import check_for_language, to_locale, get_language from django.utils.encoding import smart_text @@ -187,7 +188,10 @@ def render_javascript_catalog(catalog=None, plural=None): def get_javascript_catalog(locale, domain, packages): default_locale = to_locale(settings.LANGUAGE_CODE) - packages = [p for p in packages if p == 'django.conf' or p in settings.INSTALLED_APPS] + app_configs = app_cache.get_app_configs() + allowable_packages = set(app_config.name for app_config in app_configs) + allowable_packages.add('django.conf') + packages = [p for p in packages if p in allowable_packages] t = {} paths = [] en_selected = locale.startswith('en') diff --git a/tests/bash_completion/tests.py b/tests/bash_completion/tests.py index 6ff709809d..85fb58904c 100644 --- a/tests/bash_completion/tests.py +++ b/tests/bash_completion/tests.py @@ -5,7 +5,7 @@ import os import sys import unittest -from django.conf import settings +from django.core.apps import app_cache from django.core.management import ManagementUtility from django.utils.six import StringIO @@ -84,5 +84,7 @@ class BashCompletionTests(unittest.TestCase): "Application names will be autocompleted for an AppCommand" self._user_input('django-admin.py sqlall a') output = self._run_autocomplete() - app_labels = [name.split('.')[-1] for name in settings.INSTALLED_APPS] - self.assertEqual(output, sorted(label for label in app_labels if label.startswith('a'))) + a_labels = sorted(app_config.label + for app_config in app_cache.get_app_configs() + if app_config.label.startswith('a')) + self.assertEqual(output, a_labels) diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 77b7d18455..339317c541 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -9,6 +9,7 @@ import pickle from threading import local from django.conf import settings +from django.core.apps import app_cache from django.template import Template, Context from django.template.base import TemplateSyntaxError from django.test import TestCase, RequestFactory @@ -1035,11 +1036,29 @@ class ResolutionOrderI18NTests(TransRealMixin, TestCase): "translation of '%s'; the actual result is '%s'." % (msgstr, msgid, result))) -@override_settings(INSTALLED_APPS=['i18n.resolution'] + list(settings.INSTALLED_APPS)) class AppResolutionOrderI18NTests(ResolutionOrderI18NTests): def test_app_translation(self): - self.assertUgettext('Date/time', 'APP') + # This test relies on an implementation detail, namely the fact that + # _with_app adds the app at the list. Adjust the test if this changes. + + # Original translation. + self.assertUgettext('Date/time', 'Datum/Zeit') + + # Different translation. + with app_cache._with_app('i18n.resolution'): + self.flush_caches() + activate('de') + + # Doesn't work because it's added later in the list. + self.assertUgettext('Date/time', 'Datum/Zeit') + + with app_cache._without_app('admin'): + self.flush_caches() + activate('de') + + # Unless the original is removed from the list. + self.assertUgettext('Date/time', 'Datum/Zeit (APP)') @override_settings(LOCALE_PATHS=extended_locale_paths) @@ -1049,8 +1068,7 @@ class LocalePathsResolutionOrderI18NTests(ResolutionOrderI18NTests): self.assertUgettext('Time', 'LOCALE_PATHS') def test_locale_paths_override_app_translation(self): - extended_apps = list(settings.INSTALLED_APPS) + ['i18n.resolution'] - with self.settings(INSTALLED_APPS=extended_apps): + with app_cache._with_app('i18n.resolution'): self.assertUgettext('Time', 'LOCALE_PATHS') diff --git a/tests/staticfiles_tests/test_liveserver.py b/tests/staticfiles_tests/test_liveserver.py index 71f4bb89a1..6fa64240cc 100644 --- a/tests/staticfiles_tests/test_liveserver.py +++ b/tests/staticfiles_tests/test_liveserver.py @@ -86,6 +86,9 @@ class StaticLiveServerChecks(LiveServerBase): class StaticLiveServerView(LiveServerBase): + # The test is going to access a static file stored in this application. + available_apps = ['staticfiles_tests.apps.test'] + def urlopen(self, url): return urlopen(self.live_server_url + url) diff --git a/tests/template_tests/test_loaders.py b/tests/template_tests/test_loaders.py index 6e74477e83..551bd7fb72 100644 --- a/tests/template_tests/test_loaders.py +++ b/tests/template_tests/test_loaders.py @@ -20,6 +20,7 @@ except ImportError: pkg_resources = None +from django.core.apps import app_cache from django.template import TemplateDoesNotExist, Context from django.template.loaders.eggs import Loader as EggLoader from django.template import loader @@ -49,7 +50,6 @@ def create_egg(name, resources): @unittest.skipUnless(pkg_resources, 'setuptools is not installed') -@override_settings(INSTALLED_APPS=[]) class EggLoaderTest(TestCase): def setUp(self): # Defined here b/c at module scope we may not have pkg_resources @@ -78,29 +78,28 @@ class EggLoaderTest(TestCase): os.path.normcase('templates/x.txt'): StringIO("x"), }) - @override_settings(INSTALLED_APPS=['egg_empty']) def test_empty(self): "Loading any template on an empty egg should fail" - egg_loader = EggLoader() - self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html") + with app_cache._with_app('egg_empty'): + egg_loader = EggLoader() + self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html") - @override_settings(INSTALLED_APPS=['egg_1']) def test_non_existing(self): "Template loading fails if the template is not in the egg" - egg_loader = EggLoader() - self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html") + with app_cache._with_app('egg_1'): + egg_loader = EggLoader() + self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html") - @override_settings(INSTALLED_APPS=['egg_1']) def test_existing(self): "A template can be loaded from an egg" - egg_loader = EggLoader() - contents, template_name = egg_loader.load_template_source("y.html") - self.assertEqual(contents, "y") - self.assertEqual(template_name, "egg:egg_1:templates/y.html") + with app_cache._with_app('egg_1'): + egg_loader = EggLoader() + contents, template_name = egg_loader.load_template_source("y.html") + self.assertEqual(contents, "y") + self.assertEqual(template_name, "egg:egg_1:templates/y.html") - @override_settings(INSTALLED_APPS=[]) def test_not_installed(self): - "Loading an existent template from an egg not included in INSTALLED_APPS should fail" + "Loading an existent template from an egg not included in any app should fail" egg_loader = EggLoader() self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "y.html") diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py index 651d46d4f1..61a46a81f9 100644 --- a/tests/template_tests/tests.py +++ b/tests/template_tests/tests.py @@ -16,6 +16,7 @@ import unittest import warnings from django import template +from django.core.apps import app_cache from django.core import urlresolvers from django.template import (base as template_base, loader, Context, RequestContext, Template, TemplateSyntaxError) @@ -1873,24 +1874,26 @@ class TemplateTagLoading(TestCase): self.assertTrue('ImportError' in e.args[0]) self.assertTrue('Xtemplate' in e.args[0]) - @override_settings(INSTALLED_APPS=('tagsegg',)) def test_load_error_egg(self): ttext = "{% load broken_egg %}" egg_name = '%s/tagsegg.egg' % self.egg_dir sys.path.append(egg_name) - self.assertRaises(template.TemplateSyntaxError, template.Template, ttext) + with self.assertRaises(template.TemplateSyntaxError): + with app_cache._with_app('tagsegg'): + template.Template(ttext) try: - template.Template(ttext) + with app_cache._with_app('tagsegg'): + template.Template(ttext) except template.TemplateSyntaxError as e: self.assertTrue('ImportError' in e.args[0]) self.assertTrue('Xtemplate' in e.args[0]) - @override_settings(INSTALLED_APPS=('tagsegg',)) def test_load_working_egg(self): ttext = "{% load working_egg %}" egg_name = '%s/tagsegg.egg' % self.egg_dir sys.path.append(egg_name) - template.Template(ttext) + with app_cache._with_app('tagsegg'): + template.Template(ttext) class RequestContextTests(unittest.TestCase): diff --git a/tests/utils_tests/test_module_loading.py b/tests/utils_tests/test_module_loading.py index 5a7eadcedf..0c8dfe1e12 100644 --- a/tests/utils_tests/test_module_loading.py +++ b/tests/utils_tests/test_module_loading.py @@ -5,9 +5,9 @@ import sys import unittest from zipimport import zipimporter +from django.core.apps import app_cache from django.core.exceptions import ImproperlyConfigured from django.test import SimpleTestCase -from django.test.utils import override_settings from django.utils import six from django.utils.module_loading import autodiscover_modules, import_by_path, module_has_submodule from django.utils._os import upath @@ -135,9 +135,14 @@ class ModuleImportTestCase(unittest.TestCase): 'Should have more than the calling frame in the traceback.') -@override_settings(INSTALLED_APPS=('utils_tests.test_module',)) class AutodiscoverModulesTestCase(SimpleTestCase): + def setUp(self): + self._with_test_module = app_cache._begin_with_app('utils_tests.test_module') + + def tearDown(self): + app_cache._end_with_app(self._with_test_module) + def test_autodiscover_modules_found(self): autodiscover_modules('good_module') diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py index 42f82251f5..19bbde286c 100644 --- a/tests/view_tests/tests/test_i18n.py +++ b/tests/view_tests/tests/test_i18n.py @@ -5,6 +5,7 @@ from os import path import unittest from django.conf import settings +from django.core.apps import app_cache from django.core.urlresolvers import reverse from django.test import LiveServerTestCase, TestCase from django.test.utils import override_settings @@ -115,9 +116,8 @@ class JsI18NTests(TestCase): available. The Javascript i18n view must return a NON empty language catalog with the proper English translations. See #13726 for more details. """ - extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app0'] - with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps): - with override('en-us'): + with app_cache._with_app('view_tests.app0'): + with self.settings(LANGUAGE_CODE='fr'), override('en-us'): response = self.client.get('/views/jsi18n_english_translation/') self.assertContains(response, javascript_quote('this app0 string is to be translated')) @@ -144,9 +144,8 @@ class JsI18NTestsMultiPackage(TestCase): translations of multiple Python packages is requested. See #13388, #3594 and #13514 for more details. """ - extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app1', 'view_tests.app2'] - with self.settings(LANGUAGE_CODE='en-us', INSTALLED_APPS=extended_apps): - with override('fr'): + with app_cache._with_app('view_tests.app1'), app_cache._with_app('view_tests.app2'): + with self.settings(LANGUAGE_CODE='en-us'), override('fr'): response = self.client.get('/views/jsi18n_multi_packages1/') self.assertContains(response, javascript_quote('il faut traduire cette chaîne de caractères de app1')) @@ -155,9 +154,8 @@ class JsI18NTestsMultiPackage(TestCase): Similar to above but with neither default or requested language being English. """ - extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app3', 'view_tests.app4'] - with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps): - with override('es-ar'): + with app_cache._with_app('view_tests.app3'), app_cache._with_app('view_tests.app4'): + with self.settings(LANGUAGE_CODE='fr'), override('es-ar'): response = self.client.get('/views/jsi18n_multi_packages2/') self.assertContains(response, javascript_quote('este texto de app3 debe ser traducido'))