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

This partially reverts commit bc7d201bdb.

Thanks Tim for the review.

Refs #25858.
This commit is contained in:
Simon Charette 2016-02-22 16:05:47 -05:00
parent eac1423f9e
commit 0223e213dd
4 changed files with 71 additions and 36 deletions

View File

@ -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,11 +50,12 @@ 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
relation = scope_model
# Look for an "app.Model" relation
elif isinstance(relation, six.string_types) and '.' not in relation:
relation = "%s.%s" % (scope_model._meta.app_label, 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))

View File

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

View File

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

View File

@ -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,11 +261,19 @@ class ForeignKeyTests(test.TestCase):
app_label = 'model_fields'
abstract = True
class ConcreteReferent(AbstractReferent):
class Meta:
app_label = 'tests'
def assert_app_model_resolved(label):
class Refered(models.Model):
class Meta:
app_label = label
self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
class ConcreteReferent(AbstractReferent):
class Meta:
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):
@ -295,14 +299,6 @@ 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 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')
@ -310,12 +306,27 @@ class ManyToManyFieldTests(test.SimpleTestCase):
app_label = 'model_fields'
abstract = True
class ConcreteReferent(AbstractReferent):
class Meta:
app_label = 'tests'
def assert_app_model_resolved(label):
class Refered(models.Model):
class Meta:
app_label = label
self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
self.assertEqual(ConcreteReferent.reference.through, Through)
class Through(models.Model):
refered = models.ForeignKey('Refered', on_delete=models.CASCADE)
referent = models.ForeignKey('ConcreteReferent', on_delete=models.CASCADE)
class Meta:
app_label = label
class ConcreteReferent(AbstractReferent):
class Meta:
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):