Commit 0223e213 authored by Simon Charette's avatar Simon Charette
Browse files

Fixed #26186 -- Documented how app relative relationships of abstract models behave.

This partially reverts commit bc7d201b.

Thanks Tim for the review.

Refs #25858.
parent eac1423f
Loading
Loading
Loading
Loading
+6 −15
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ from .reverse_related import (
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'


def resolve_relation(scope_model, relation, resolve_recursive_relationship=True):
def resolve_relation(scope_model, relation):
    """
    Transform relation into a model or fully-qualified model string of the form
    "app_label.ModelName", relative to scope_model.
@@ -50,10 +50,11 @@ def resolve_relation(scope_model, relation, resolve_recursive_relationship=True)
    """
    # Check for recursive relations
    if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
        if resolve_recursive_relationship:
        relation = scope_model

    # Look for an "app.Model" relation
    elif isinstance(relation, six.string_types) and '.' not in relation:
    if isinstance(relation, six.string_types):
        if "." not in relation:
            relation = "%s.%s" % (scope_model._meta.app_label, relation)

    return relation
@@ -312,11 +313,6 @@ 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):
        """
@@ -1565,11 +1561,6 @@ 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))
+29 −0
Original line number Diff line number Diff line
@@ -1157,6 +1157,35 @@ you can use the name of the model, rather than the model object itself::
        # ...
        pass

Relationships defined this way on :ref:`abstract models
<abstract-base-classes>` are resolved when the model is subclassed as a
concrete model and are not relative to the abstract model's ``app_label``:

.. snippet::
    :filename: products/models.py

    from django.db import models

    class AbstractCar(models.Model):
        manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE)

        class Meta:
            abstract = True

.. snippet::
    :filename: production/models.py

    from django.db import models
    from products.models import AbstractCar

    class Manufacturer(models.Model):
        pass

    class Car(AbstractCar):
        pass

    # Car.manufacturer will point to `production.Manufacturer` here.

To refer to models defined in another application, you can explicitly specify
a model with the full application label. For example, if the ``Manufacturer``
model above is defined in another application called ``production``, you'd
+4 −0
Original line number Diff line number Diff line
@@ -52,3 +52,7 @@ Bugfixes

* Prevented ``ContentTypeManager`` instances from sharing their cache
  (:ticket:`26286`).

* Reverted a change in Django 1.9.2 (:ticket:`25858`) that prevented relative
  lazy relationships defined on abstract models to be resolved according to
  their concrete model's ``app_label`` (:ticket:`26186`).
+32 −21
Original line number Diff line number Diff line
@@ -254,10 +254,6 @@ class ForeignKeyTests(test.TestCase):

    @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)

@@ -265,12 +261,20 @@ class ForeignKeyTests(test.TestCase):
                app_label = 'model_fields'
                abstract = True

        def assert_app_model_resolved(label):
            class Refered(models.Model):
                class Meta:
                    app_label = label

            class ConcreteReferent(AbstractReferent):
                class Meta:
                app_label = 'tests'
                    app_label = label

            self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)

        assert_app_model_resolved('model_fields')
        assert_app_model_resolved('tests')


class ManyToManyFieldTests(test.SimpleTestCase):
    def test_abstract_model_pending_operations(self):
@@ -295,28 +299,35 @@ class ManyToManyFieldTests(test.SimpleTestCase):

    @isolate_apps('model_fields', 'model_fields.tests')
    def test_abstract_model_app_relative_foreign_key(self):
        class Refered(models.Model):
        class AbstractReferent(models.Model):
            reference = models.ManyToManyField('Refered', through='Through')

            class Meta:
                app_label = 'model_fields'
                abstract = True

        def assert_app_model_resolved(label):
            class Refered(models.Model):
                class Meta:
                    app_label = label

            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')
                referent = models.ForeignKey('ConcreteReferent', on_delete=models.CASCADE)

                class Meta:
                app_label = 'model_fields'
                abstract = True
                    app_label = label

            class ConcreteReferent(AbstractReferent):
                class Meta:
                app_label = 'tests'
                    app_label = label

            self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
            self.assertEqual(ConcreteReferent.reference.through, Through)

        assert_app_model_resolved('model_fields')
        assert_app_model_resolved('tests')


class TextFieldTests(test.TestCase):
    def test_to_python(self):