diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index fa3edb4430..a9b0c26062 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -23,7 +23,6 @@ def get_validation_errors(outfile, app=None):
     validates all models of all installed apps. Writes errors, if any, to outfile.
     Returns number of errors.
     """
-    from django.conf import settings
     from django.db import models, connection
     from django.db.models.loading import get_app_errors
     from django.db.models.fields.related import RelatedObject
@@ -37,7 +36,19 @@ def get_validation_errors(outfile, app=None):
     for cls in models.get_models(app):
         opts = cls._meta
 
-        # Do field-specific validation.
+        # Check swappable attribute.
+        if opts.swapped:
+            try:
+                app_label, model_name = opts.swapped.split('.')
+            except ValueError:
+                e.add(opts, "%s is not of the form 'app_label.app_name'." % opts.swappable)
+                continue
+            if not models.get_model(app_label, model_name):
+                e.add(opts, "Model has been swapped out for '%s' which has not been installed or is abstract." % opts.swapped)
+            # No need to perform any other validation checks on a swapped model.
+            continue
+
+        # Model isn't swapped; do field-specific validation.
         for f in opts.local_fields:
             if f.name == 'id' and not f.primary_key and opts.pk.name == 'id':
                 e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name)
@@ -285,16 +296,6 @@ def get_validation_errors(outfile, app=None):
                     if r.get_accessor_name() == rel_query_name:
                         e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
 
-        # Check swappable attribute.
-        if opts.swapped:
-            try:
-                app_label, model_name = opts.swapped.split('.')
-            except ValueError:
-                e.add(opts, "%s is not of the form 'app_label.app_name'." % opts.swappable)
-                continue
-            if not models.get_model(app_label, model_name):
-                e.add(opts, "Model has been swapped out for '%s' which has not been installed or is abstract." % opts.swapped)
-
         # Check ordering attribute.
         if opts.ordering:
             for field_name in opts.ordering:
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index c065162aa0..2c35411d7c 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -1025,8 +1025,8 @@ class ForeignKey(RelatedField, Field):
 
     def contribute_to_related_class(self, cls, related):
         # Internal FK's - i.e., those with a related name ending with '+' -
-        # don't get a related descriptor.
-        if not self.rel.is_hidden():
+        # and swapped models don't get a related descriptor.
+        if not self.rel.is_hidden() and related.model._meta.swapped:
             setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
             if self.rel.limit_choices_to:
                 cls._meta.related_fkey_lookups.append(self.rel.limit_choices_to)
@@ -1265,8 +1265,8 @@ class ManyToManyField(RelatedField, Field):
 
     def contribute_to_related_class(self, cls, related):
         # Internal M2Ms (i.e., those with a related name ending with '+')
-        # don't get a related descriptor.
-        if not self.rel.is_hidden():
+        # and swapped models don't get a related descriptor.
+        if not self.rel.is_hidden() and not related.model._meta.swapped:
             setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related))
 
         # Set up the accessors for the column names on the m2m table
diff --git a/django/db/models/options.py b/django/db/models/options.py
index d2de96ea5c..d471bba262 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -418,13 +418,14 @@ class Options(object):
         # Collect also objects which are in relation to some proxy child/parent of self.
         proxy_cache = cache.copy()
         for klass in get_models(include_auto_created=True, only_installed=False):
-            for f in klass._meta.local_fields:
-                if f.rel and not isinstance(f.rel.to, six.string_types):
-                    if self == f.rel.to._meta:
-                        cache[RelatedObject(f.rel.to, klass, f)] = None
-                        proxy_cache[RelatedObject(f.rel.to, klass, f)] = None
-                    elif self.concrete_model == f.rel.to._meta.concrete_model:
-                        proxy_cache[RelatedObject(f.rel.to, klass, f)] = None
+            if not klass._meta.swapped:
+                for f in klass._meta.local_fields:
+                    if f.rel and not isinstance(f.rel.to, six.string_types):
+                        if self == f.rel.to._meta:
+                            cache[RelatedObject(f.rel.to, klass, f)] = None
+                            proxy_cache[RelatedObject(f.rel.to, klass, f)] = None
+                        elif self.concrete_model == f.rel.to._meta.concrete_model:
+                            proxy_cache[RelatedObject(f.rel.to, klass, f)] = None
         self._related_objects_cache = cache
         self._related_objects_proxy_cache = proxy_cache
 
@@ -460,9 +461,12 @@ class Options(object):
                 else:
                     cache[obj] = model
         for klass in get_models(only_installed=False):
-            for f in klass._meta.local_many_to_many:
-                if f.rel and not isinstance(f.rel.to, six.string_types) and self == f.rel.to._meta:
-                    cache[RelatedObject(f.rel.to, klass, f)] = None
+            if not klass._meta.swapped:
+                for f in klass._meta.local_many_to_many:
+                    if (f.rel
+                            and not isinstance(f.rel.to, six.string_types)
+                            and self == f.rel.to._meta):
+                        cache[RelatedObject(f.rel.to, klass, f)] = None
         if app_cache_ready():
             self._related_many_to_many_cache = cache
         return cache
diff --git a/tests/modeltests/invalid_models/invalid_models/models.py b/tests/modeltests/invalid_models/invalid_models/models.py
index 3f95d314e3..ccb6396352 100644
--- a/tests/modeltests/invalid_models/invalid_models/models.py
+++ b/tests/modeltests/invalid_models/invalid_models/models.py
@@ -303,13 +303,28 @@ class SwappedModel(models.Model):
     References to this model *should* raise a validation error.
     Requires TEST_SWAPPED_MODEL to be defined in the test environment;
     this is guaranteed by the test runner using @override_settings.
+
+    The foreign keys and m2m relations on this model *shouldn't*
+    install related accessors, so there shouldn't be clashes with
+    the equivalent names on the replacement.
     """
     name = models.CharField(max_length=100)
 
+    foreign = models.ForeignKey(Target, related_name='swappable_fk_set')
+    m2m = models.ManyToManyField(Target, related_name='swappable_m2m_set')
+
     class Meta:
         swappable = 'TEST_SWAPPED_MODEL'
 
 
+class ReplacementModel(models.Model):
+    """A replacement model for swapping purposes."""
+    name = models.CharField(max_length=100)
+
+    foreign = models.ForeignKey(Target, related_name='swappable_fk_set')
+    m2m = models.ManyToManyField(Target, related_name='swappable_m2m_set')
+
+
 class BadSwappableValue(models.Model):
     """A model that can be swapped out; during testing, the swappable
     value is not of the format app.model
diff --git a/tests/modeltests/invalid_models/tests.py b/tests/modeltests/invalid_models/tests.py
index 6050a20880..5f6224c45d 100644
--- a/tests/modeltests/invalid_models/tests.py
+++ b/tests/modeltests/invalid_models/tests.py
@@ -37,7 +37,7 @@ class InvalidModelTestCase(unittest.TestCase):
     # easier to set this up as an override than to require every developer
     # to specify a value in their test settings.
     @override_settings(
-        TEST_SWAPPED_MODEL='invalid_models.Target',
+        TEST_SWAPPED_MODEL='invalid_models.ReplacementModel',
         TEST_SWAPPED_MODEL_BAD_VALUE='not-a-model',
         TEST_SWAPPED_MODEL_BAD_MODEL='not_an_app.Target',
     )