diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 7fc7a092fd..c30ba03489 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -2,7 +2,7 @@ from operator import attrgetter
 
 from django.db import connection, connections, router
 from django.db.backends import util
-from django.db.models import signals, get_model
+from django.db.models import signals
 from django.db.models.fields import (AutoField, Field, IntegerField,
     PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist)
 from django.db.models.related import RelatedObject, PathInfo
@@ -18,8 +18,6 @@ from django import forms
 
 RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
 
-pending_lookups = {}
-
 
 def add_lazy_relation(cls, field, relation, operation):
     """
@@ -70,14 +68,14 @@ def add_lazy_relation(cls, field, relation, operation):
     # string right away. If get_model returns None, it means that the related
     # model isn't loaded yet, so we need to pend the relation until the class
     # is prepared.
-    model = get_model(app_label, model_name,
+    model = cls._meta.app_cache.get_model(app_label, model_name,
                       seed_cache=False, only_installed=False)
     if model:
         operation(field, model, cls)
     else:
         key = (app_label, model_name)
         value = (cls, field, operation)
-        pending_lookups.setdefault(key, []).append(value)
+        cls._meta.app_cache.pending_lookups.setdefault(key, []).append(value)
 
 
 def do_pending_lookups(sender, **kwargs):
@@ -85,7 +83,7 @@ def do_pending_lookups(sender, **kwargs):
     Handle any pending relations to the sending model. Sent from class_prepared.
     """
     key = (sender._meta.app_label, sender.__name__)
-    for cls, field, operation in pending_lookups.pop(key, []):
+    for cls, field, operation in sender._meta.app_cache.pending_lookups.pop(key, []):
         operation(field, sender, cls)
 
 signals.class_prepared.connect(do_pending_lookups)
@@ -1330,6 +1328,7 @@ def create_many_to_many_intermediary_model(field, klass):
         'unique_together': (from_, to),
         'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to},
         'verbose_name_plural': '%(from)s-%(to)s relationships' % {'from': from_, 'to': to},
+        'app_cache': field.model._meta.app_cache,
     })
     # Construct and return the new class.
     return type(str(name), (models.Model,), {
diff --git a/django/db/models/loading.py b/django/db/models/loading.py
index 61273e512a..075cae4c61 100644
--- a/django/db/models/loading.py
+++ b/django/db/models/loading.py
@@ -35,7 +35,11 @@ def _initialize():
         # Mapping of app_labels to errors raised when trying to import the app.
         app_errors = {},
 
+        # Pending lookups for lazy relations
+        pending_lookups = {},
+
         # -- Everything below here is only used when populating the cache --
+        loads_installed = True,
         loaded = False,
         handled = {},
         postponed = [],
@@ -56,12 +60,44 @@ class BaseAppCache(object):
 
     def __init__(self):
         self.__dict__ = _initialize()
+        # This stops _populate loading from INSTALLED_APPS and ignores the
+        # only_installed arguments to get_model[s]
+        self.loads_installed = False
 
     def _populate(self):
         """
         Stub method - this base class does no auto-loading.
         """
-        self.loaded = True
+        """
+        Fill in all the cache information. This method is threadsafe, in the
+        sense that every caller will see the same state upon return, and if the
+        cache is already initialised, it does no work.
+        """
+        if self.loaded:
+            return
+        if not self.loads_installed:
+            self.loaded = True
+            return
+        # Note that we want to use the import lock here - the app loading is
+        # in many cases initiated implicitly by importing, and thus it is
+        # possible to end up in deadlock when one thread initiates loading
+        # without holding the importer lock and another thread then tries to
+        # import something which also launches the app loading. For details of
+        # this situation see #18251.
+        imp.acquire_lock()
+        try:
+            if self.loaded:
+                return
+            for app_name in settings.INSTALLED_APPS:
+                if app_name in self.handled:
+                    continue
+                self.load_app(app_name, True)
+            if not self.nesting_level:
+                for app_name in self.postponed:
+                    self.load_app(app_name)
+                self.loaded = True
+        finally:
+            imp.release_lock()
 
     def _label_for(self, app_mod):
         """
@@ -169,12 +205,15 @@ class BaseAppCache(object):
 
         By default, models that aren't part of installed apps will *not*
         be included in the list of models. However, if you specify
-        only_installed=False, they will be.
+        only_installed=False, they will be. If you're using a non-default
+        AppCache, this argument does nothing - all models will be included.
 
         By default, models that have been swapped out will *not* be
         included in the list of models. However, if you specify
         include_swapped, they will be.
         """
+        if not self.loads_installed:
+            only_installed = False
         cache_key = (app_mod, include_auto_created, include_deferred, only_installed, include_swapped)
         try:
             return self._get_models_cache[cache_key]
@@ -212,6 +251,8 @@ class BaseAppCache(object):
 
         Returns None if no model is found.
         """
+        if not self.loads_installed:
+            only_installed = False
         if seed_cache:
             self._populate()
         if only_installed and app_label not in self.app_labels:
@@ -241,12 +282,6 @@ class BaseAppCache(object):
             model_dict[model_name] = model
         self._get_models_cache.clear()
 
-    def copy_from(self, other):
-        "Registers all models from the other cache into this one"
-        cache._populate()
-        for app_label, models in other.app_models.items():
-            self.register_models(app_label, *models.values())
-
 
 class AppCache(BaseAppCache):
     """
@@ -261,35 +296,6 @@ class AppCache(BaseAppCache):
     def __init__(self):
         self.__dict__ = self.__shared_state
 
-    def _populate(self):
-        """
-        Fill in all the cache information. This method is threadsafe, in the
-        sense that every caller will see the same state upon return, and if the
-        cache is already initialised, it does no work.
-        """
-        if self.loaded:
-            return
-        # Note that we want to use the import lock here - the app loading is
-        # in many cases initiated implicitly by importing, and thus it is
-        # possible to end up in deadlock when one thread initiates loading
-        # without holding the importer lock and another thread then tries to
-        # import something which also launches the app loading. For details of
-        # this situation see #18251.
-        imp.acquire_lock()
-        try:
-            if self.loaded:
-                return
-            for app_name in settings.INSTALLED_APPS:
-                if app_name in self.handled:
-                    continue
-                self.load_app(app_name, True)
-            if not self.nesting_level:
-                for app_name in self.postponed:
-                    self.load_app(app_name)
-                self.loaded = True
-        finally:
-            imp.release_lock()
-
 cache = AppCache()
 
 
diff --git a/tests/app_cache/__init__.py b/tests/app_cache/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/app_cache/models.py b/tests/app_cache/models.py
new file mode 100644
index 0000000000..1b4d33c2f9
--- /dev/null
+++ b/tests/app_cache/models.py
@@ -0,0 +1,17 @@
+from django.db import models
+from django.db.models.loading import BaseAppCache
+
+# We're testing app cache presence on load, so this is handy.
+
+new_app_cache = BaseAppCache()
+
+
+class TotallyNormal(models.Model):
+    name = models.CharField(max_length=255)
+
+
+class SoAlternative(models.Model):
+    name = models.CharField(max_length=255)
+
+    class Meta:
+        app_cache = new_app_cache
diff --git a/tests/app_cache/tests.py b/tests/app_cache/tests.py
new file mode 100644
index 0000000000..42598d90c7
--- /dev/null
+++ b/tests/app_cache/tests.py
@@ -0,0 +1,50 @@
+from __future__ import absolute_import
+import datetime
+from django.test import TransactionTestCase
+from django.utils.unittest import skipUnless
+from django.db import connection, DatabaseError, IntegrityError
+from django.db.models.fields import IntegerField, TextField, CharField, SlugField
+from django.db.models.fields.related import ManyToManyField, ForeignKey
+from django.db.models.loading import cache, BaseAppCache
+from django.db import models
+from .models import TotallyNormal, SoAlternative, new_app_cache
+
+
+class AppCacheTests(TransactionTestCase):
+    """
+    Tests the AppCache borg and non-borg versions
+    """
+
+    def test_models_py(self):
+        """
+        Tests that the models in the models.py file were loaded correctly.
+        """
+
+        self.assertEqual(cache.get_model("app_cache", "TotallyNormal"), TotallyNormal)
+        self.assertEqual(cache.get_model("app_cache", "SoAlternative"), None)
+
+        self.assertEqual(new_app_cache.get_model("app_cache", "TotallyNormal"), None)
+        self.assertEqual(new_app_cache.get_model("app_cache", "SoAlternative"), SoAlternative)
+
+    def test_dynamic_load(self):
+        """
+        Makes a new model at runtime and ensures it goes into the right place.
+        """
+        old_models = cache.get_models(cache.get_app("app_cache"))
+        # Construct a new model in a new app cache
+        body = {}
+        new_app_cache = BaseAppCache()
+        meta_contents = {
+            'app_label': "app_cache",
+            'app_cache': new_app_cache,
+        }
+        meta = type("Meta", tuple(), meta_contents)
+        body['Meta'] = meta
+        body['__module__'] = TotallyNormal.__module__
+        temp_model = type("SouthPonies", (models.Model,), body)
+        # Make sure it appeared in the right place!
+        self.assertEqual(
+            old_models,
+            cache.get_models(cache.get_app("app_cache")),
+        )
+        self.assertEqual(new_app_cache.get_model("app_cache", "SouthPonies"), temp_model)