From bc7d201bdbaeac14a49f51a9ef292d6312b4c45e Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Sat, 9 Jan 2016 18:05:57 -0500 Subject: [PATCH] Fixed #25858 -- Bound abstract model application relative relationships. Thanks to Karl Hobley for the report and Markus, Shai, Aymeric for their input and Tim for the review. --- django/db/models/fields/related.py | 21 ++++++++++----- docs/releases/1.9.2.txt | 5 ++++ tests/model_fields/tests.py | 43 ++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index c877ec17084..162a84793d4 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -34,7 +34,7 @@ from .reverse_related import ( RECURSIVE_RELATIONSHIP_CONSTANT = 'self' -def resolve_relation(scope_model, relation): +def resolve_relation(scope_model, relation, resolve_recursive_relationship=True): """ Transform relation into a model or fully-qualified model string of the form "app_label.ModelName", relative to scope_model. @@ -49,12 +49,11 @@ def resolve_relation(scope_model, relation): """ # Check for recursive relations if relation == RECURSIVE_RELATIONSHIP_CONSTANT: - relation = scope_model - + if resolve_recursive_relationship: + relation = scope_model # Look for an "app.Model" relation - if isinstance(relation, six.string_types): - if "." not in relation: - relation = "%s.%s" % (scope_model._meta.app_label, relation) + elif isinstance(relation, six.string_types) and '.' not in relation: + relation = "%s.%s" % (scope_model._meta.app_label, relation) return relation @@ -301,6 +300,11 @@ class RelatedField(Field): field.remote_field.model = related field.do_related_class(related, model) lazy_related_operation(resolve_related_class, cls, self.remote_field.model, field=self) + else: + # Bind a lazy reference to the app in which the model is defined. + self.remote_field.model = resolve_relation( + cls, self.remote_field.model, resolve_recursive_relationship=False + ) def get_forward_related_filter(self, obj): """ @@ -1553,6 +1557,11 @@ class ManyToManyField(RelatedField): lazy_related_operation(resolve_through_model, cls, self.remote_field.through, field=self) elif not cls._meta.swapped: self.remote_field.through = create_many_to_many_intermediary_model(self, cls) + else: + # Bind a lazy reference to the app in which the model is defined. + self.remote_field.through = resolve_relation( + cls, self.remote_field.through, resolve_recursive_relationship=False + ) # Add the descriptor for the m2m relation. setattr(cls, self.name, ManyToManyDescriptor(self.remote_field, reverse=False)) diff --git a/docs/releases/1.9.2.txt b/docs/releases/1.9.2.txt index d7857052ad0..e68a1b5cf96 100644 --- a/docs/releases/1.9.2.txt +++ b/docs/releases/1.9.2.txt @@ -30,3 +30,8 @@ Bugfixes ``db_index=True`` or ``unique=True`` to a ``CharField`` or ``TextField`` that already had the other specified, or when removing one of them from a field that had both (:ticket:`26034`). + +* Fixed a regression where defining a relation on an abstract model's field + using a string model name without an app_label no longer resolved that + reference to the abstract model's app if using that model in another + application (:ticket`25858`). \ No newline at end of file diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 14b56139dee..9a27dd46700 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -251,6 +251,25 @@ class ForeignKeyTests(test.TestCase): "Pending lookup added for a foreign key on an abstract model" ) + @isolate_apps('model_fields', 'model_fields.tests') + def test_abstract_model_app_relative_foreign_key(self): + class Refered(models.Model): + class Meta: + app_label = 'model_fields' + + class AbstractReferent(models.Model): + reference = models.ForeignKey('Refered', on_delete=models.CASCADE) + + class Meta: + app_label = 'model_fields' + abstract = True + + class ConcreteReferent(AbstractReferent): + class Meta: + app_label = 'tests' + + self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) + class ManyToManyFieldTests(test.SimpleTestCase): def test_abstract_model_pending_operations(self): @@ -273,6 +292,30 @@ class ManyToManyFieldTests(test.SimpleTestCase): "Pending lookup added for a many-to-many field on an abstract model" ) + @isolate_apps('model_fields', 'model_fields.tests') + def test_abstract_model_app_relative_foreign_key(self): + class Refered(models.Model): + class Meta: + app_label = 'model_fields' + + class Through(models.Model): + refered = models.ForeignKey('Refered', on_delete=models.CASCADE) + referent = models.ForeignKey('tests.ConcreteReferent', on_delete=models.CASCADE) + + class AbstractReferent(models.Model): + reference = models.ManyToManyField('Refered', through='Through') + + class Meta: + app_label = 'model_fields' + abstract = True + + class ConcreteReferent(AbstractReferent): + class Meta: + app_label = 'tests' + + self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) + self.assertEqual(ConcreteReferent.reference.through, Through) + class TextFieldTests(test.TestCase): def test_to_python(self):