From 9239f1dda7b94f53d21efb8b5e4d056e24f4e906 Mon Sep 17 00:00:00 2001
From: Simon Charette <charette.s@gmail.com>
Date: Thu, 12 Feb 2015 01:28:24 -0500
Subject: [PATCH] Refs #24215 -- Prevented pending lookup pollution by abstract
 models.

---
 django/db/models/fields/related.py | 31 +++++++++++----------
 tests/model_fields/models.py       | 11 ++++++++
 tests/model_fields/tests.py        | 44 ++++++++++++++++++++++++++----
 3 files changed, 66 insertions(+), 20 deletions(-)

diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index c045391aaa..c991945217 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -291,20 +291,21 @@ class RelatedField(Field):
         if hasattr(sup, 'contribute_to_class'):
             sup.contribute_to_class(cls, name, virtual_only=virtual_only)
 
-        if not cls._meta.abstract and self.rel.related_name:
-            related_name = force_text(self.rel.related_name) % {
-                'class': cls.__name__.lower(),
-                'app_label': cls._meta.app_label.lower()
-            }
-            self.rel.related_name = related_name
-        other = self.rel.to
-        if isinstance(other, six.string_types) or other._meta.pk is None:
-            def resolve_related_class(field, model, cls):
-                field.rel.to = model
-                field.do_related_class(model, cls)
-            add_lazy_relation(cls, self, other, resolve_related_class)
-        else:
-            self.do_related_class(other, cls)
+        if not cls._meta.abstract:
+            if self.rel.related_name:
+                related_name = force_text(self.rel.related_name) % {
+                    'class': cls.__name__.lower(),
+                    'app_label': cls._meta.app_label.lower()
+                }
+                self.rel.related_name = related_name
+            other = self.rel.to
+            if isinstance(other, six.string_types) or other._meta.pk is None:
+                def resolve_related_class(field, model, cls):
+                    field.rel.to = model
+                    field.do_related_class(model, cls)
+                add_lazy_relation(cls, self, other, resolve_related_class)
+            else:
+                self.do_related_class(other, cls)
 
     @property
     def swappable_setting(self):
@@ -2605,7 +2606,7 @@ class ManyToManyField(RelatedField):
 
         # Populate some necessary rel arguments so that cross-app relations
         # work correctly.
-        if isinstance(self.rel.through, six.string_types):
+        if not cls._meta.abstract and isinstance(self.rel.through, six.string_types):
             def resolve_through_model(field, model, cls):
                 field.rel.through = model
             add_lazy_relation(cls, self, self.rel.through, resolve_through_model)
diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py
index 40499b0f8c..746071c916 100644
--- a/tests/model_fields/models.py
+++ b/tests/model_fields/models.py
@@ -367,3 +367,14 @@ class NullableUUIDModel(models.Model):
 
 class PrimaryKeyUUIDModel(models.Model):
     id = models.UUIDField(primary_key=True, default=uuid.uuid4)
+
+
+###############################################################################
+
+# See ticket #24215.
+class AbstractForeignFieldsModel(models.Model):
+    fk = models.ForeignKey('missing.FK')
+    m2m = models.ManyToManyField('missing.M2M', through='missing.Through')
+
+    class Meta:
+        abstract = True
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index 8894ef9158..379d9e8b52 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -21,11 +21,12 @@ from django.utils import six
 from django.utils.functional import lazy
 
 from .models import (
-    Bar, BigD, BigIntegerModel, BigS, BooleanModel, DataModel, DateTimeModel,
-    Document, FksToBooleans, FkToChar, FloatModel, Foo, GenericIPAddress,
-    IntegerModel, NullBooleanModel, PositiveIntegerModel,
-    PositiveSmallIntegerModel, Post, PrimaryKeyCharModel, RenamedField,
-    SmallIntegerModel, VerboseNameField, Whiz, WhizIter, WhizIterEmpty,
+    AbstractForeignFieldsModel, Bar, BigD, BigIntegerModel, BigS, BooleanModel,
+    DataModel, DateTimeModel, Document, FksToBooleans, FkToChar, FloatModel,
+    Foo, GenericIPAddress, IntegerModel, NullBooleanModel,
+    PositiveIntegerModel, PositiveSmallIntegerModel, Post, PrimaryKeyCharModel,
+    RenamedField, SmallIntegerModel, VerboseNameField, Whiz, WhizIter,
+    WhizIterEmpty,
 )
 
 
@@ -201,6 +202,39 @@ class ForeignKeyTests(test.TestCase):
         rel_name = Bar._meta.get_field('a').rel.related_name
         self.assertIsInstance(rel_name, six.text_type)
 
+    def test_abstract_model_pending_lookups(self):
+        """
+        Foreign key fields declared on abstract models should not add lazy relations to
+        resolve relationship declared as string. refs #24215
+        """
+        opts = AbstractForeignFieldsModel._meta
+        to_key = ('missing', 'FK')
+        fk_lookup = (AbstractForeignFieldsModel, opts.get_field('fk'))
+        self.assertFalse(
+            any(lookup[0:2] == fk_lookup for lookup in opts.apps._pending_lookups.get(to_key, [])),
+            'Pending lookup added for the abstract model foreign key `to` parameter'
+        )
+
+
+class ManyToManyFieldTests(test.TestCase):
+    def test_abstract_model_pending_lookups(self):
+        """
+        Many-to-many fields declared on abstract models should not add lazy relations to
+        resolve relationship declared as string. refs #24215
+        """
+        opts = AbstractForeignFieldsModel._meta
+        to_key = ('missing', 'M2M')
+        fk_lookup = (AbstractForeignFieldsModel, opts.get_field('m2m'))
+        self.assertFalse(
+            any(lookup[0:2] == fk_lookup for lookup in opts.apps._pending_lookups.get(to_key, [])),
+            'Pending lookup added for the abstract model many-to-many `to` parameter.'
+        )
+        through_key = ('missing', 'Through')
+        self.assertFalse(
+            any(lookup[0:2] == fk_lookup for lookup in opts.apps._pending_lookups.get(through_key, [])),
+            'Pending lookup added for the abstract model many-to-many `through` parameter.'
+        )
+
 
 class DateTimeFieldTests(unittest.TestCase):
     def test_datetimefield_to_python_usecs(self):