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:
parent
eac1423f9e
commit
0223e213dd
|
@ -35,7 +35,7 @@ from .reverse_related import (
|
||||||
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
|
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
|
Transform relation into a model or fully-qualified model string of the form
|
||||||
"app_label.ModelName", relative to scope_model.
|
"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
|
# Check for recursive relations
|
||||||
if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
|
if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
|
||||||
if resolve_recursive_relationship:
|
relation = scope_model
|
||||||
relation = scope_model
|
|
||||||
# Look for an "app.Model" relation
|
# Look for an "app.Model" relation
|
||||||
elif isinstance(relation, six.string_types) and '.' not in relation:
|
if isinstance(relation, six.string_types):
|
||||||
relation = "%s.%s" % (scope_model._meta.app_label, relation)
|
if "." not in relation:
|
||||||
|
relation = "%s.%s" % (scope_model._meta.app_label, relation)
|
||||||
|
|
||||||
return relation
|
return relation
|
||||||
|
|
||||||
|
@ -312,11 +313,6 @@ class RelatedField(Field):
|
||||||
field.remote_field.model = related
|
field.remote_field.model = related
|
||||||
field.do_related_class(related, model)
|
field.do_related_class(related, model)
|
||||||
lazy_related_operation(resolve_related_class, cls, self.remote_field.model, field=self)
|
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):
|
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)
|
lazy_related_operation(resolve_through_model, cls, self.remote_field.through, field=self)
|
||||||
elif not cls._meta.swapped:
|
elif not cls._meta.swapped:
|
||||||
self.remote_field.through = create_many_to_many_intermediary_model(self, cls)
|
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.
|
# Add the descriptor for the m2m relation.
|
||||||
setattr(cls, self.name, ManyToManyDescriptor(self.remote_field, reverse=False))
|
setattr(cls, self.name, ManyToManyDescriptor(self.remote_field, reverse=False))
|
||||||
|
|
|
@ -1157,6 +1157,35 @@ you can use the name of the model, rather than the model object itself::
|
||||||
# ...
|
# ...
|
||||||
pass
|
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
|
To refer to models defined in another application, you can explicitly specify
|
||||||
a model with the full application label. For example, if the ``Manufacturer``
|
a model with the full application label. For example, if the ``Manufacturer``
|
||||||
model above is defined in another application called ``production``, you'd
|
model above is defined in another application called ``production``, you'd
|
||||||
|
|
|
@ -52,3 +52,7 @@ Bugfixes
|
||||||
|
|
||||||
* Prevented ``ContentTypeManager`` instances from sharing their cache
|
* Prevented ``ContentTypeManager`` instances from sharing their cache
|
||||||
(:ticket:`26286`).
|
(: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`).
|
||||||
|
|
|
@ -254,10 +254,6 @@ class ForeignKeyTests(test.TestCase):
|
||||||
|
|
||||||
@isolate_apps('model_fields', 'model_fields.tests')
|
@isolate_apps('model_fields', 'model_fields.tests')
|
||||||
def test_abstract_model_app_relative_foreign_key(self):
|
def test_abstract_model_app_relative_foreign_key(self):
|
||||||
class Refered(models.Model):
|
|
||||||
class Meta:
|
|
||||||
app_label = 'model_fields'
|
|
||||||
|
|
||||||
class AbstractReferent(models.Model):
|
class AbstractReferent(models.Model):
|
||||||
reference = models.ForeignKey('Refered', on_delete=models.CASCADE)
|
reference = models.ForeignKey('Refered', on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
@ -265,11 +261,19 @@ class ForeignKeyTests(test.TestCase):
|
||||||
app_label = 'model_fields'
|
app_label = 'model_fields'
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
class ConcreteReferent(AbstractReferent):
|
def assert_app_model_resolved(label):
|
||||||
class Meta:
|
class Refered(models.Model):
|
||||||
app_label = 'tests'
|
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):
|
class ManyToManyFieldTests(test.SimpleTestCase):
|
||||||
|
@ -295,14 +299,6 @@ class ManyToManyFieldTests(test.SimpleTestCase):
|
||||||
|
|
||||||
@isolate_apps('model_fields', 'model_fields.tests')
|
@isolate_apps('model_fields', 'model_fields.tests')
|
||||||
def test_abstract_model_app_relative_foreign_key(self):
|
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):
|
class AbstractReferent(models.Model):
|
||||||
reference = models.ManyToManyField('Refered', through='Through')
|
reference = models.ManyToManyField('Refered', through='Through')
|
||||||
|
|
||||||
|
@ -310,12 +306,27 @@ class ManyToManyFieldTests(test.SimpleTestCase):
|
||||||
app_label = 'model_fields'
|
app_label = 'model_fields'
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
class ConcreteReferent(AbstractReferent):
|
def assert_app_model_resolved(label):
|
||||||
class Meta:
|
class Refered(models.Model):
|
||||||
app_label = 'tests'
|
class Meta:
|
||||||
|
app_label = label
|
||||||
|
|
||||||
self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
|
class Through(models.Model):
|
||||||
self.assertEqual(ConcreteReferent.reference.through, Through)
|
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):
|
class TextFieldTests(test.TestCase):
|
||||||
|
|
Loading…
Reference in New Issue