Browse Source

Fixed #25354 -- Added class/app_label interpolation for related_query_name.

James Pulec 9 years ago
parent
commit
f05722a08a

+ 7 - 0
django/db/models/fields/related.py

@@ -299,6 +299,13 @@ class RelatedField(Field):
                 }
                 self.remote_field.related_name = related_name
 
+            if self.remote_field.related_query_name:
+                related_query_name = force_text(self.remote_field.related_query_name) % {
+                    'class': cls.__name__.lower(),
+                    'app_label': cls._meta.app_label.lower(),
+                }
+                self.remote_field.related_query_name = related_query_name
+
             def resolve_related_class(model, related, field):
                 field.remote_field.model = related
                 field.do_related_class(related, model)

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

@@ -1344,6 +1344,9 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
         # That's now the name of the reverse filter
         Article.objects.filter(tag__name="important")
 
+    Like :attr:`related_name`, ``related_query_name`` supports app label and
+    class interpolation via :ref:`some special syntax <abstract-related-name>`.
+
 .. attribute:: ForeignKey.to_field
 
     The field on the related object that the relation is to. By default, Django

+ 4 - 0
docs/releases/1.10.txt

@@ -268,6 +268,10 @@ Models
 * :meth:`QuerySet.in_bulk() <django.db.models.query.QuerySet.in_bulk>`
   may be called without any arguments to return all objects in the queryset.
 
+* :attr:`~django.db.models.ForeignKey.related_query_name` now supports
+  app label and class interpolation using the ``'%(app_label)s'`` and
+  ``'%(class)s'`` strings.
+
 Requests and Responses
 ~~~~~~~~~~~~~~~~~~~~~~
 

+ 35 - 18
docs/topics/db/models.txt

@@ -967,18 +967,23 @@ the same database table, which is almost certainly not what you want.
 
 .. _abstract-related-name:
 
-Be careful with ``related_name``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you are using the :attr:`~django.db.models.ForeignKey.related_name` attribute on a ``ForeignKey`` or
-``ManyToManyField``, you must always specify a *unique* reverse name for the
-field. This would normally cause a problem in abstract base classes, since the
-fields on this class are included into each of the child classes, with exactly
-the same values for the attributes (including :attr:`~django.db.models.ForeignKey.related_name`) each time.
+Be careful with ``related_name`` and ``related_query_name``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-To work around this problem, when you are using :attr:`~django.db.models.ForeignKey.related_name` in an
-abstract base class (only), part of the name should contain
-``'%(app_label)s'`` and ``'%(class)s'``.
+If you are using :attr:`~django.db.models.ForeignKey.related_name` or
+:attr:`~django.db.models.ForeignKey.related_query_name` on a ``ForeignKey`` or
+``ManyToManyField``, you must always specify a *unique* reverse name and query
+name for the field. This would normally cause a problem in abstract base
+classes, since the fields on this class are included into each of the child
+classes, with exactly the same values for the attributes (including
+:attr:`~django.db.models.ForeignKey.related_name` and
+:attr:`~django.db.models.ForeignKey.related_query_name`) each time.
+
+To work around this problem, when you are using
+:attr:`~django.db.models.ForeignKey.related_name` or
+:attr:`~django.db.models.ForeignKey.related_query_name` in an abstract base
+class (only), part of the value should contain ``'%(app_label)s'`` and
+``'%(class)s'``.
 
 - ``'%(class)s'`` is replaced by the lower-cased name of the child class
   that the field is used in.
@@ -992,7 +997,11 @@ For example, given an app ``common/models.py``::
     from django.db import models
 
     class Base(models.Model):
-        m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")
+        m2m = models.ManyToManyField(
+            OtherModel,
+            related_name="%(app_label)s_%(class)s_related",
+            related_query_name="%(app_label)s_%(class)ss",
+        )
 
         class Meta:
             abstract = True
@@ -1011,12 +1020,15 @@ Along with another app ``rare/models.py``::
         pass
 
 The reverse name of the ``common.ChildA.m2m`` field will be
-``common_childa_related``, while the reverse name of the
-``common.ChildB.m2m`` field will be ``common_childb_related``, and finally the
-reverse name of the ``rare.ChildB.m2m`` field will be ``rare_childb_related``.
-It is up to you how you use the ``'%(class)s'`` and ``'%(app_label)s`` portion
-to construct your related name, but if you forget to use it, Django will raise
-errors when you perform system checks (or run :djadmin:`migrate`).
+``common_childa_related`` and the reverse query name will be ``common_childas``.
+The reverse name of the ``common.ChildB.m2m`` field will be
+``common_childb_related`` and the reverse query name will be
+``common_childbs``. Finally, the reverse name of the ``rare.ChildB.m2m`` field
+will be ``rare_childb_related`` and the reverse query name will be
+``rare_childbs``. It's up to you how you use the `'%(class)s'`` and
+``'%(app_label)s`` portion to construct your related name or related query name
+but if you forget to use it, Django will raise errors when you perform system
+checks (or run :djadmin:`migrate`).
 
 If you don't specify a :attr:`~django.db.models.ForeignKey.related_name`
 attribute for a field in an abstract base class, the default reverse name will
@@ -1027,6 +1039,11 @@ attribute was omitted, the reverse name for the ``m2m`` field would be
 ``childa_set`` in the ``ChildA`` case and ``childb_set`` for the ``ChildB``
 field.
 
+.. versionchanged:: 1.10
+
+   Interpolation of  ``'%(app_label)s'`` and ``'%(class)s'`` for
+   ``related_query_name`` was added.
+
 .. _multi-table-inheritance:
 
 Multi-table inheritance

+ 6 - 1
tests/model_inheritance/models.py

@@ -56,7 +56,12 @@ class Post(models.Model):
 
 @python_2_unicode_compatible
 class Attachment(models.Model):
-    post = models.ForeignKey(Post, models.CASCADE, related_name='attached_%(class)s_set')
+    post = models.ForeignKey(
+        Post,
+        models.CASCADE,
+        related_name='attached_%(class)s_set',
+        related_query_name='attached_%(app_label)s_%(class)ss',
+    )
     content = models.TextField()
 
     class Meta:

+ 9 - 0
tests/model_inheritance/tests.py

@@ -72,6 +72,15 @@ class ModelInheritanceTests(TestCase):
             AttributeError, getattr, post, "attached_%(class)s_set"
         )
 
+    def test_model_with_distinct_related_query_name(self):
+        self.assertQuerysetEqual(Post.objects.filter(attached_model_inheritance_comments__is_spam=True), [])
+
+        # The Post model doesn't have a related query accessor based on
+        # related_name (attached_comment_set).
+        msg = "Cannot resolve keyword 'attached_comment_set' into field."
+        with self.assertRaisesMessage(FieldError, msg):
+            Post.objects.filter(attached_comment_set__is_spam=True)
+
     def test_meta_fields_and_ordering(self):
         # Make sure Restaurant and ItalianRestaurant have the right fields in
         # the right order.