瀏覽代碼

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

This partially reverts commit bc7d201bdbaeac14a49f51a9ef292d6312b4c45e.

Thanks Tim for the review.

Refs #25858.
Simon Charette 9 年之前
父節點
當前提交
0223e213dd
共有 4 個文件被更改,包括 71 次插入36 次删除
  1. 6 15
      django/db/models/fields/related.py
  2. 29 0
      docs/ref/models/fields.txt
  3. 4 0
      docs/releases/1.9.3.txt
  4. 32 21
      tests/model_fields/tests.py

+ 6 - 15
django/db/models/fields/related.py

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

+ 29 - 0
docs/ref/models/fields.txt

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

+ 32 - 21
tests/model_fields/tests.py

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