1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

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

This commit is contained in:
James Pulec 2015-09-29 10:52:26 -07:00 committed by Tim Graham
parent 5453aa66cf
commit f05722a08a
6 changed files with 63 additions and 18 deletions

View File

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

View File

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

View File

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

View File

@ -967,18 +967,23 @@ the same database table, which is almost certainly not what you want.
.. _abstract-related-name:
Be careful with ``related_name``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Be careful with ``related_name`` and ``related_query_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.
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` in an
abstract base class (only), part of the name should contain
``'%(app_label)s'`` and ``'%(class)s'``.
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

View File

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

View File

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