Commit bc7d201b authored by Simon Charette's avatar Simon Charette
Browse files

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.
parent 2ed2db2e
Loading
Loading
Loading
Loading
+15 −6
Original line number Diff line number Diff line
@@ -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,11 +49,10 @@ def resolve_relation(scope_model, relation):
    """
    # Check for recursive relations
    if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
        if resolve_recursive_relationship:
            relation = scope_model

    # Look for an "app.Model" relation
    if isinstance(relation, six.string_types):
        if "." not in 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))
+5 −0
Original line number Diff line number Diff line
@@ -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
+43 −0
Original line number Diff line number Diff line
@@ -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):