Code

Ticket #3591: app-loading.2.diff

File app-loading.2.diff, 101.5 KB (added by jezdez, 3 years ago)
Line 
1diff --git a/django/apps/__init__.py b/django/apps/__init__.py
2new file mode 100644
3index 0000000..ca60d0c
4--- /dev/null
5+++ b/django/apps/__init__.py
6@@ -0,0 +1,22 @@
7+from django.apps.base import App
8+from django.apps.cache import AppCache
9+
10+__all__ = (
11+    'App', 'find_app', 'get_apps', 'get_app', 'get_app_errors',
12+    'get_models', 'get_model', 'register_models', 'load_app',
13+    'app_cache_ready'
14+)
15+
16+cache = AppCache()
17+
18+# These methods were always module level, so are kept that way for backwards
19+# compatibility.
20+find_app = cache.find_app
21+get_apps = cache.get_apps
22+get_app = cache.get_app
23+get_app_errors = cache.get_app_errors
24+get_models = cache.get_models
25+get_model = cache.get_model
26+register_models = cache.register_models
27+load_app = cache.load_app
28+app_cache_ready = cache.app_cache_ready
29diff --git a/django/apps/base.py b/django/apps/base.py
30new file mode 100644
31index 0000000..354dfbf
32--- /dev/null
33+++ b/django/apps/base.py
34@@ -0,0 +1,67 @@
35+import re
36+import sys
37+
38+from django.apps.options import AppOptions, DEFAULT_NAMES
39+
40+module_name_re = re.compile(r'_([a-z])')
41+
42+
43+class AppBase(type):
44+    """
45+    Metaclass for all apps.
46+    """
47+    def __new__(cls, name, bases, attrs):
48+        super_new = super(AppBase, cls).__new__
49+        parents = [b for b in bases if isinstance(b, AppBase)]
50+        if not parents:
51+            # If this isn't a subclass of App, don't do anything special.
52+            return super_new(cls, name, bases, attrs)
53+        module = attrs.pop('__module__', None)
54+        new_class = super_new(cls, name, bases, {'__module__': module})
55+        attr_meta = attrs.pop('Meta', None)
56+        if not attr_meta:
57+            meta = getattr(new_class, 'Meta', None)
58+        else:
59+            meta = attr_meta
60+        app_name = attrs.pop('_name', None)
61+        if app_name is None:
62+            # Figure out the app_name by looking one level up.
63+            # For 'django.contrib.sites.app', this would be 'django.contrib.sites'
64+            app_module = sys.modules[new_class.__module__]
65+            app_name = app_module.__name__.rsplit('.', 1)[0]
66+        new_class.add_to_class('_meta', AppOptions(app_name, meta))
67+        # For easier Meta inheritance
68+        new_class.add_to_class('Meta', attr_meta)
69+        # Add all remaining attributes to the class.
70+        for obj_name, obj in attrs.items():
71+            new_class.add_to_class(obj_name, obj)
72+        return new_class
73+
74+    def add_to_class(cls, name, value):
75+        if hasattr(value, 'contribute_to_class'):
76+            value.contribute_to_class(cls, name)
77+        else:
78+            setattr(cls, name, value)
79+
80+
81+class App(object):
82+    """
83+    The base app class to be subclassed for own uses.
84+    """
85+    __metaclass__ = AppBase
86+
87+    def __init__(self, **options):
88+        for key, value in options.iteritems():
89+            if key in DEFAULT_NAMES:
90+                setattr(self._meta, key, value)
91+            else:
92+                setattr(self, key, value)
93+
94+    def __repr__(self):
95+        return '<App: %s>' % self._meta.name
96+
97+    @classmethod
98+    def from_name(cls, name):
99+        upper = lambda match: match.group(1).upper()
100+        cls_name = module_name_re.sub(upper, name.split('.')[-1])
101+        return type(cls_name[0].upper()+cls_name[1:], (cls,), {'_name': name})
102diff --git a/django/apps/cache.py b/django/apps/cache.py
103new file mode 100644
104index 0000000..4973e74
105--- /dev/null
106+++ b/django/apps/cache.py
107@@ -0,0 +1,351 @@
108+import sys
109+import os
110+import threading
111+
112+from django.conf import settings
113+from django.core.exceptions import ImproperlyConfigured
114+from django.utils.datastructures import SortedDict
115+from django.utils.importlib import import_module
116+from django.utils.module_loading import module_has_submodule
117+
118+from django.apps.base import App
119+from django.apps.signals import app_loaded, post_apps_loaded
120+
121+
122+def _initialize():
123+    """
124+    Returns a dictionary to be used as the initial value of the
125+    shared state of the app cache.
126+    """
127+    return {
128+        # list of loaded app instances
129+        'loaded_apps': [],
130+
131+        # Mapping of app_labels to a dictionary of model names to model code.
132+        'app_models': SortedDict(),
133+
134+        # -- Everything below here is only used when populating the cache --
135+        'loaded': False,
136+        'handled': [],
137+        'postponed': [],
138+        'nesting_level': 0,
139+        '_get_models_cache': {},
140+    }
141+
142+
143+class AppCache(object):
144+    """
145+    A cache that stores installed applications and their models. Used to
146+    provide reverse-relations and for app introspection (e.g. admin).
147+    """
148+    # Use the Borg pattern to share state between all instances. Details at
149+    # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531.
150+    __shared_state = dict(_initialize(), write_lock=threading.RLock())
151+
152+    def __init__(self):
153+        self.__dict__ = self.__shared_state
154+
155+    def _reset(self):
156+        """
157+        Resets the cache to its initial (unseeded) state
158+        """
159+        # remove imported model modules, so ModelBase.__new__ can register
160+        # them with the cache again
161+        for app, models in self.app_models.iteritems():
162+            for model in models.itervalues():
163+                module = model.__module__
164+                if module in sys.modules:
165+                    del sys.modules[module]
166+        self.__class__.__shared_state.update(_initialize())
167+
168+    def _reload(self):
169+        """
170+        Reloads the cache
171+        """
172+        self._reset()
173+        self._populate()
174+
175+    def _populate(self):
176+        """
177+        Fill in all the cache information. This method is threadsafe, in the
178+        sense that every caller will see the same state upon return, and if the
179+        cache is already initialised, it does no work.
180+        """
181+        if self.loaded:
182+            return
183+        self.write_lock.acquire()
184+        try:
185+            if self.loaded:
186+                return
187+            for app_name in settings.INSTALLED_APPS:
188+                if isinstance(app_name, (tuple, list)):
189+                    app_name, app_kwargs = app_name
190+                else:
191+                    app_kwargs = {}
192+                if app_name in self.handled:
193+                    continue
194+                self.load_app(app_name, app_kwargs, True)
195+            if not self.nesting_level:
196+                for app_name, app_kwargs in self.postponed:
197+                    self.load_app(app_name, app_kwargs)
198+                # assign models to app instances
199+                for app in self.loaded_apps:
200+                    parents = [p for p in app.__class__.mro()
201+                               if hasattr(p, '_meta')]
202+                    for parent in reversed(parents):
203+                        parent_models = self.app_models.get(parent._meta.label, {})
204+                        # update app_label and installed attribute of parent models
205+                        for model in parent_models.itervalues():
206+                            model._meta.app_label = app._meta.label
207+                            model._meta.installed = True
208+                        app._meta.models.update(parent_models)
209+
210+                # check if there is more than one app with the same
211+                # db_prefix attribute
212+                for app1 in self.loaded_apps:
213+                    for app2 in self.loaded_apps:
214+                        if (app1 != app2 and
215+                                app1._meta.db_prefix == app2._meta.db_prefix):
216+                            raise ImproperlyConfigured(
217+                                'The apps "%s" and "%s" have the same db_prefix "%s"'
218+                                % (app1, app2, app1._meta.db_prefix))
219+                self.loaded = True
220+                # send the post_apps_loaded signal
221+                post_apps_loaded.send(sender=self, apps=self.loaded_apps)
222+        finally:
223+            self.write_lock.release()
224+
225+    def get_app_class(self, app_name):
226+        """
227+        Returns an app class for the given app name, which can be a
228+        dotted path to an app class or a dotted app module path.
229+        """
230+        try:
231+            app_path, app_attr = app_name.rsplit('.', 1)
232+        except ValueError:
233+            # First, return a new app class for the given module if
234+            # it's one level module path that can't be rsplit (e.g. 'myapp')
235+            return App.from_name(app_name)
236+        try:
237+            # Secondly, try to import the module directly,
238+            # because it'll fail with a class path or a bad path
239+            app_module = import_module(app_path)
240+        except ImportError, e:
241+            raise ImproperlyConfigured(
242+                "Could not import app '%s': %s" % (app_path, e))
243+        else:
244+            # Thirdly, check if there is the submodule and fall back if yes
245+            # If not look for the app class and do some type checks
246+            if not module_has_submodule(app_module, app_attr):
247+                try:
248+                    app_class = getattr(app_module, app_attr)
249+                except AttributeError:
250+                    raise ImproperlyConfigured(
251+                        "Could not find app '%s' in "
252+                        "module '%s'" % (app_attr, app_path))
253+                else:
254+                    if not issubclass(app_class, App):
255+                        raise ImproperlyConfigured(
256+                            "App '%s' must be a subclass of "
257+                            "'django.apps.App'" % app_name)
258+                    return app_class
259+        return App.from_name(app_name)
260+
261+    def load_app(self, app_name, app_kwargs=None, can_postpone=False):
262+        """
263+        Loads the app with the provided fully qualified name, and returns the
264+        model module.
265+
266+        Keyword Arguments:
267+            app_name: fully qualified name (e.g. 'django.contrib.auth')
268+            can_postpone: If set to True and the import raises an ImportError
269+                the loading will be postponed and tried again when all other
270+                modules are loaded.
271+        """
272+        if app_kwargs is None:
273+            app_kwargs = {}
274+
275+        self.handled.append(app_name)
276+        self.nesting_level += 1
277+
278+        # check if an app instance with app_name already exists, if not
279+        # then create one
280+        app = self.find_app(app_name.split('.')[-1])
281+        if not app:
282+            app_class = self.get_app_class(app_name)
283+            app = app_class(**app_kwargs)
284+            self.loaded_apps.append(app)
285+            # Send the signal that the app has been loaded
286+            app_loaded.send(sender=app_class, app=app)
287+
288+        # import the app's models module and handle ImportErrors
289+        try:
290+            models = import_module(app._meta.models_path)
291+        except ImportError:
292+            self.nesting_level -= 1
293+            # If the app doesn't have a models module, we can just ignore the
294+            # ImportError and return no models for it.
295+            if not module_has_submodule(app._meta.module, 'models'):
296+                return None
297+            # But if the app does have a models module, we need to figure out
298+            # whether to suppress or propagate the error. If can_postpone is
299+            # True then it may be that the package is still being imported by
300+            # Python and the models module isn't available yet. So we add the
301+            # app to the postponed list and we'll try it again after all the
302+            # recursion has finished (in populate). If can_postpone is False
303+            # then it's time to raise the ImportError.
304+            else:
305+                if can_postpone:
306+                    self.postponed.append((app_name, app_kwargs))
307+                    return None
308+                else:
309+                    raise
310+
311+        self.nesting_level -= 1
312+        app._meta.models_module = models
313+        return models
314+
315+    def find_app(self, app_label):
316+        """
317+        Returns the app instance that matches the given label.
318+        """
319+        for app in self.loaded_apps:
320+            if app._meta.label == app_label:
321+                return app
322+
323+    def find_app_by_models_module(self, models_module):
324+        """
325+        Returns the app instance that matches the models module
326+        """
327+        for app in self.loaded_apps:
328+            if app._meta.models_module == models_module:
329+                return app
330+
331+    def app_cache_ready(self):
332+        """
333+        Returns true if the model cache is fully populated.
334+
335+        Useful for code that wants to cache the results of get_models() for
336+        themselves once it is safe to do so.
337+        """
338+        return self.loaded
339+
340+    def get_apps(self):
341+        """
342+        Returns a list of all models modules.
343+        """
344+        self._populate()
345+        return [app._meta.models_module for app in self.loaded_apps
346+                if app._meta.models_module]
347+
348+    def get_app(self, app_label, emptyOK=False):
349+        """
350+        Returns the module containing the models for the given app_label. If
351+        the app has no models in it and 'emptyOK' is True, returns None.
352+        """
353+        self._populate()
354+        app = self.find_app(app_label)
355+        if app:
356+            mod = app._meta.models_module
357+            if mod is None:
358+                if emptyOK:
359+                    return None
360+            else:
361+                return mod
362+        raise ImproperlyConfigured(
363+                "App with label %s could not be found" % app_label)
364+
365+    def get_app_errors(self):
366+        """
367+        Returns the map of known problems with the INSTALLED_APPS.
368+        """
369+        self._populate()
370+        errors = {}
371+        for app in self.loaded_apps:
372+            if app._meta.errors:
373+                errors.update({app._meta.label: app._meta.errors})
374+        return errors
375+
376+    def get_models(self, app_mod=None,
377+                   include_auto_created=False, include_deferred=False,
378+                   only_installed=True):
379+        """
380+        Given a module containing models, returns a list of the models.
381+        Otherwise returns a list of all installed models.
382+
383+        By default, auto-created models (i.e., m2m models without an
384+        explicit intermediate table) are not included. However, if you
385+        specify include_auto_created=True, they will be.
386+
387+        By default, models created to satisfy deferred attribute
388+        queries are *not* included in the list of models. However, if
389+        you specify include_deferred, they will be.
390+        """
391+        cache_key = (app_mod, include_auto_created, include_deferred,
392+                     only_installed)
393+        try:
394+            return self._get_models_cache[cache_key]
395+        except KeyError:
396+            pass
397+        self._populate()
398+        app_list = []
399+        if app_mod and only_installed:
400+            app_label = app_mod.__name__.split('.')[-2]
401+            app = self.find_app(app_label)
402+            if app:
403+                app_list = [app._meta.models]
404+        else:
405+            if only_installed:
406+                app_list = [app._meta.models for app in self.loaded_apps]
407+            else:
408+                app_list = self.app_models.itervalues()
409+        model_list = []
410+        for app in app_list:
411+            model_list.extend(
412+                model for model in app.values()
413+                if ((not model._deferred or include_deferred) and
414+                    (not model._meta.auto_created or include_auto_created))
415+            )
416+        self._get_models_cache[cache_key] = model_list
417+        return model_list
418+
419+    def get_model(self, app_label, model_name,
420+                  seed_cache=True, only_installed=True):
421+        """
422+        Returns the model matching the given app_label and case-insensitive
423+        model_name.
424+
425+        Returns None if no model is found.
426+        """
427+        if seed_cache:
428+            self._populate()
429+        if only_installed:
430+            app = self.find_app(app_label)
431+            if not app:
432+                return
433+            return app._meta.models.get(model_name.lower())
434+        return self.app_models.get(app_label, SortedDict()).get(model_name.lower())
435+
436+    def register_models(self, app_label, *models):
437+        """
438+        Register a set of models as belonging to an app.
439+        """
440+        app = self.find_app(app_label)
441+        for model in models:
442+            model_name = model._meta.object_name.lower()
443+            model_dict = self.app_models.setdefault(app_label, SortedDict())
444+            if model_name in model_dict:
445+                # The same model may be imported via different paths (e.g.
446+                # appname.models and project.appname.models). We use the source
447+                # filename as a means to detect identity.
448+                fname1 = os.path.abspath(sys.modules[model.__module__].__file__)
449+                fname2 = os.path.abspath(sys.modules[model_dict[model_name].__module__].__file__)
450+                # Since the filename extension could be .py the first time and
451+                # .pyc or .pyo the second time, ignore the extension when
452+                # comparing.
453+                if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
454+                    continue
455+            if app:
456+                app._meta.models[model_name] = model
457+            model_dict[model_name] = model
458+        self._get_models_cache.clear()
459diff --git a/django/apps/options.py b/django/apps/options.py
460new file mode 100644
461index 0000000..7a71552
462--- /dev/null
463+++ b/django/apps/options.py
464@@ -0,0 +1,50 @@
465+import re
466+import os
467+
468+from django.utils.datastructures import SortedDict
469+from django.utils.importlib import import_module
470+from django.utils.text import get_verbose_name
471+
472+DEFAULT_NAMES = ('verbose_name', 'db_prefix', 'models_path')
473+
474+
475+class AppOptions(object):
476+    def __init__(self, name, meta):
477+        self.name = name
478+        self.meta = meta
479+        self.errors = []
480+        self.models = SortedDict()
481+
482+    def contribute_to_class(self, cls, name):
483+        cls._meta = self
484+        # get the name from the path e.g. "auth" for "django.contrib.auth"
485+        self.label = self.name.split('.')[-1]
486+        self.models_module = None
487+        self.module = import_module(self.name)
488+        self.path = os.path.dirname(self.module.__file__)
489+        defaults = {
490+            'db_prefix': self.label,
491+            'models_path': '%s.models' % self.name,
492+            'verbose_name': get_verbose_name(self.label),
493+        }
494+        # Next, apply any overridden values from 'class Meta'.
495+        if self.meta:
496+            meta_attrs = self.meta.__dict__.copy()
497+            for name in self.meta.__dict__:
498+                # Ignore any private attributes that Django doesn't care about.
499+                if name.startswith('_'):
500+                    del meta_attrs[name]
501+            for attr_name in DEFAULT_NAMES:
502+                if attr_name in meta_attrs:
503+                    setattr(self, attr_name, meta_attrs.pop(attr_name))
504+                elif hasattr(self.meta, attr_name):
505+                    setattr(self, attr_name, getattr(self.meta, attr_name))
506+            # Any leftover attributes must be invalid.
507+            if meta_attrs != {}:
508+                raise TypeError("'class Meta' got invalid attribute(s): %s"
509+                                % ','.join(meta_attrs.keys()))
510+        del self.meta
511+
512+        for k, v in defaults.iteritems():
513+            if not hasattr(self, k):
514+                setattr(self, k, v)
515diff --git a/django/apps/signals.py b/django/apps/signals.py
516new file mode 100644
517index 0000000..befb77c
518--- /dev/null
519+++ b/django/apps/signals.py
520@@ -0,0 +1,7 @@
521+from django.dispatch import Signal
522+
523+# Sent when an app is loaded by the app cache
524+app_loaded = Signal(providing_args=["app"])
525+
526+# Sent when the app cache loads the apps
527+post_apps_loaded = Signal(providing_args=["apps"])
528diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py
529index 1084784..0507223 100644
530--- a/django/contrib/admin/__init__.py
531+++ b/django/contrib/admin/__init__.py
532@@ -1,5 +1,6 @@
533 # ACTION_CHECKBOX_NAME is unused, but should stay since its import from here
534 # has been referenced in documentation.
535+from django.apps import cache
536 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
537 from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
538 from django.contrib.admin.options import StackedInline, TabularInline
539@@ -8,7 +9,6 @@ from django.contrib.admin.filters import (ListFilter, SimpleListFilter,
540     FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter,
541     ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter)
542 
543-
544 def autodiscover():
545     """
546     Auto-discover INSTALLED_APPS admin.py modules and fail silently when
547@@ -21,12 +21,11 @@ def autodiscover():
548     from django.utils.importlib import import_module
549     from django.utils.module_loading import module_has_submodule
550 
551-    for app in settings.INSTALLED_APPS:
552-        mod = import_module(app)
553+    for app in cache.loaded_apps:
554         # Attempt to import the app's admin module.
555         try:
556             before_import_registry = copy.copy(site._registry)
557-            import_module('%s.admin' % app)
558+            import_module('%s.admin' % app._meta.name)
559         except:
560             # Reset the model registry to the state before the last import as
561             # this import will have to reoccur on the next request and this
562@@ -37,5 +36,5 @@ def autodiscover():
563             # Decide whether to bubble up this error. If the app just
564             # doesn't have an admin module, we can ignore the error
565             # attempting to import it, otherwise we want it to bubble up.
566-            if module_has_submodule(mod, 'admin'):
567+            if module_has_submodule(app._meta.module, 'admin'):
568                 raise
569diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
570index cf7ea83..6a0f5dd 100644
571--- a/django/contrib/admin/options.py
572+++ b/django/contrib/admin/options.py
573@@ -1,5 +1,5 @@
574 from functools import update_wrapper, partial
575-from django import forms
576+from django import apps, forms, template
577 from django.forms.formsets import all_valid
578 from django.forms.models import (modelform_factory, modelformset_factory,
579     inlineformset_factory, BaseInlineFormSet)
580@@ -968,7 +968,7 @@ class ModelAdmin(BaseModelAdmin):
581             'media': mark_safe(media),
582             'inline_admin_formsets': inline_admin_formsets,
583             'errors': helpers.AdminErrorList(form, formsets),
584-            'app_label': opts.app_label,
585+            'app_label': apps.find_app(opts.app_label)._meta.verbose_name,
586         }
587         context.update(extra_context or {})
588         return self.render_change_form(request, context, form_url=form_url, add=True)
589@@ -1058,7 +1058,7 @@ class ModelAdmin(BaseModelAdmin):
590             'media': mark_safe(media),
591             'inline_admin_formsets': inline_admin_formsets,
592             'errors': helpers.AdminErrorList(form, formsets),
593-            'app_label': opts.app_label,
594+            'app_label': apps.find_app(opts.app_label)._meta.verbose_name,
595         }
596         context.update(extra_context or {})
597         return self.render_change_form(request, context, change=True, obj=obj)
598@@ -1199,7 +1199,7 @@ class ModelAdmin(BaseModelAdmin):
599             'cl': cl,
600             'media': media,
601             'has_add_permission': self.has_add_permission(request),
602-            'app_label': app_label,
603+            'app_label': apps.find_app(app_label)._meta.verbose_name,
604             'action_form': action_form,
605             'actions_on_top': self.actions_on_top,
606             'actions_on_bottom': self.actions_on_bottom,
607@@ -1263,7 +1263,7 @@ class ModelAdmin(BaseModelAdmin):
608             "perms_lacking": perms_needed,
609             "protected": protected,
610             "opts": opts,
611-            "app_label": app_label,
612+            "app_label": apps.find_app(app_label)._meta.verbose_name,
613         }
614         context.update(extra_context or {})
615 
616@@ -1290,7 +1290,7 @@ class ModelAdmin(BaseModelAdmin):
617             'action_list': action_list,
618             'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
619             'object': obj,
620-            'app_label': app_label,
621+            'app_label': apps.find_app(app_label)._meta.verbose_name,
622         }
623         context.update(extra_context or {})
624         return TemplateResponse(request, self.object_history_template or [
625diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
626index 49c1e78..92b4478 100644
627--- a/django/contrib/admin/sites.py
628+++ b/django/contrib/admin/sites.py
629@@ -1,5 +1,6 @@
630 from functools import update_wrapper
631-from django import http
632+
633+from django import apps, http
634 from django.contrib.admin import ModelAdmin, actions
635 from django.contrib.admin.forms import AdminAuthenticationForm
636 from django.contrib.auth import REDIRECT_FIELD_NAME
637@@ -348,7 +349,7 @@ class AdminSite(object):
638                         app_dict[app_label]['models'].append(model_dict)
639                     else:
640                         app_dict[app_label] = {
641-                            'name': app_label.title(),
642+                            'name': apps.find_app(app_label)._meta.verbose_name,
643                             'app_url': app_label + '/',
644                             'has_module_perms': has_module_perms,
645                             'models': [model_dict],
646@@ -375,6 +376,7 @@ class AdminSite(object):
647         user = request.user
648         has_module_perms = user.has_module_perms(app_label)
649         app_dict = {}
650+        app = apps.find_app(app_label)
651         for model, model_admin in self._registry.items():
652             if app_label == model._meta.app_label:
653                 if has_module_perms:
654@@ -395,7 +397,7 @@ class AdminSite(object):
655                             # something to display, add in the necessary meta
656                             # information.
657                             app_dict = {
658-                                'name': app_label.title(),
659+                                'name': app._meta.verbose_name,
660                                 'app_url': '',
661                                 'has_module_perms': has_module_perms,
662                                 'models': [model_dict],
663@@ -405,7 +407,7 @@ class AdminSite(object):
664         # Sort the models alphabetically within each app.
665         app_dict['models'].sort(key=lambda x: x['name'])
666         context = {
667-            'title': _('%s administration') % capfirst(app_label),
668+            'title': _('%s administration') % app._meta.verbose_name,
669             'app_list': [app_dict],
670         }
671         context.update(extra_context or {})
672diff --git a/django/contrib/auth/app.py b/django/contrib/auth/app.py
673new file mode 100644
674index 0000000..159b713
675--- /dev/null
676+++ b/django/contrib/auth/app.py
677@@ -0,0 +1,7 @@
678+from django import apps
679+from django.utils.translation import ugettext_lazy as _
680+
681+class AuthApp(apps.App):
682+
683+    class Meta:
684+        verbose_name = _('auth')
685diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py
686index 27d1275..a444714 100644
687--- a/django/contrib/contenttypes/management.py
688+++ b/django/contrib/contenttypes/management.py
689@@ -1,5 +1,6 @@
690+from django.apps import cache, get_apps, get_models
691 from django.contrib.contenttypes.models import ContentType
692-from django.db.models import get_apps, get_models, signals
693+from django.db.models import signals
694 from django.utils.encoding import smart_unicode
695 
696 def update_contenttypes(app, created_models, verbosity=2, **kwargs):
697@@ -7,8 +8,9 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs):
698     Creates content types for models in the given app, removing any model
699     entries that no longer have a matching model class.
700     """
701+    app_cls = cache.find_app_by_models_module(app)
702     ContentType.objects.clear_cache()
703-    content_types = list(ContentType.objects.filter(app_label=app.__name__.split('.')[-2]))
704+    content_types = list(ContentType.objects.filter(app_label=app_cls._meta.label))
705     app_models = get_models(app)
706     if not app_models:
707         return
708diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py
709index 45bf4a1..b23428b 100644
710--- a/django/contrib/staticfiles/finders.py
711+++ b/django/contrib/staticfiles/finders.py
712@@ -1,4 +1,5 @@
713 import os
714+from django.apps import cache
715 from django.conf import settings
716 from django.core.exceptions import ImproperlyConfigured
717 from django.core.files.storage import default_storage, Storage, FileSystemStorage
718@@ -113,14 +114,12 @@ class AppDirectoriesFinder(BaseFinder):
719     """
720     storage_class = AppStaticStorage
721 
722-    def __init__(self, apps=None, *args, **kwargs):
723+    def __init__(self, *args, **kwargs):
724         # The list of apps that are handled
725         self.apps = []
726         # Mapping of app module paths to storage instances
727         self.storages = SortedDict()
728-        if apps is None:
729-            apps = settings.INSTALLED_APPS
730-        for app in apps:
731+        for app in cache.loaded_apps:
732             app_storage = self.storage_class(app)
733             if os.path.isdir(app_storage.location):
734                 self.storages[app] = app_storage
735diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py
736index a804c56..ef04f20 100644
737--- a/django/contrib/staticfiles/storage.py
738+++ b/django/contrib/staticfiles/storage.py
739@@ -194,9 +194,7 @@ class AppStaticStorage(FileSystemStorage):
740         Returns a static file storage if available in the given app.
741         """
742         # app is the actual app module
743-        mod = import_module(app)
744-        mod_path = os.path.dirname(mod.__file__)
745-        location = os.path.join(mod_path, self.source_dir)
746+        location = os.path.join(app._meta.path, self.source_dir)
747         super(AppStaticStorage, self).__init__(location, *args, **kwargs)
748 
749 
750diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
751index 1345eaf..bf3790d 100644
752--- a/django/core/management/__init__.py
753+++ b/django/core/management/__init__.py
754@@ -97,8 +97,8 @@ def get_commands():
755 
756         # Find the installed apps
757         try:
758-            from django.conf import settings
759-            apps = settings.INSTALLED_APPS
760+            from django.apps import cache
761+            apps = cache.loaded_apps
762         except (AttributeError, EnvironmentError, ImportError):
763             apps = []
764 
765@@ -111,10 +111,10 @@ def get_commands():
766             project_directory = None
767 
768         # Find and load the management module for each installed app.
769-        for app_name in apps:
770+        for app in apps:
771             try:
772-                path = find_management_module(app_name)
773-                _commands.update(dict([(name, app_name)
774+                path = find_management_module(app._meta.name)
775+                _commands.update(dict([(name, app._meta.name)
776                                        for name in find_commands(path)]))
777             except ImportError:
778                 pass # No management module - ignore this app
779@@ -316,7 +316,7 @@ class ManagementUtility(object):
780                 try:
781                     from django.conf import settings
782                     # Get the last part of the dotted path as the app name.
783-                    options += [(a.split('.')[-1], 0) for a in settings.INSTALLED_APPS]
784+                    options += [(a._meta.label, 0) for app in cache.loaded_apps]
785                 except ImportError:
786                     # Fail silently if DJANGO_SETTINGS_MODULE isn't set. The
787                     # user will find out once they execute the command.
788@@ -423,6 +423,12 @@ def setup_environ(settings_mod, original_settings_path=None):
789     project_module = import_module(project_name)
790     sys.path.pop()
791 
792+    # Initialize the appcache and look for errors
793+    from django.apps import cache
794+    for (app_name, error) in cache.get_app_errors().items():
795+        sys.stderr.write("%s: %s" % (app_name, error))
796+        sys.exit(1)
797+
798     return project_directory
799 
800 def execute_from_command_line(argv=None):
801diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py
802index bcadb96..dcb2a1a 100644
803--- a/django/core/management/commands/flush.py
804+++ b/django/core/management/commands/flush.py
805@@ -1,5 +1,6 @@
806 from optparse import make_option
807 
808+from django.apps import cache
809 from django.conf import settings
810 from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
811 from django.core.management import call_command
812@@ -29,9 +30,9 @@ class Command(NoArgsCommand):
813 
814         # Import the 'management' module within each installed app, to register
815         # dispatcher events.
816-        for app_name in settings.INSTALLED_APPS:
817+        for app in cache.loaded_apps:
818             try:
819-                import_module('.management', app_name)
820+                import_module('%s.management' % app._meta.name)
821             except ImportError:
822                 pass
823 
824diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py
825index 4383c34..13b8224 100644
826--- a/django/core/management/commands/syncdb.py
827+++ b/django/core/management/commands/syncdb.py
828@@ -1,6 +1,7 @@
829 from optparse import make_option
830 import sys
831 
832+from django.apps import cache
833 from django.conf import settings
834 from django.core.management.base import NoArgsCommand
835 from django.core.management.color import no_style
836@@ -18,7 +19,7 @@ class Command(NoArgsCommand):
837             default=DEFAULT_DB_ALIAS, help='Nominates a database to synchronize. '
838                 'Defaults to the "default" database.'),
839     )
840-    help = "Create the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
841+    help = "Create the database tables for all installed apps whose tables haven't already been created."
842 
843     def handle_noargs(self, **options):
844 
845@@ -34,9 +35,9 @@ class Command(NoArgsCommand):
846 
847         # Import the 'management' module within each installed app, to register
848         # dispatcher events.
849-        for app_name in settings.INSTALLED_APPS:
850+        for app in cache.loaded_apps:
851             try:
852-                import_module('.management', app_name)
853+                import_module('.management', app._meta.name)
854             except ImportError, exc:
855                 # This is slightly hackish. We want to ignore ImportErrors
856                 # if the "management" module itself is missing -- but we don't
857@@ -63,10 +64,11 @@ class Command(NoArgsCommand):
858 
859         # Build the manifest of apps and models that are to be synchronized
860         all_models = [
861-            (app.__name__.split('.')[-2],
862-                [m for m in models.get_models(app, include_auto_created=True)
863+            (app._meta.label,
864+                [m for m in models.get_models(app._meta.models_module,
865+                                              include_auto_created=True)
866                 if router.allow_syncdb(db, m)])
867-            for app in models.get_apps()
868+            for app in cache.loaded_apps
869         ]
870         def model_installed(model):
871             opts = model._meta
872@@ -101,7 +103,6 @@ class Command(NoArgsCommand):
873                     cursor.execute(statement)
874                 tables.append(connection.introspection.table_name_converter(model._meta.db_table))
875 
876-
877         transaction.commit_unless_managed(using=db)
878 
879         # Send the post_syncdb signal, so individual apps can do whatever they need
880diff --git a/django/core/management/sql.py b/django/core/management/sql.py
881index 298882f..1b170c2 100644
882--- a/django/core/management/sql.py
883+++ b/django/core/management/sql.py
884@@ -1,10 +1,10 @@
885 import os
886 import re
887 
888+from django.apps import cache, get_models
889 from django.conf import settings
890 from django.core.management.base import CommandError
891 from django.db import models
892-from django.db.models import get_models
893 
894 def sql_create(app, style, connection):
895     "Returns a list of the CREATE TABLE SQL statements for the given app."
896@@ -182,7 +182,8 @@ def custom_sql_for_model(model, style, connection):
897 def emit_post_sync_signal(created_models, verbosity, interactive, db):
898     # Emit the post_sync signal for every application.
899     for app in models.get_apps():
900-        app_name = app.__name__.split('.')[-2]
901+        app_cls = cache.find_app_by_models_module(app)
902+        app_name = app_cls._meta.label
903         if verbosity >= 2:
904             print "Running post-sync handlers for application", app_name
905         models.signals.post_syncdb.send(sender=app, app=app,
906diff --git a/django/db/models/base.py b/django/db/models/base.py
907index 71fd1f7..7891676 100644
908--- a/django/db/models/base.py
909+++ b/django/db/models/base.py
910@@ -118,7 +118,7 @@ class ModelBase(type):
911                 else:
912                     base = parent
913             if base is None:
914-                    raise TypeError("Proxy model '%s' has no non-abstract model base class." % name)
915+                raise TypeError("Proxy model '%s' has no non-abstract model base class." % name)
916             if (new_class._meta.local_fields or
917                     new_class._meta.local_many_to_many):
918                 raise FieldError("Proxy model '%s' contains model fields." % name)
919diff --git a/django/db/models/loading.py b/django/db/models/loading.py
920index 03cdcc2..22c2c41 100644
921--- a/django/db/models/loading.py
922+++ b/django/db/models/loading.py
923@@ -1,251 +1,10 @@
924-"Utilities for loading models and the modules that contain them."
925+import warnings
926 
927-from django.conf import settings
928-from django.core.exceptions import ImproperlyConfigured
929-from django.utils.datastructures import SortedDict
930-from django.utils.importlib import import_module
931-from django.utils.module_loading import module_has_submodule
932+from django.apps import (App, AppCache, cache,
933+    find_app, get_apps, get_app, get_app_errors, get_models, get_model,
934+    register_models, load_app, app_cache_ready)
935 
936-import sys
937-import os
938-import threading
939-
940-__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
941-        'load_app', 'app_cache_ready')
942-
943-class AppCache(object):
944-    """
945-    A cache that stores installed applications and their models. Used to
946-    provide reverse-relations and for app introspection (e.g. admin).
947-    """
948-    # Use the Borg pattern to share state between all instances. Details at
949-    # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531.
950-    __shared_state = dict(
951-        # Keys of app_store are the model modules for each application.
952-        app_store = SortedDict(),
953-
954-        # Mapping of installed app_labels to model modules for that app.
955-        app_labels = {},
956-
957-        # Mapping of app_labels to a dictionary of model names to model code.
958-        # May contain apps that are not installed.
959-        app_models = SortedDict(),
960-
961-        # Mapping of app_labels to errors raised when trying to import the app.
962-        app_errors = {},
963-
964-        # -- Everything below here is only used when populating the cache --
965-        loaded = False,
966-        handled = {},
967-        postponed = [],
968-        nesting_level = 0,
969-        write_lock = threading.RLock(),
970-        _get_models_cache = {},
971-    )
972-
973-    def __init__(self):
974-        self.__dict__ = self.__shared_state
975-
976-    def _populate(self):
977-        """
978-        Fill in all the cache information. This method is threadsafe, in the
979-        sense that every caller will see the same state upon return, and if the
980-        cache is already initialised, it does no work.
981-        """
982-        if self.loaded:
983-            return
984-        self.write_lock.acquire()
985-        try:
986-            if self.loaded:
987-                return
988-            for app_name in settings.INSTALLED_APPS:
989-                if app_name in self.handled:
990-                    continue
991-                self.load_app(app_name, True)
992-            if not self.nesting_level:
993-                for app_name in self.postponed:
994-                    self.load_app(app_name)
995-                self.loaded = True
996-        finally:
997-            self.write_lock.release()
998-
999-    def _label_for(self, app_mod):
1000-        """
1001-        Return app_label for given models module.
1002-
1003-        """
1004-        return app_mod.__name__.split('.')[-2]
1005-
1006-    def load_app(self, app_name, can_postpone=False):
1007-        """
1008-        Loads the app with the provided fully qualified name, and returns the
1009-        model module.
1010-        """
1011-        self.handled[app_name] = None
1012-        self.nesting_level += 1
1013-        app_module = import_module(app_name)
1014-        try:
1015-            models = import_module('.models', app_name)
1016-        except ImportError:
1017-            self.nesting_level -= 1
1018-            # If the app doesn't have a models module, we can just ignore the
1019-            # ImportError and return no models for it.
1020-            if not module_has_submodule(app_module, 'models'):
1021-                return None
1022-            # But if the app does have a models module, we need to figure out
1023-            # whether to suppress or propagate the error. If can_postpone is
1024-            # True then it may be that the package is still being imported by
1025-            # Python and the models module isn't available yet. So we add the
1026-            # app to the postponed list and we'll try it again after all the
1027-            # recursion has finished (in populate). If can_postpone is False
1028-            # then it's time to raise the ImportError.
1029-            else:
1030-                if can_postpone:
1031-                    self.postponed.append(app_name)
1032-                    return None
1033-                else:
1034-                    raise
1035-
1036-        self.nesting_level -= 1
1037-        if models not in self.app_store:
1038-            self.app_store[models] = len(self.app_store)
1039-            self.app_labels[self._label_for(models)] = models
1040-        return models
1041-
1042-    def app_cache_ready(self):
1043-        """
1044-        Returns true if the model cache is fully populated.
1045-
1046-        Useful for code that wants to cache the results of get_models() for
1047-        themselves once it is safe to do so.
1048-        """
1049-        return self.loaded
1050-
1051-    def get_apps(self):
1052-        "Returns a list of all installed modules that contain models."
1053-        self._populate()
1054-
1055-        # Ensure the returned list is always in the same order (with new apps
1056-        # added at the end). This avoids unstable ordering on the admin app
1057-        # list page, for example.
1058-        apps = [(v, k) for k, v in self.app_store.items()]
1059-        apps.sort()
1060-        return [elt[1] for elt in apps]
1061-
1062-    def get_app(self, app_label, emptyOK=False):
1063-        """
1064-        Returns the module containing the models for the given app_label. If
1065-        the app has no models in it and 'emptyOK' is True, returns None.
1066-        """
1067-        self._populate()
1068-        self.write_lock.acquire()
1069-        try:
1070-            for app_name in settings.INSTALLED_APPS:
1071-                if app_label == app_name.split('.')[-1]:
1072-                    mod = self.load_app(app_name, False)
1073-                    if mod is None:
1074-                        if emptyOK:
1075-                            return None
1076-                    else:
1077-                        return mod
1078-            raise ImproperlyConfigured("App with label %s could not be found" % app_label)
1079-        finally:
1080-            self.write_lock.release()
1081-
1082-    def get_app_errors(self):
1083-        "Returns the map of known problems with the INSTALLED_APPS."
1084-        self._populate()
1085-        return self.app_errors
1086-
1087-    def get_models(self, app_mod=None,
1088-                   include_auto_created=False, include_deferred=False,
1089-                   only_installed=True):
1090-        """
1091-        Given a module containing models, returns a list of the models.
1092-        Otherwise returns a list of all installed models.
1093-
1094-        By default, auto-created models (i.e., m2m models without an
1095-        explicit intermediate table) are not included. However, if you
1096-        specify include_auto_created=True, they will be.
1097-
1098-        By default, models created to satisfy deferred attribute
1099-        queries are *not* included in the list of models. However, if
1100-        you specify include_deferred, they will be.
1101-        """
1102-        cache_key = (app_mod, include_auto_created, include_deferred, only_installed)
1103-        try:
1104-            return self._get_models_cache[cache_key]
1105-        except KeyError:
1106-            pass
1107-        self._populate()
1108-        if app_mod:
1109-            if app_mod in self.app_store:
1110-                app_list = [self.app_models.get(self._label_for(app_mod),
1111-                                                SortedDict())]
1112-            else:
1113-                app_list = []
1114-        else:
1115-            if only_installed:
1116-                app_list = [self.app_models.get(app_label, SortedDict())
1117-                            for app_label in self.app_labels.iterkeys()]
1118-            else:
1119-                app_list = self.app_models.itervalues()
1120-        model_list = []
1121-        for app in app_list:
1122-            model_list.extend(
1123-                model for model in app.values()
1124-                if ((not model._deferred or include_deferred) and
1125-                    (not model._meta.auto_created or include_auto_created))
1126-            )
1127-        self._get_models_cache[cache_key] = model_list
1128-        return model_list
1129-
1130-    def get_model(self, app_label, model_name,
1131-                  seed_cache=True, only_installed=True):
1132-        """
1133-        Returns the model matching the given app_label and case-insensitive
1134-        model_name.
1135-
1136-        Returns None if no model is found.
1137-        """
1138-        if seed_cache:
1139-            self._populate()
1140-        if only_installed and app_label not in self.app_labels:
1141-            return None
1142-        return self.app_models.get(app_label, SortedDict()).get(model_name.lower())
1143-
1144-    def register_models(self, app_label, *models):
1145-        """
1146-        Register a set of models as belonging to an app.
1147-        """
1148-        for model in models:
1149-            # Store as 'name: model' pair in a dictionary
1150-            # in the app_models dictionary
1151-            model_name = model._meta.object_name.lower()
1152-            model_dict = self.app_models.setdefault(app_label, SortedDict())
1153-            if model_name in model_dict:
1154-                # The same model may be imported via different paths (e.g.
1155-                # appname.models and project.appname.models). We use the source
1156-                # filename as a means to detect identity.
1157-                fname1 = os.path.abspath(sys.modules[model.__module__].__file__)
1158-                fname2 = os.path.abspath(sys.modules[model_dict[model_name].__module__].__file__)
1159-                # Since the filename extension could be .py the first time and
1160-                # .pyc or .pyo the second time, ignore the extension when
1161-                # comparing.
1162-                if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
1163-                    continue
1164-            model_dict[model_name] = model
1165-        self._get_models_cache.clear()
1166-
1167-cache = AppCache()
1168-
1169-# These methods were always module level, so are kept that way for backwards
1170-# compatibility.
1171-get_apps = cache.get_apps
1172-get_app = cache.get_app
1173-get_app_errors = cache.get_app_errors
1174-get_models = cache.get_models
1175-get_model = cache.get_model
1176-register_models = cache.register_models
1177-load_app = cache.load_app
1178-app_cache_ready = cache.app_cache_ready
1179+warnings.warn(
1180+    'The utilities in django.db.models.loading have been moved to '
1181+    'django.apps. Please update your code accordingly.',
1182+    PendingDeprecationWarning)
1183diff --git a/django/db/models/options.py b/django/db/models/options.py
1184index 6f0f406..5d19fe9 100644
1185--- a/django/db/models/options.py
1186+++ b/django/db/models/options.py
1187@@ -1,6 +1,7 @@
1188 import re
1189 from bisect import bisect
1190 
1191+from django.apps import cache
1192 from django.conf import settings
1193 from django.db.models.related import RelatedObject
1194 from django.db.models.fields.related import ManyToManyRel
1195@@ -10,9 +11,8 @@ from django.db.models.loading import get_models, app_cache_ready
1196 from django.utils.translation import activate, deactivate_all, get_language, string_concat
1197 from django.utils.encoding import force_unicode, smart_str
1198 from django.utils.datastructures import SortedDict
1199+from django.utils.text import get_verbose_name
1200 
1201-# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
1202-get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
1203 
1204 DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
1205                  'unique_together', 'permissions', 'get_latest_by',
1206@@ -59,7 +59,8 @@ class Options(object):
1207         from django.db.backends.util import truncate_name
1208 
1209         cls._meta = self
1210-        self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
1211+        # The AppCache sets this attribute to True for apps that are installed
1212+        self.installed = False
1213         # First, construct the default values for these options.
1214         self.object_name = cls.__name__
1215         self.module_name = self.object_name.lower()
1216@@ -100,10 +101,14 @@ class Options(object):
1217             self.verbose_name_plural = string_concat(self.verbose_name, 's')
1218         del self.meta
1219 
1220-        # If the db_table wasn't provided, use the app_label + module_name.
1221+        # If the db_table wasn't provided, use the db_prefix + module_name.
1222+        # Or use the app label when no app instance was found, which happens
1223+        # when the app cache is not initialized but the model is imported
1224         if not self.db_table:
1225-            self.db_table = "%s_%s" % (self.app_label, self.module_name)
1226-            self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
1227+            app = cache.find_app(self.app_label)
1228+            prefix = app and app._meta.db_prefix or self.app_label
1229+            self.db_table = truncate_name("%s_%s" % (prefix, self.module_name),
1230+                                          connection.ops.max_name_length())
1231 
1232     def _prepare(self, model):
1233         if self.order_with_respect_to:
1234diff --git a/django/template/base.py b/django/template/base.py
1235index 9a7f55f..0acda61 100644
1236--- a/django/template/base.py
1237+++ b/django/template/base.py
1238@@ -2,6 +2,7 @@ import re
1239 from functools import partial
1240 from inspect import getargspec
1241 
1242+from django.apps import cache
1243 from django.conf import settings
1244 from django.template.context import Context, RequestContext, ContextPopException
1245 from django.utils.importlib import import_module
1246@@ -1046,7 +1047,7 @@ def get_templatetags_modules():
1247     if not templatetags_modules:
1248         _templatetags_modules = []
1249         # Populate list once per thread.
1250-        for app_module in ['django'] + list(settings.INSTALLED_APPS):
1251+        for app_module in ['django'] + [app._meta.name for app in cache.loaded_apps]:
1252             try:
1253                 templatetag_module = '%s.templatetags' % app_module
1254                 import_module(templatetag_module)
1255diff --git a/django/template/loaders/app_directories.py b/django/template/loaders/app_directories.py
1256index b0560b4..b401e6c 100644
1257--- a/django/template/loaders/app_directories.py
1258+++ b/django/template/loaders/app_directories.py
1259@@ -6,6 +6,7 @@ packages.
1260 import os
1261 import sys
1262 
1263+from django.apps import cache
1264 from django.conf import settings
1265 from django.core.exceptions import ImproperlyConfigured
1266 from django.template.base import TemplateDoesNotExist
1267@@ -16,12 +17,8 @@ from django.utils.importlib import import_module
1268 # At compile time, cache the directories to search.
1269 fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
1270 app_template_dirs = []
1271-for app in settings.INSTALLED_APPS:
1272-    try:
1273-        mod = import_module(app)
1274-    except ImportError, e:
1275-        raise ImproperlyConfigured('ImportError %s: %s' % (app, e.args[0]))
1276-    template_dir = os.path.join(os.path.dirname(mod.__file__), 'templates')
1277+for app in cache.loaded_apps:
1278+    template_dir = os.path.join(app._meta.path, 'templates')
1279     if os.path.isdir(template_dir):
1280         app_template_dirs.append(template_dir.decode(fs_encoding))
1281 
1282diff --git a/django/template/loaders/eggs.py b/django/template/loaders/eggs.py
1283index 42f87a4..a5ef838 100644
1284--- a/django/template/loaders/eggs.py
1285+++ b/django/template/loaders/eggs.py
1286@@ -1,4 +1,5 @@
1287 # Wrapper for loading templates from eggs via pkg_resources.resource_string.
1288+from django.apps import cache
1289 
1290 try:
1291     from pkg_resources import resource_string
1292@@ -20,10 +21,10 @@ class Loader(BaseLoader):
1293         """
1294         if resource_string is not None:
1295             pkg_name = 'templates/' + template_name
1296-            for app in settings.INSTALLED_APPS:
1297+            for app in cache.loaded_apps:
1298                 try:
1299-                    return (resource_string(app, pkg_name).decode(settings.FILE_CHARSET), 'egg:%s:%s' % (app, pkg_name))
1300-                except:
1301+                    return (resource_string(app._meta.name, pkg_name).decode(settings.FILE_CHARSET), 'egg:%s:%s' % (app._meta.name, pkg_name))
1302+                except Exception, e:
1303                     pass
1304         raise TemplateDoesNotExist(template_name)
1305 
1306diff --git a/django/utils/module_loading.py b/django/utils/module_loading.py
1307index 26a7e51..54c04e9 100644
1308--- a/django/utils/module_loading.py
1309+++ b/django/utils/module_loading.py
1310@@ -14,6 +14,9 @@ def module_has_submodule(package, module_name):
1311     for finder in sys.meta_path:
1312         if finder.find_module(name, package.__path__):
1313             return True
1314+    # If this isn't really a package return False right away
1315+    if not hasattr(package, '__path__'):
1316+        return False
1317     for entry in package.__path__:  # No __path__, then not a package.
1318         try:
1319             # Try the cached finder.
1320diff --git a/django/utils/text.py b/django/utils/text.py
1321index 14555dd..2afb2b0 100644
1322--- a/django/utils/text.py
1323+++ b/django/utils/text.py
1324@@ -362,9 +362,9 @@ def unescape_entities(text):
1325 unescape_entities = allow_lazy(unescape_entities, unicode)
1326 
1327 def unescape_string_literal(s):
1328-    r"""
1329-    Convert quoted string literals to unquoted strings with escaped quotes and
1330-    backslashes unquoted::
1331+    """
1332+    Converts quoted string literals to unquoted strings with escaped quotes
1333+    and backslashes unquoted::
1334 
1335         >>> unescape_string_literal('"abc"')
1336         'abc'
1337@@ -380,3 +380,11 @@ def unescape_string_literal(s):
1338     quote = s[0]
1339     return s[1:-1].replace(r'\%s' % quote, quote).replace(r'\\', '\\')
1340 unescape_string_literal = allow_lazy(unescape_string_literal)
1341+
1342+def get_verbose_name(class_name):
1343+    """
1344+    Calculates the verbose_name by converting from
1345+    InitialCaps to "lowercase with spaces".
1346+    """
1347+    new = re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name)
1348+    return new.lower().strip()
1349diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
1350index 71765e7..5eefd70 100644
1351--- a/django/utils/translation/trans_real.py
1352+++ b/django/utils/translation/trans_real.py
1353@@ -12,6 +12,7 @@ try:
1354 except ImportError:
1355     from StringIO import StringIO
1356 
1357+from django.apps import cache
1358 from django.utils.importlib import import_module
1359 from django.utils.safestring import mark_safe, SafeData
1360 
1361@@ -156,9 +157,8 @@ def translation(language):
1362                     res.merge(t)
1363             return res
1364 
1365-        for appname in reversed(settings.INSTALLED_APPS):
1366-            app = import_module(appname)
1367-            apppath = os.path.join(os.path.dirname(app.__file__), 'locale')
1368+        for app in reversed(cache.loaded_apps):
1369+            apppath = os.path.join(app._meta.path, 'locale')
1370 
1371             if os.path.isdir(apppath):
1372                 res = _merge(apppath)
1373diff --git a/django/views/i18n.py b/django/views/i18n.py
1374index 140dc54..055323e 100644
1375--- a/django/views/i18n.py
1376+++ b/django/views/i18n.py
1377@@ -2,6 +2,7 @@ import os
1378 import gettext as gettext_module
1379 
1380 from django import http
1381+from django.apps import cache
1382 from django.conf import settings
1383 from django.utils import importlib
1384 from django.utils.translation import check_for_language, activate, to_locale, get_language
1385@@ -186,7 +187,8 @@ def javascript_catalog(request, domain='djangojs', packages=None):
1386         packages = ['django.conf']
1387     if isinstance(packages, basestring):
1388         packages = packages.split('+')
1389-    packages = [p for p in packages if p == 'django.conf' or p in settings.INSTALLED_APPS]
1390+    apps = [app._meta.name for app in cache.loaded_apps]
1391+    packages = [p for p in packages if p == 'django.conf' or p in apps]
1392     default_locale = to_locale(settings.LANGUAGE_CODE)
1393     locale = to_locale(get_language())
1394     t = {}
1395diff --git a/tests/appcachetests/anothermodel_app/__init__.py b/tests/appcachetests/anothermodel_app/__init__.py
1396new file mode 100644
1397index 0000000..e69de29
1398diff --git a/tests/appcachetests/anothermodel_app/app.py b/tests/appcachetests/anothermodel_app/app.py
1399new file mode 100644
1400index 0000000..7433592
1401--- /dev/null
1402+++ b/tests/appcachetests/anothermodel_app/app.py
1403@@ -0,0 +1,11 @@
1404+from django import apps
1405+
1406+class MyApp(apps.App):
1407+
1408+    class Meta:
1409+        models_path = 'model_app.othermodels'
1410+
1411+class MyOtherApp(MyApp):
1412+
1413+    class Meta:
1414+        db_prefix = 'nomodel_app'
1415diff --git a/tests/appcachetests/anothermodel_app/models.py b/tests/appcachetests/anothermodel_app/models.py
1416new file mode 100644
1417index 0000000..cd849d1
1418--- /dev/null
1419+++ b/tests/appcachetests/anothermodel_app/models.py
1420@@ -0,0 +1,12 @@
1421+from django.db import models
1422+
1423+class Job(models.Model):
1424+    name = models.CharField(max_length=30)
1425+
1426+class Person(models.Model):
1427+    first_name = models.CharField(max_length=30)
1428+    last_name = models.CharField(max_length=30)
1429+    jobs = models.ManyToManyField(Job)
1430+
1431+class Contact(models.Model):
1432+    person = models.ForeignKey(Person)
1433diff --git a/tests/appcachetests/anothermodel_app/othermodels.py b/tests/appcachetests/anothermodel_app/othermodels.py
1434new file mode 100644
1435index 0000000..ffb04e8
1436--- /dev/null
1437+++ b/tests/appcachetests/anothermodel_app/othermodels.py
1438@@ -0,0 +1,5 @@
1439+from django.db import models
1440+
1441+class Person(models.Model):
1442+    first_name = models.CharField(max_length=30)
1443+    last_name = models.CharField(max_length=30)
1444diff --git a/tests/appcachetests/eggs/brokenapp.egg b/tests/appcachetests/eggs/brokenapp.egg
1445new file mode 100755
1446index 0000000..8aca671
1447Binary files /dev/null and b/tests/appcachetests/eggs/brokenapp.egg differ
1448diff --git a/tests/appcachetests/eggs/modelapp.egg b/tests/appcachetests/eggs/modelapp.egg
1449new file mode 100755
1450index 0000000..c2370b5
1451Binary files /dev/null and b/tests/appcachetests/eggs/modelapp.egg differ
1452diff --git a/tests/appcachetests/eggs/nomodelapp.egg b/tests/appcachetests/eggs/nomodelapp.egg
1453new file mode 100755
1454index 0000000..5b8d217
1455Binary files /dev/null and b/tests/appcachetests/eggs/nomodelapp.egg differ
1456diff --git a/tests/appcachetests/eggs/omelet.egg b/tests/appcachetests/eggs/omelet.egg
1457new file mode 100755
1458index 0000000..bd1c687
1459Binary files /dev/null and b/tests/appcachetests/eggs/omelet.egg differ
1460diff --git a/tests/appcachetests/model_app/__init__.py b/tests/appcachetests/model_app/__init__.py
1461new file mode 100644
1462index 0000000..e69de29
1463diff --git a/tests/appcachetests/model_app/app.py b/tests/appcachetests/model_app/app.py
1464new file mode 100644
1465index 0000000..1426bcf
1466--- /dev/null
1467+++ b/tests/appcachetests/model_app/app.py
1468@@ -0,0 +1,36 @@
1469+from django import apps
1470+
1471+class MyApp(apps.App):
1472+    some_attribute = True
1473+
1474+    class Meta:
1475+        models_path = 'model_app.othermodels'
1476+
1477+class MyOtherApp(MyApp):
1478+
1479+    class Meta(MyApp.Meta):
1480+        db_prefix = 'nomodel_app'
1481+
1482+
1483+class MySecondApp(MyOtherApp):
1484+
1485+    class Meta(MyOtherApp.Meta):
1486+        models_path = 'model_app.models'
1487+
1488+
1489+class YetAnotherApp(apps.App):
1490+
1491+    class Meta:
1492+        models_path = 'model_app.yetanother'
1493+
1494+
1495+class MyThirdApp(YetAnotherApp, MySecondApp):
1496+
1497+    class Meta(YetAnotherApp.Meta, MySecondApp.Meta):
1498+        pass
1499+
1500+
1501+class MyOverrideApp(MyOtherApp):
1502+
1503+    pass
1504+
1505diff --git a/tests/appcachetests/model_app/models.py b/tests/appcachetests/model_app/models.py
1506new file mode 100644
1507index 0000000..ffb04e8
1508--- /dev/null
1509+++ b/tests/appcachetests/model_app/models.py
1510@@ -0,0 +1,5 @@
1511+from django.db import models
1512+
1513+class Person(models.Model):
1514+    first_name = models.CharField(max_length=30)
1515+    last_name = models.CharField(max_length=30)
1516diff --git a/tests/appcachetests/model_app/othermodels.py b/tests/appcachetests/model_app/othermodels.py
1517new file mode 100644
1518index 0000000..ffb04e8
1519--- /dev/null
1520+++ b/tests/appcachetests/model_app/othermodels.py
1521@@ -0,0 +1,5 @@
1522+from django.db import models
1523+
1524+class Person(models.Model):
1525+    first_name = models.CharField(max_length=30)
1526+    last_name = models.CharField(max_length=30)
1527diff --git a/tests/appcachetests/model_app/yetanother.py b/tests/appcachetests/model_app/yetanother.py
1528new file mode 100644
1529index 0000000..e69de29
1530diff --git a/tests/appcachetests/nomodel_app/__init__.py b/tests/appcachetests/nomodel_app/__init__.py
1531new file mode 100644
1532index 0000000..e69de29
1533diff --git a/tests/appcachetests/nomodel_app/app.py b/tests/appcachetests/nomodel_app/app.py
1534new file mode 100644
1535index 0000000..194278e
1536--- /dev/null
1537+++ b/tests/appcachetests/nomodel_app/app.py
1538@@ -0,0 +1,7 @@
1539+from django import apps
1540+
1541+class MyApp(apps.App):
1542+    pass
1543+
1544+class ObjectApp(object):
1545+    pass
1546\ No newline at end of file
1547diff --git a/tests/appcachetests/same_label/__init__.py b/tests/appcachetests/same_label/__init__.py
1548new file mode 100644
1549index 0000000..e69de29
1550diff --git a/tests/appcachetests/same_label/model_app/__init__.py b/tests/appcachetests/same_label/model_app/__init__.py
1551new file mode 100644
1552index 0000000..e69de29
1553diff --git a/tests/appcachetests/same_label/model_app/models.py b/tests/appcachetests/same_label/model_app/models.py
1554new file mode 100644
1555index 0000000..ffb04e8
1556--- /dev/null
1557+++ b/tests/appcachetests/same_label/model_app/models.py
1558@@ -0,0 +1,5 @@
1559+from django.db import models
1560+
1561+class Person(models.Model):
1562+    first_name = models.CharField(max_length=30)
1563+    last_name = models.CharField(max_length=30)
1564diff --git a/tests/appcachetests/same_label/nomodel_app/__init__.py b/tests/appcachetests/same_label/nomodel_app/__init__.py
1565new file mode 100644
1566index 0000000..e69de29
1567diff --git a/tests/appcachetests/same_label/nomodel_app/app.py b/tests/appcachetests/same_label/nomodel_app/app.py
1568new file mode 100644
1569index 0000000..86993e0
1570--- /dev/null
1571+++ b/tests/appcachetests/same_label/nomodel_app/app.py
1572@@ -0,0 +1,4 @@
1573+from django import apps
1574+
1575+class MyApp(apps.App):
1576+    pass
1577diff --git a/tests/appcachetests/tests.py b/tests/appcachetests/tests.py
1578new file mode 100755
1579index 0000000..5310745
1580--- /dev/null
1581+++ b/tests/appcachetests/tests.py
1582@@ -0,0 +1,708 @@
1583+#!/usr/bin/env python
1584+import os
1585+import sys
1586+import unittest
1587+import threading
1588+
1589+from django.apps import cache
1590+from django.apps.cache import _initialize
1591+from django.apps.signals import app_loaded, post_apps_loaded
1592+from django.conf import settings
1593+from django.core.exceptions import ImproperlyConfigured
1594+from django.utils.datastructures import SortedDict
1595+
1596+# remove when tests are integrated into the django testsuite
1597+settings.configure()
1598+
1599+
1600+class AppCacheTestCase(unittest.TestCase):
1601+    """
1602+    TestCase that resets the AppCache after each test.
1603+    """
1604+    def setUp(self):
1605+        self.old_installed_apps = settings.INSTALLED_APPS
1606+        settings.INSTALLED_APPS = ()
1607+        settings.DATABASES = {
1608+            'default': {
1609+                'ENGINE': 'django.db.backends.sqlite3',
1610+                'NAME': ':memory:'
1611+            }
1612+        }
1613+
1614+    def tearDown(self):
1615+        settings.INSTALLED_APPS = self.old_installed_apps
1616+        cache._reset()
1617+
1618+class ReloadTests(AppCacheTestCase):
1619+    """
1620+    Tests for the _reload function
1621+    """
1622+
1623+    def test_reload(self):
1624+        """
1625+        Test reloading the cache
1626+        """
1627+        settings.INSTALLED_APPS = ('model_app',)
1628+        cache._populate()
1629+        self.assertEquals(len(cache.loaded_apps), 1)
1630+        self.assertEquals(cache.loaded_apps[0]._meta.name, 'model_app')
1631+        settings.INSTALLED_APPS = ('anothermodel_app', 'model_app')
1632+        cache._reload()
1633+        self.assertEquals(len(cache.loaded_apps), 2)
1634+        self.assertEquals(cache.loaded_apps[0]._meta.name, 'anothermodel_app')
1635+
1636+    def test_reload_register_models(self):
1637+        """
1638+        Test that models are registered with the cache again after it
1639+        was reloaded
1640+        """
1641+        settings.INSTALLED_APPS = ('model_app',)
1642+        cache._populate()
1643+        self.assertTrue('model_app' in cache.app_models)
1644+        cache._reload()
1645+        self.assertTrue('model_app' in cache.app_models)
1646+
1647+
1648+class AppCacheReadyTests(AppCacheTestCase):
1649+    """
1650+    Tests for the app_cache_ready function that indicates if the cache
1651+    is fully populated.
1652+    """
1653+
1654+    def test_not_initialized(self):
1655+        """
1656+        Should return False if the AppCache hasn't been initialized
1657+        """
1658+        self.assertFalse(cache.app_cache_ready())
1659+
1660+    def test_load_app(self):
1661+        """
1662+        Should return False after executing the load_app function
1663+        """
1664+        cache.load_app('nomodel_app')
1665+        self.assertFalse(cache.app_cache_ready())
1666+        cache.load_app('nomodel_app', can_postpone=True)
1667+        self.assertFalse(cache.app_cache_ready())
1668+
1669+
1670+class GetAppClassTests(AppCacheTestCase):
1671+    """Tests for the get_app_class function"""
1672+
1673+    def test_app_class(self):
1674+        """
1675+        Tests that the full path app class is returned
1676+        """
1677+        settings.INSTALLED_APPS = ('model_app.app.MyApp',)
1678+        from model_app.app import MyApp
1679+        app_class = cache.get_app_class(settings.INSTALLED_APPS[0])
1680+        self.assertEquals(app_class, MyApp)
1681+
1682+    def test_one_level_module(self):
1683+        """
1684+        Tests that a new app class is generated for an one level app module
1685+        """
1686+        settings.INSTALLED_APPS = ('model_app',)
1687+        app_class = cache.get_app_class(settings.INSTALLED_APPS[0])
1688+        self.assertEquals(app_class.__name__, 'ModelApp')
1689+
1690+    def test_multi_level_module(self):
1691+        """
1692+        Tests that a new app class is generated for a multiple level app module
1693+        """
1694+        settings.INSTALLED_APPS = ('django.contrib.admin',)
1695+        app_class = cache.get_app_class(settings.INSTALLED_APPS[0])
1696+        self.assertEquals(app_class.__name__, 'Admin')
1697+
1698+    def test_defunct_module(self):
1699+        """
1700+        Tests that a wrong module raises an ImproperlyConfigured exception
1701+        """
1702+        settings.INSTALLED_APPS = ('lalalala.admin',)
1703+        self.assertRaises(ImproperlyConfigured, cache.get_app_class,
1704+                          settings.INSTALLED_APPS[0])
1705+
1706+    def test_missing_attribute(self):
1707+        """
1708+        Tests that a missing attribute raises an ImproperlyConfigured exception
1709+        """
1710+        settings.INSTALLED_APPS = ('nomodel_app.app.NotThereApp',)
1711+        self.assertRaises(ImproperlyConfigured, cache.get_app_class,
1712+                          settings.INSTALLED_APPS[0])
1713+
1714+    def test_incorrect_subclass(self):
1715+        """
1716+        Tests that a class not subclassing django.apps.App raises an
1717+        ImproperlyConfigured exception
1718+        """
1719+        settings.INSTALLED_APPS = ('nomodel_app.app.ObjectApp',)
1720+        self.assertRaises(ImproperlyConfigured, cache.get_app_class,
1721+                          settings.INSTALLED_APPS[0])
1722+
1723+
1724+class GetAppsTests(AppCacheTestCase):
1725+    """Tests for the get_apps function"""
1726+
1727+    def test_app_classes(self):
1728+        """
1729+        Test that the correct models modules are returned for app classes
1730+        installed via the INSTALLED_APPS setting
1731+        """
1732+        settings.INSTALLED_APPS = ('model_app.app.MyApp',)
1733+        apps = cache.get_apps()
1734+        self.assertTrue(cache.app_cache_ready())
1735+        self.assertEquals(apps[0].__name__, 'model_app.othermodels')
1736+
1737+    def test_installed_apps(self):
1738+        """
1739+        Test that the correct models modules are returned for apps installed
1740+        via the INSTALLED_APPS setting
1741+        """
1742+        settings.INSTALLED_APPS = ('model_app',)
1743+        apps = cache.get_apps()
1744+        self.assertTrue(cache.app_cache_ready())
1745+        self.assertEquals(apps[0].__name__, 'model_app.models')
1746+
1747+    def test_same_app_in_both_settings(self):
1748+        """
1749+        Test that if an App is listed multiple times in INSTALLED_APPS
1750+        only one of them is loaded
1751+        """
1752+        settings.INSTALLED_APPS = ('model_app.app.MyApp', 'model_app')
1753+        apps = cache.get_apps()
1754+        self.assertEquals(len(apps), 1)
1755+        self.assertEquals(apps[0].__name__, 'model_app.othermodels')
1756+
1757+    def test_empty_models(self):
1758+        """
1759+        Test that modules that don't contain models are not returned
1760+        """
1761+        settings.INSTALLED_APPS = ('nomodel_app',)
1762+        self.assertEqual(cache.get_apps(), [])
1763+        self.assertTrue(cache.app_cache_ready())
1764+
1765+    def test_db_prefix_exception(self):
1766+        """
1767+        Test that an exception is raised if two app instances
1768+        have the same db_prefix attribute
1769+        """
1770+        settings.INSTALLED_APPS = ('nomodel_app.app.MyApp',
1771+                                   'model_app.app.MyOtherApp')
1772+        self.assertRaises(ImproperlyConfigured, cache.get_apps)
1773+
1774+
1775+class GetAppTests(AppCacheTestCase):
1776+    """Tests for the get_app function"""
1777+
1778+    def test_installed_apps(self):
1779+        """
1780+        Test that the correct module is returned when the app was installed
1781+        via the INSTALLED_APPS setting
1782+        """
1783+        settings.INSTALLED_APPS = ('model_app',)
1784+        mod = cache.get_app('model_app')
1785+        self.assertTrue(cache.app_cache_ready())
1786+        self.assertEquals(mod.__name__, 'model_app.models')
1787+
1788+    def test_not_found_exception(self):
1789+        """
1790+        Test that an ImproperlyConfigured exception is raised if an app
1791+        could not be found
1792+        """
1793+        self.assertRaises(ImproperlyConfigured, cache.get_app,
1794+                          'notarealapp')
1795+        self.assertTrue(cache.app_cache_ready())
1796+
1797+    def test_emptyOK(self):
1798+        """
1799+        Test that None is returned if emptyOK is True and the module
1800+        has no models
1801+        """
1802+        settings.INSTALLED_APPS = ('nomodel_app',)
1803+        module = cache.get_app('nomodel_app', emptyOK=True)
1804+        self.assertTrue(cache.app_cache_ready())
1805+        self.failUnless(module is None)
1806+
1807+    def test_exception_if_no_models(self):
1808+        """
1809+        Test that an ImproperlyConfigured exception is raised if the app
1810+        has no modules and the emptyOK arg is False
1811+        """
1812+        settings.INSTALLED_APPS = ('nomodel_app',)
1813+        self.assertRaises(ImproperlyConfigured, cache.get_app,
1814+                          'nomodel_app')
1815+        self.assertTrue(cache.app_cache_ready())
1816+
1817+
1818+class GetAppErrorsTests(AppCacheTestCase):
1819+    """Tests for the get_app_errors function"""
1820+
1821+    def test_get_app_errors(self):
1822+        """Test that the function returns an empty dict"""
1823+        self.assertEqual(cache.get_app_errors(), {})
1824+        self.assertTrue(cache.app_cache_ready())
1825+
1826+
1827+class GetModelsTests(AppCacheTestCase):
1828+    """Tests for the get_models function"""
1829+
1830+    def test_installed(self):
1831+        """
1832+        Test that only models from apps are returned that are listed in
1833+        the INSTALLED_APPS setting
1834+        """
1835+        from anothermodel_app.models import Person
1836+        from model_app.models import Person
1837+        settings.INSTALLED_APPS = ('model_app',)
1838+        models = cache.get_models()
1839+        self.assertTrue(cache.app_cache_ready())
1840+        self.assertEqual(models, [Person])
1841+
1842+    def test_not_only_installed(self):
1843+        """
1844+        Test that not only installed models are returned
1845+        """
1846+        from anothermodel_app.models import Job, Person, Contact
1847+        from model_app.models import Person as p2
1848+        settings.INSTALLED_APPS = ('model_app',)
1849+        models = cache.get_models(only_installed=False)
1850+        self.assertTrue(cache.app_cache_ready())
1851+        self.assertEqual(models, [Job, Person, Contact, p2])
1852+
1853+    def test_app_mod(self):
1854+        """
1855+        Test that the correct models are returned if an models module is
1856+        passed and the app is listed in INSTALLED_APPS
1857+        """
1858+        from model_app import models
1859+        from model_app.models import Person
1860+        settings.INSTALLED_APPS = ('model_app', 'anothermodel_app')
1861+        models = cache.get_models(app_mod=models)
1862+        self.assertTrue(cache.app_cache_ready())
1863+        self.assertEqual(models, [Person])
1864+
1865+    def test_app_mod_not_installed(self):
1866+        """
1867+        Test that no models are returned when a models module is
1868+        passed and the app is _not_ listed in INSTALLED_APPS
1869+        """
1870+        from model_app import models
1871+        from model_app.models import Person
1872+        models = cache.get_models(app_mod=models)
1873+        self.assertEqual(models, [])
1874+
1875+    def test_include_auto_created(self):
1876+        """
1877+        Test that auto created models are included
1878+        """
1879+        settings.INSTALLED_APPS = ('anothermodel_app',)
1880+        models = cache.get_models(include_auto_created=True)
1881+        self.assertTrue(cache.app_cache_ready())
1882+        from anothermodel_app.models import Job, Person
1883+        self.assertEqual(models[0], Job)
1884+        self.assertEqual(models[1].__name__, 'Person_jobs')
1885+        self.assertEqual(models[2], Person)
1886+
1887+    def test_related_objects_cache(self):
1888+        """
1889+        Test that the related objects cache is filled correctly
1890+        """
1891+        from anothermodel_app.models import Contact
1892+        self.assertEqual(Contact._meta.get_all_field_names(),
1893+                         ['id', 'person'])
1894+
1895+    def test_related_many_to_many_cache(self):
1896+        """
1897+        Test that the related m2m cache is filled correctly
1898+        """
1899+        from anothermodel_app.models import Job
1900+        self.assertEqual(Job._meta.get_all_field_names(),
1901+                         ['id', 'name', 'person'])
1902+
1903+
1904+class GetModelTests(AppCacheTestCase):
1905+    """Tests for the get_model function"""
1906+
1907+    def test_seeded_only_installed_valid(self):
1908+        """
1909+        Test that the correct model is returned if the cache is seeded
1910+        and only models from apps listed in INSTALLED_APPS should be returned
1911+        """
1912+        settings.INSTALLED_APPS = ('model_app',)
1913+        model = cache.get_model('model_app', 'Person')
1914+        self.assertEqual(model.__name__, 'Person')
1915+        self.assertTrue(cache.app_cache_ready())
1916+
1917+    def test_seeded_only_installed_invalid(self):
1918+        """
1919+        Test that None is returned if the cache is seeded but the model
1920+        was not registered with the cache
1921+        """
1922+        model = cache.get_model('model_app', 'Person')
1923+        self.assertEqual(model, None)
1924+        self.assertTrue(cache.app_cache_ready())
1925+
1926+    def test_unseeded_only_installed_invalid(self):
1927+        """
1928+        Test that None is returned if the cache is unseeded and the model
1929+        was not registered with the cache
1930+        """
1931+        model = cache.get_model('model_app', 'Person', seed_cache=False)
1932+        self.assertEqual(model, None)
1933+        self.assertFalse(cache.app_cache_ready())
1934+
1935+    def test_seeded_all_models_valid(self):
1936+        """
1937+        Test that the correct model is returned if the cache is seeded and
1938+        all models (including unbound) should be returned
1939+        """
1940+        cache._populate()
1941+        from model_app.models import Person
1942+        model = cache.get_model('model_app', 'Person', only_installed=False)
1943+        self.assertEquals(model, Person)
1944+
1945+    def test_seeded_all_models_invalid(self):
1946+        """
1947+        Test that None is returned if the cache is seeded and all models
1948+        should be returned, but the model wasnt registered with the cache
1949+        """
1950+        cache._populate()
1951+        model = cache.get_model('model_app', 'Person', only_installed=False)
1952+        self.assertEquals(model, None)
1953+
1954+    def test_unseeded_all_models_valid(self):
1955+        """
1956+        Test that the correct model is returned if the cache is unseeded and
1957+        all models should be returned
1958+        """
1959+        from model_app.models import Person
1960+        model = cache.get_model('model_app', 'Person', seed_cache=False, only_installed=False)
1961+        self.assertEquals(model, Person)
1962+
1963+    def test_unseeded_all_models_invalid(self):
1964+        """
1965+        Test that None is returned if the cache is unseeded, all models should
1966+        be returned but the model wasn't registered with the cache
1967+        """
1968+        model = cache.get_model('model_app', 'Person', seed_cache=False, only_installed=False)
1969+        self.assertEquals(model, None)
1970+
1971+class LoadAppTests(AppCacheTestCase):
1972+    """Tests for the load_app function"""
1973+
1974+    def test_with_models(self):
1975+        """
1976+        Test that an app instance is created and the models
1977+        module is returned
1978+        """
1979+        mod = cache.load_app('model_app')
1980+        app = cache.loaded_apps[0]
1981+        self.assertEqual(len(cache.loaded_apps), 1)
1982+        self.assertEqual(app._meta.name, 'model_app')
1983+        self.assertEqual(app._meta.models_module.__name__, 'model_app.models')
1984+        self.assertEqual(mod.__name__, 'model_app.models')
1985+
1986+    def test_with_inheritance(self):
1987+        from model_app.app import MyApp
1988+        mod = cache.load_app('model_app.app.MyOtherApp')
1989+        app = cache.loaded_apps[0]
1990+        self.assertEqual(app._meta.name, 'model_app')
1991+        self.assertEqual(app._meta.models_module.__name__, 'model_app.othermodels')
1992+        self.assertEqual(mod.__name__, 'model_app.othermodels')
1993+        self.assertEqual(app.__class__.__bases__, (MyApp,))
1994+        self.assertEqual(app._meta.models_path, 'model_app.othermodels')
1995+        self.assertEqual(app._meta.db_prefix, 'nomodel_app')
1996+        self.assertEqual(app._meta.verbose_name, 'model_app')
1997+
1998+    def test_with_multiple_inheritance(self):
1999+        from model_app.app import MyOtherApp
2000+        from django.apps import App
2001+        mod = cache.load_app('model_app.app.MySecondApp')
2002+        app = cache.loaded_apps[0]
2003+        self.assertEqual(app._meta.name, 'model_app')
2004+        self.assertEqual(app._meta.models_module.__name__, 'model_app.models')
2005+        self.assertEqual(mod.__name__, 'model_app.models')
2006+        self.assertEqual(app.__class__.__bases__, (MyOtherApp,))
2007+        self.assertEqual(app._meta.models_path, 'model_app.models')
2008+        self.assertEqual(app._meta.db_prefix, 'nomodel_app')
2009+        self.assertEqual(app._meta.verbose_name, 'model_app')
2010+
2011+    def test_with_complicated_inheritance(self):
2012+        from model_app.app import MySecondApp, YetAnotherApp
2013+        from django.apps import App
2014+        mod = cache.load_app('model_app.app.MyThirdApp')
2015+        app = cache.loaded_apps[0]
2016+        self.assertEqual(app._meta.name, 'model_app')
2017+        self.assertEqual(app._meta.models_module.__name__, 'model_app.yetanother')
2018+        self.assertEqual(mod.__name__, 'model_app.yetanother')
2019+        self.assertEqual(app.__class__.__bases__, (YetAnotherApp, MySecondApp))
2020+        self.assertEqual(app._meta.models_path, 'model_app.yetanother')
2021+        self.assertEqual(app._meta.db_prefix, 'nomodel_app')
2022+        self.assertEqual(app._meta.verbose_name, 'model_app')
2023+
2024+    def test_with_custom_models(self):
2025+        """
2026+        Test that custom models are imported correctly, if the App specifies
2027+        an models_path attribute
2028+        """
2029+        from model_app.app import MyApp
2030+        mod = cache.load_app('model_app.app.MyApp', can_postpone=False)
2031+        app = cache.loaded_apps[0]
2032+        self.assertEqual(app._meta.models_module.__name__, 'model_app.othermodels')
2033+        self.assertTrue(isinstance(app, MyApp))
2034+        self.assertEqual(mod.__name__, 'model_app.othermodels')
2035+
2036+    def test_without_models(self):
2037+        """
2038+        Test that an app instance is created even when there are
2039+        no models provided
2040+        """
2041+        mod = cache.load_app('nomodel_app')
2042+        app = cache.loaded_apps[0]
2043+        self.assertEqual(len(cache.loaded_apps), 1)
2044+        self.assertEqual(app._meta.name, 'nomodel_app')
2045+        self.assertEqual(mod, None)
2046+
2047+    def test_loading_the_same_app_twice(self):
2048+        """
2049+        Test that loading the same app twice results in only one app instance
2050+        being created
2051+        """
2052+        mod = cache.load_app('model_app')
2053+        mod2 = cache.load_app('model_app')
2054+        self.assertEqual(len(cache.loaded_apps), 1)
2055+        self.assertEqual(mod.__name__, 'model_app.models')
2056+        self.assertEqual(mod2.__name__, 'model_app.models')
2057+
2058+    def test_importerror(self):
2059+        """
2060+        Test that an ImportError exception is raised if a package cannot
2061+        be imported
2062+        """
2063+        self.assertRaises(ImportError, cache.load_app, 'garageland')
2064+
2065+
2066+class RegisterModelsTests(AppCacheTestCase):
2067+    """Tests for the register_models function"""
2068+
2069+    def test_seeded_cache(self):
2070+        """
2071+        Test that the models are attached to the correct app instance
2072+        in a seeded cache
2073+        """
2074+        settings.INSTALLED_APPS = ('model_app',)
2075+        cache._populate()
2076+        self.assertTrue(cache.app_cache_ready())
2077+        app_models = cache.loaded_apps[0]._meta.models.values()
2078+        self.assertEqual(len(app_models), 1)
2079+        self.assertEqual(app_models[0].__name__, 'Person')
2080+
2081+    def test_seeded_cache_invalid_app(self):
2082+        """
2083+        Test that registering models with an app that doesn't have an app
2084+        instance works
2085+        """
2086+        settings.INSTALLED_APPS = ('model_app',)
2087+        cache._populate()
2088+        self.assertTrue(cache.app_cache_ready())
2089+        from model_app.models import Person
2090+        cache.register_models('model_app_NONEXISTENT', *(Person,))
2091+        self.assertEquals(cache.app_models['model_app_NONEXISTENT']['person'], Person)
2092+
2093+    def test_unseeded_cache(self):
2094+        """
2095+        Test that models can be registered with an unseeded cache
2096+        """
2097+        from model_app.models import Person
2098+        self.assertFalse(cache.app_cache_ready())
2099+        self.assertEquals(cache.app_models['model_app']['person'], Person)
2100+
2101+
2102+class FindAppTests(AppCacheTestCase):
2103+    """Tests for the find_app function"""
2104+
2105+    def test_seeded(self):
2106+        """
2107+        Test that the correct app is returned when the cache is seeded
2108+        """
2109+        from django.apps import App
2110+        settings.INSTALLED_APPS = ('model_app',)
2111+        cache._populate()
2112+        self.assertTrue(cache.app_cache_ready())
2113+        app = cache.find_app('model_app')
2114+        self.assertEquals(app._meta.name, 'model_app')
2115+        self.assertTrue(isinstance(app, App))
2116+        self.assertEquals(app.__repr__(), '<App: model_app>')
2117+
2118+    def test_seeded_invalid(self):
2119+        """
2120+        Test that None is returned if an app could not be found
2121+        """
2122+        settings.INSTALLED_APPS = ('model_app',)
2123+        cache._populate()
2124+        self.assertTrue(cache.app_cache_ready())
2125+        app = cache.find_app('model_app_NOTVALID')
2126+        self.assertEquals(app, None)
2127+
2128+    def test_unseeded(self):
2129+        """
2130+        Test that the correct app is returned when the cache is unseeded
2131+        """
2132+        from django.apps import App
2133+        cache.load_app('model_app')
2134+        self.assertFalse(cache.app_cache_ready())
2135+        app = cache.find_app('model_app')
2136+        self.assertEquals(app._meta.name, 'model_app')
2137+        self.assertTrue(isinstance(app, App))
2138+
2139+    def test_option_override(self):
2140+        """
2141+        Tests that options of the app can be overridden in the settings
2142+        """
2143+        settings.INSTALLED_APPS = (
2144+            ('django.contrib.admin', {
2145+                'spam': 'spam',
2146+            }),
2147+            ('model_app.app.MyOverrideApp', {
2148+                'db_prefix': 'foobar_prefix',
2149+                'eggs': 'eggs',
2150+            }),
2151+        )
2152+        cache._populate()
2153+        admin = cache.find_app('admin')
2154+        self.assertRaises(AttributeError, lambda: admin._meta.spam)
2155+        self.assertEquals(admin.spam, 'spam')
2156+        model_app = cache.find_app('model_app')
2157+        self.assertEquals(model_app._meta.db_prefix, 'foobar_prefix')
2158+        self.assertEquals(model_app.eggs, 'eggs')
2159+
2160+    def test_conflicting_option_override(self):
2161+        """
2162+        Tests that when overrdiding the db_prefix option in the settings
2163+        it still throws an exception
2164+        """
2165+        settings.INSTALLED_APPS = (
2166+            'nomodel_app.app.MyApp',
2167+            ('model_app.app.MyOtherApp', {
2168+                'db_prefix': 'nomodel_app',
2169+            }),
2170+        )
2171+        self.assertRaises(ImproperlyConfigured, cache._populate)
2172+
2173+    def test_class_attribute(self):
2174+        """
2175+        Tests that class attributes of apps are correctly set in the
2176+        instances, not only the _meta options.
2177+        """
2178+        settings.INSTALLED_APPS = ('model_app.app.MyApp',)
2179+        cache._populate()
2180+        model_app = cache.find_app('model_app')
2181+        self.assertEquals(model_app._meta.db_prefix, 'model_app')
2182+        self.assertEquals(model_app.some_attribute, True)
2183+
2184+
2185+class SignalTests(AppCacheTestCase):
2186+    """Tests for the signals"""
2187+
2188+    def setUp(self):
2189+        super(SignalTests, self).setUp()
2190+        self.signal_fired = False
2191+
2192+    def test_app_loaded(self):
2193+        """
2194+        Test the app_loaded signal
2195+        """
2196+        # connect the callback before the cache is initialized
2197+        def app_loaded_callback(sender, app, **kwargs):
2198+            self.assertEqual(app._meta.name, 'model_app')
2199+            self.signal_fired = True
2200+        app_loaded.connect(app_loaded_callback)
2201+
2202+        settings.INSTALLED_APPS = ('model_app',)
2203+        cache._populate()
2204+        self.assertTrue(cache.app_cache_ready())
2205+        self.assertTrue(self.signal_fired)
2206+
2207+    def test_post_apps_loaded(self):
2208+        """
2209+        Test the post_apps_loaded signal
2210+        """
2211+        settings.INSTALLED_APPS = ('model_app', 'anothermodel_app')
2212+        def callback(sender, apps, **kwargs):
2213+            self.assertEqual(len(apps), 2)
2214+            self.assertEqual(apps[0]._meta.name, 'model_app')
2215+            self.assertEqual(apps[1]._meta.name, 'anothermodel_app')
2216+            self.signal_fired = True
2217+        post_apps_loaded.connect(callback)
2218+        cache._populate()
2219+        self.assertTrue(cache.app_cache_ready())
2220+        self.assertTrue(self.signal_fired)
2221+
2222+
2223+class EggLoadingTests(AppCacheTestCase):
2224+    """Tests loading apps from eggs"""
2225+
2226+    def setUp(self):
2227+        super(EggLoadingTests, self).setUp()
2228+        self.egg_dir = '%s/eggs' % os.path.abspath(os.path.dirname(__file__))
2229+        self.old_path = sys.path[:]
2230+
2231+    def tearDown(self):
2232+        super(EggLoadingTests, self).tearDown()
2233+        sys.path = self.old_path
2234+
2235+    def test_egg1(self):
2236+        """
2237+        Models module can be loaded from an app in an egg
2238+        """
2239+        egg_name = '%s/modelapp.egg' % self.egg_dir
2240+        sys.path.append(egg_name)
2241+        models = cache.load_app('app_with_models')
2242+        self.assertFalse(models is None)
2243+
2244+    def test_egg2(self):
2245+        """
2246+        Loading an app from an egg that has no models returns no models
2247+        (and no error)
2248+        """
2249+        egg_name = '%s/nomodelapp.egg' % self.egg_dir
2250+        sys.path.append(egg_name)
2251+        models = cache.load_app('app_no_models')
2252+        self.assertTrue(models is None)
2253+
2254+    def test_egg3(self):
2255+        """
2256+        Models module can be loaded from an app located under an egg's
2257+        top-level package
2258+        """
2259+        egg_name = '%s/omelet.egg' % self.egg_dir
2260+        sys.path.append(egg_name)
2261+        models = cache.load_app('omelet.app_with_models')
2262+        self.assertFalse(models is None)
2263+
2264+    def test_egg4(self):
2265+        """
2266+        Loading an app with no models from under the top-level egg package
2267+        generates no error
2268+        """
2269+        egg_name = '%s/omelet.egg' % self.egg_dir
2270+        sys.path.append(egg_name)
2271+        models = cache.load_app('omelet.app_no_models')
2272+        self.assertTrue(models is None)
2273+
2274+    def test_egg5(self):
2275+        """
2276+        Loading an app from an egg that has an import error in its models
2277+        module raises that error
2278+        """
2279+        egg_name = '%s/brokenapp.egg' % self.egg_dir
2280+        sys.path.append(egg_name)
2281+        self.assertRaises(ImportError, cache.load_app, 'broken_app')
2282+        try:
2283+            cache.load_app('broken_app')
2284+        except ImportError, e:
2285+            # Make sure the message is indicating the actual
2286+            # problem in the broken app.
2287+            self.assertTrue("modelz" in e.args[0])
2288+
2289+if __name__ == '__main__':
2290+    unittest.main()
2291diff --git a/tests/regressiontests/app_loading/eggs/brokenapp.egg b/tests/regressiontests/app_loading/eggs/brokenapp.egg
2292deleted file mode 100755
2293index 8aca671..0000000
2294Binary files a/tests/regressiontests/app_loading/eggs/brokenapp.egg and /dev/null differ
2295diff --git a/tests/regressiontests/app_loading/eggs/modelapp.egg b/tests/regressiontests/app_loading/eggs/modelapp.egg
2296deleted file mode 100755
2297index c2370b5..0000000
2298Binary files a/tests/regressiontests/app_loading/eggs/modelapp.egg and /dev/null differ
2299diff --git a/tests/regressiontests/app_loading/eggs/nomodelapp.egg b/tests/regressiontests/app_loading/eggs/nomodelapp.egg
2300deleted file mode 100755
2301index 5b8d217..0000000
2302Binary files a/tests/regressiontests/app_loading/eggs/nomodelapp.egg and /dev/null differ
2303diff --git a/tests/regressiontests/app_loading/eggs/omelet.egg b/tests/regressiontests/app_loading/eggs/omelet.egg
2304deleted file mode 100755
2305index bd1c687..0000000
2306Binary files a/tests/regressiontests/app_loading/eggs/omelet.egg and /dev/null differ
2307diff --git a/tests/regressiontests/app_loading/not_installed/__init__.py b/tests/regressiontests/app_loading/not_installed/__init__.py
2308deleted file mode 100644
2309index e69de29..0000000
2310diff --git a/tests/regressiontests/app_loading/not_installed/models.py b/tests/regressiontests/app_loading/not_installed/models.py
2311deleted file mode 100644
2312index 1e4b598..0000000
2313--- a/tests/regressiontests/app_loading/not_installed/models.py
2314+++ /dev/null
2315@@ -1,13 +0,0 @@
2316-from django.db import models
2317-
2318-
2319-class NotInstalledModel(models.Model):
2320-    pass
2321-
2322-
2323-class RelatedModel(models.Model):
2324-    not_installed = models.ForeignKey(NotInstalledModel)
2325-
2326-
2327-class M2MRelatedModel(models.Model):
2328-    not_installed = models.ManyToManyField(NotInstalledModel)
2329diff --git a/tests/regressiontests/app_loading/tests.py b/tests/regressiontests/app_loading/tests.py
2330index 749f24a..5378a66 100644
2331--- a/tests/regressiontests/app_loading/tests.py
2332+++ b/tests/regressiontests/app_loading/tests.py
2333@@ -1,10 +1,8 @@
2334-import copy
2335 import os
2336 import sys
2337 import time
2338 
2339 from django.conf import Settings
2340-from django.db.models.loading import cache, load_app, get_model, get_models
2341 from django.utils.unittest import TestCase
2342 
2343 
2344@@ -23,103 +21,3 @@ class InstalledAppsGlobbingTest(TestCase):
2345         if hasattr(time, "tzset") and self.OLD_TZ:
2346             os.environ["TZ"] = self.OLD_TZ
2347             time.tzset()
2348-
2349-
2350-class EggLoadingTest(TestCase):
2351-
2352-    def setUp(self):
2353-        self.old_path = sys.path[:]
2354-        self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
2355-
2356-        # This test adds dummy applications to the app cache. These
2357-        # need to be removed in order to prevent bad interactions
2358-        # with the flush operation in other tests.
2359-        self.old_app_models = copy.deepcopy(cache.app_models)
2360-        self.old_app_store = copy.deepcopy(cache.app_store)
2361-
2362-    def tearDown(self):
2363-        sys.path = self.old_path
2364-        cache.app_models = self.old_app_models
2365-        cache.app_store = self.old_app_store
2366-
2367-    def test_egg1(self):
2368-        """Models module can be loaded from an app in an egg"""
2369-        egg_name = '%s/modelapp.egg' % self.egg_dir
2370-        sys.path.append(egg_name)
2371-        models = load_app('app_with_models')
2372-        self.assertFalse(models is None)
2373-
2374-    def test_egg2(self):
2375-        """Loading an app from an egg that has no models returns no models (and no error)"""
2376-        egg_name = '%s/nomodelapp.egg' % self.egg_dir
2377-        sys.path.append(egg_name)
2378-        models = load_app('app_no_models')
2379-        self.assertTrue(models is None)
2380-
2381-    def test_egg3(self):
2382-        """Models module can be loaded from an app located under an egg's top-level package"""
2383-        egg_name = '%s/omelet.egg' % self.egg_dir
2384-        sys.path.append(egg_name)
2385-        models = load_app('omelet.app_with_models')
2386-        self.assertFalse(models is None)
2387-
2388-    def test_egg4(self):
2389-        """Loading an app with no models from under the top-level egg package generates no error"""
2390-        egg_name = '%s/omelet.egg' % self.egg_dir
2391-        sys.path.append(egg_name)
2392-        models = load_app('omelet.app_no_models')
2393-        self.assertTrue(models is None)
2394-
2395-    def test_egg5(self):
2396-        """Loading an app from an egg that has an import error in its models module raises that error"""
2397-        egg_name = '%s/brokenapp.egg' % self.egg_dir
2398-        sys.path.append(egg_name)
2399-        self.assertRaises(ImportError, load_app, 'broken_app')
2400-        try:
2401-            load_app('broken_app')
2402-        except ImportError, e:
2403-            # Make sure the message is indicating the actual
2404-            # problem in the broken app.
2405-            self.assertTrue("modelz" in e.args[0])
2406-
2407-
2408-class GetModelsTest(TestCase):
2409-    def setUp(self):
2410-        from .not_installed import models
2411-        self.not_installed_module = models
2412-
2413-
2414-    def test_get_model_only_returns_installed_models(self):
2415-        self.assertEqual(
2416-            get_model("not_installed", "NotInstalledModel"), None)
2417-
2418-
2419-    def test_get_model_with_not_installed(self):
2420-        self.assertEqual(
2421-            get_model(
2422-                "not_installed", "NotInstalledModel", only_installed=False),
2423-            self.not_installed_module.NotInstalledModel)
2424-
2425-
2426-    def test_get_models_only_returns_installed_models(self):
2427-        self.assertFalse(
2428-            "NotInstalledModel" in
2429-            [m.__name__ for m in get_models()])
2430-
2431-
2432-    def test_get_models_with_app_label_only_returns_installed_models(self):
2433-        self.assertEqual(get_models(self.not_installed_module), [])
2434-
2435-
2436-    def test_get_models_with_not_installed(self):
2437-        self.assertTrue(
2438-            "NotInstalledModel" in [
2439-                m.__name__ for m in get_models(only_installed=False)])
2440-
2441-
2442-class NotInstalledModelsTest(TestCase):
2443-    def test_related_not_installed_model(self):
2444-        from .not_installed.models import NotInstalledModel
2445-        self.assertEqual(
2446-            set(NotInstalledModel._meta.get_all_field_names()),
2447-            set(["id", "relatedmodel", "m2mrelatedmodel"]))
2448diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
2449index 0b955ad..5350b2b 100644
2450--- a/tests/regressiontests/i18n/tests.py
2451+++ b/tests/regressiontests/i18n/tests.py
2452@@ -7,6 +7,7 @@ import pickle
2453 from threading import local
2454 
2455 from django.conf import settings
2456+from django.apps import cache
2457 from django.template import Template, Context
2458 from django.test import TestCase, RequestFactory
2459 from django.utils.formats import (get_format, date_format, time_format,
2460@@ -618,15 +619,22 @@ class ResolutionOrderI18NTests(TestCase):
2461         self.assertTrue(msgstr in result, ("The string '%s' isn't in the "
2462             "translation of '%s'; the actual result is '%s'." % (msgstr, msgid, result)))
2463 
2464+class MockedApp(object):
2465+    class Meta:
2466+        def path(self):
2467+            return os.path.join(
2468+                os.path.dirname(os.path.abspath(__file__)), 'resolution')
2469+    _meta = Meta()
2470+
2471 class AppResolutionOrderI18NTests(ResolutionOrderI18NTests):
2472 
2473     def setUp(self):
2474-        self.old_installed_apps = settings.INSTALLED_APPS
2475-        settings.INSTALLED_APPS = ['regressiontests.i18n.resolution'] + list(settings.INSTALLED_APPS)
2476+        self.old_loaded_apps = cache.loaded_apps
2477+        cache.loaded_apps = [MockedApp()] + cache.loaded_apps
2478         super(AppResolutionOrderI18NTests, self).setUp()
2479 
2480     def tearDown(self):
2481-        settings.INSTALLED_APPS = self.old_installed_apps
2482+        cache.loaded_apps = self.old_loaded_apps
2483         super(AppResolutionOrderI18NTests, self).tearDown()
2484 
2485     def test_app_translation(self):
2486diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py
2487index 4056948..4c14fb8 100644
2488--- a/tests/regressiontests/staticfiles_tests/tests.py
2489+++ b/tests/regressiontests/staticfiles_tests/tests.py
2490@@ -8,11 +8,11 @@ import sys
2491 import tempfile
2492 from StringIO import StringIO
2493 
2494-from django.template import loader, Context
2495 from django.conf import settings
2496 from django.core.exceptions import ImproperlyConfigured
2497 from django.core.files.storage import default_storage
2498 from django.core.management import call_command
2499+from django.template import loader, Context
2500 from django.test import TestCase
2501 from django.test.utils import override_settings
2502 from django.utils.encoding import smart_unicode
2503diff --git a/tests/regressiontests/templates/loaders.py b/tests/regressiontests/templates/loaders.py
2504index c2ae0bd..d5738be 100644
2505--- a/tests/regressiontests/templates/loaders.py
2506+++ b/tests/regressiontests/templates/loaders.py
2507@@ -5,6 +5,7 @@ Note: This test requires setuptools!
2508 """
2509 
2510 from django.conf import settings
2511+from django.apps import cache
2512 
2513 if __name__ == '__main__':
2514     settings.configure()
2515@@ -54,6 +55,12 @@ def create_egg(name, resources):
2516     egg._resources = resources
2517     sys.modules[name] = egg
2518 
2519+class MockedApp(object):
2520+    def __init__(self, name):
2521+        self.name = name
2522+    @property
2523+    def _meta(self):
2524+        return self
2525 
2526 class EggLoaderTest(unittest.TestCase):
2527     def setUp(self):
2528@@ -64,35 +71,34 @@ class EggLoaderTest(unittest.TestCase):
2529             os.path.normcase('templates/y.html') : StringIO.StringIO("y"),
2530             os.path.normcase('templates/x.txt') : StringIO.StringIO("x"),
2531         })
2532-        self._old_installed_apps = settings.INSTALLED_APPS
2533-        settings.INSTALLED_APPS = []
2534+        self.old_loaded_apps = cache.loaded_apps
2535 
2536     def tearDown(self):
2537-        settings.INSTALLED_APPS = self._old_installed_apps
2538+        cache.loaded_apps = self.old_loaded_apps
2539 
2540     def test_empty(self):
2541         "Loading any template on an empty egg should fail"
2542-        settings.INSTALLED_APPS = ['egg_empty']
2543+        cache.loaded_apps = [MockedApp('egg_empty')]
2544         egg_loader = EggLoader()
2545         self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html")
2546 
2547     def test_non_existing(self):
2548         "Template loading fails if the template is not in the egg"
2549-        settings.INSTALLED_APPS = ['egg_1']
2550+        cache.loaded_apps = [MockedApp('egg_1')]
2551         egg_loader = EggLoader()
2552         self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html")
2553 
2554     def test_existing(self):
2555         "A template can be loaded from an egg"
2556-        settings.INSTALLED_APPS = ['egg_1']
2557+        cache.loaded_apps = [MockedApp('egg_1')]
2558         egg_loader = EggLoader()
2559         contents, template_name = egg_loader.load_template_source("y.html")
2560         self.assertEqual(contents, "y")
2561         self.assertEqual(template_name, "egg:egg_1:templates/y.html")
2562 
2563     def test_not_installed(self):
2564-        "Loading an existent template from an egg not included in INSTALLED_APPS should fail"
2565-        settings.INSTALLED_APPS = []
2566+        "Loading an existent template from an egg not included in loaded_apps should fail"
2567+        cache.loaded_apps = []
2568         egg_loader = EggLoader()
2569         self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "y.html")
2570 
2571diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
2572index 1d1efe5..2ee883c 100644
2573--- a/tests/regressiontests/templates/tests.py
2574+++ b/tests/regressiontests/templates/tests.py
2575@@ -1,4 +1,5 @@
2576 # -*- coding: utf-8 -*-
2577+from django.apps import cache
2578 from django.conf import settings
2579 
2580 if __name__ == '__main__':
2581@@ -1613,17 +1614,24 @@ class Templates(unittest.TestCase):
2582             'static-statictag02': ('{% load static %}{% static base_css %}', {'base_css': 'admin/base.css'}, urljoin(settings.STATIC_URL, 'admin/base.css')),
2583         }
2584 
2585+class MockedApp(object):
2586+    def __init__(self, name):
2587+        self.name = name
2588+    @property
2589+    def _meta(self):
2590+        return self
2591+
2592 class TemplateTagLoading(unittest.TestCase):
2593 
2594     def setUp(self):
2595         self.old_path = sys.path[:]
2596-        self.old_apps = settings.INSTALLED_APPS
2597+        self.old_loaded_apps = cache.loaded_apps
2598         self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
2599         self.old_tag_modules = template_base.templatetags_modules
2600         template_base.templatetags_modules = []
2601 
2602     def tearDown(self):
2603-        settings.INSTALLED_APPS = self.old_apps
2604+        cache.loaded_apps = self.old_loaded_apps
2605         sys.path = self.old_path
2606         template_base.templatetags_modules = self.old_tag_modules
2607 
2608@@ -1640,7 +1648,7 @@ class TemplateTagLoading(unittest.TestCase):
2609         ttext = "{% load broken_egg %}"
2610         egg_name = '%s/tagsegg.egg' % self.egg_dir
2611         sys.path.append(egg_name)
2612-        settings.INSTALLED_APPS = ('tagsegg',)
2613+        cache.loaded_apps = (MockedApp('tagsegg'),)
2614         self.assertRaises(template.TemplateSyntaxError, template.Template, ttext)
2615         try:
2616             template.Template(ttext)
2617@@ -1652,7 +1660,7 @@ class TemplateTagLoading(unittest.TestCase):
2618         ttext = "{% load working_egg %}"
2619         egg_name = '%s/tagsegg.egg' % self.egg_dir
2620         sys.path.append(egg_name)
2621-        settings.INSTALLED_APPS = ('tagsegg',)
2622+        cache.loaded_apps = (MockedApp('tagsegg'),)
2623         t = template.Template(ttext)
2624 
2625 
2626diff --git a/tests/runtests.py b/tests/runtests.py
2627index ba66d2a..b955b6b 100755
2628--- a/tests/runtests.py
2629+++ b/tests/runtests.py
2630@@ -136,11 +136,9 @@ def setup(verbosity, test_labels):
2631     # in our tests.
2632     settings.MANAGERS = ("admin@djangoproject.com",)
2633 
2634-    # Load all the ALWAYS_INSTALLED_APPS.
2635-    # (This import statement is intentionally delayed until after we
2636-    # access settings because of the USE_I18N dependency.)
2637-    from django.db.models.loading import get_apps, load_app
2638-    get_apps()
2639+    # This import statement is intentionally delayed until after we
2640+    # access settings because of the USE_I18N dependency.
2641+    from django.db.models.loading import load_app
2642 
2643     # Load all the test model apps.
2644     test_labels_set = set([label.split('.')[0] for label in test_labels])