ソースを参照

Stopped iterating on INSTALLED_APPS.

Used the app cache's get_app_configs() method instead.
Aymeric Augustin 11 年 前
コミット
65cd74be8e

+ 4 - 2
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):

+ 14 - 6
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.

+ 2 - 3
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
 

+ 3 - 4
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')

+ 6 - 2
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:

+ 3 - 8
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)

+ 4 - 3
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)

+ 3 - 6
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)]

+ 4 - 5
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
 
 

+ 3 - 5
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)
 

+ 5 - 1
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')

+ 5 - 3
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)

+ 22 - 4
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')
 
 

+ 3 - 0
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)
 

+ 13 - 14
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")
 

+ 8 - 5
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):

+ 7 - 2
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')
 

+ 7 - 9
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'))