Code

Ticket #3591: app-loading.diff

File app-loading.diff, 100.8 KB (added by jezdez, 3 years ago)

correct patch

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