From 9239f1dda7b94f53d21efb8b5e4d056e24f4e906 Mon Sep 17 00:00:00 2001 From: Simon Charette 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):