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 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): def resolve_related_class(model, related, field):
field.remote_field.model = related field.remote_field.model = related
field.do_related_class(related, model) 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 # That's now the name of the reverse filter
Article.objects.filter(tag__name="important") 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 .. attribute:: ForeignKey.to_field
The field on the related object that the relation is to. By default, Django 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>` * :meth:`QuerySet.in_bulk() <django.db.models.query.QuerySet.in_bulk>`
may be called without any arguments to return all objects in the queryset. 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 Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~

View File

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

View File

@ -56,7 +56,12 @@ class Post(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class Attachment(models.Model): 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() content = models.TextField()
class Meta: class Meta:

View File

@ -72,6 +72,15 @@ class ModelInheritanceTests(TestCase):
AttributeError, getattr, post, "attached_%(class)s_set" 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): def test_meta_fields_and_ordering(self):
# Make sure Restaurant and ItalianRestaurant have the right fields in # Make sure Restaurant and ItalianRestaurant have the right fields in
# the right order. # the right order.