mirror of
https://github.com/django/django.git
synced 2024-12-23 01:25:58 +00:00
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'
|
||||
|
||||
|
||||
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))
|
||||
|
@ -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
|
||||
|
@ -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`).
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user