summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2016-02-22 16:05:47 -0500
committerSimon Charette <charette.s@gmail.com>2016-02-29 22:13:54 -0500
commit48cf7516409c668bf11e24e6b7cd8eaea13ae3b8 (patch)
tree5b4c48da5e86dcf2cf6412fde0ce67dff01147fa
parente9234569f69e0afcc97b3b1ff5293bad1678a076 (diff)
downloaddjango-48cf7516409c668bf11e24e6b7cd8eaea13ae3b8.tar.gz
[1.9.x] Fixed #26186 -- Documented how app relative relationships of abstract models behave.
This partially reverts commit bc7d201bdbaeac14a49f51a9ef292d6312b4c45e. Thanks Tim for the review. Refs #25858. Backport of 0223e213dd690b6b6e0669f836a20efb10998c83 from master
-rw-r--r--django/db/models/fields/related.py21
-rw-r--r--docs/ref/models/fields.txt29
-rw-r--r--docs/releases/1.9.3.txt4
-rw-r--r--tests/model_fields/tests.py66
4 files changed, 77 insertions, 43 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 31434864cd..5d9b881c16 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -40,7 +40,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.
@@ -55,11 +55,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
@@ -303,11 +304,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):
"""
@@ -1584,11 +1580,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))
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index d8d047e3c9..20c50a0b12 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -1155,6 +1155,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
diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt
index e0447d2afe..e94d555ec0 100644
--- a/docs/releases/1.9.3.txt
+++ b/docs/releases/1.9.3.txt
@@ -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`).
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index 5aafdb2844..cc6639b032 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -251,24 +251,29 @@ class ForeignKeyTests(test.TestCase):
def test_abstract_model_app_relative_foreign_key(self):
test_apps = Apps(['model_fields', 'model_fields.tests'])
- class Refered(models.Model):
- class Meta:
- apps = test_apps
- app_label = 'model_fields'
-
class AbstractReferent(models.Model):
reference = models.ForeignKey('Refered', on_delete=models.CASCADE)
class Meta:
+ apps = test_apps
app_label = 'model_fields'
abstract = True
- class ConcreteReferent(AbstractReferent):
- class Meta:
- apps = test_apps
- app_label = 'tests'
+ def assert_app_model_resolved(label):
+ class Refered(models.Model):
+ class Meta:
+ apps = test_apps
+ app_label = label
+
+ class ConcreteReferent(AbstractReferent):
+ class Meta:
+ apps = test_apps
+ app_label = label
+
+ self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
- 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,33 +300,38 @@ class ManyToManyFieldTests(test.SimpleTestCase):
def test_abstract_model_app_relative_foreign_key(self):
test_apps = Apps(['model_fields', 'model_fields.tests'])
- class Refered(models.Model):
+ class AbstractReferent(models.Model):
+ reference = models.ManyToManyField('Refered', through='Through')
+
class Meta:
apps = test_apps
app_label = 'model_fields'
+ abstract = True
- class Through(models.Model):
- refered = models.ForeignKey('Refered', on_delete=models.CASCADE)
- referent = models.ForeignKey('tests.ConcreteReferent', on_delete=models.CASCADE)
+ def assert_app_model_resolved(label):
+ class Refered(models.Model):
+ class Meta:
+ apps = test_apps
+ app_label = label
- class Meta:
- apps = test_apps
- app_label = 'model_fields'
+ class Through(models.Model):
+ refered = models.ForeignKey('Refered', on_delete=models.CASCADE)
+ referent = models.ForeignKey('ConcreteReferent', on_delete=models.CASCADE)
- class AbstractReferent(models.Model):
- reference = models.ManyToManyField('Refered', through='Through')
+ class Meta:
+ apps = test_apps
+ app_label = label
- class Meta:
- app_label = 'model_fields'
- abstract = True
+ class ConcreteReferent(AbstractReferent):
+ class Meta:
+ apps = test_apps
+ app_label = label
- class ConcreteReferent(AbstractReferent):
- class Meta:
- apps = test_apps
- app_label = 'tests'
+ self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
+ self.assertEqual(ConcreteReferent.reference.through, Through)
- 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):