Kaydet (Commit) f05722a0 authored tarafından James Pulec's avatar James Pulec Kaydeden (comit) Tim Graham

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

üst 5453aa66
...@@ -299,6 +299,13 @@ class RelatedField(Field): ...@@ -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)
......
...@@ -1344,6 +1344,9 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in ...@@ -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
......
...@@ -268,6 +268,10 @@ Models ...@@ -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
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
......
...@@ -967,18 +967,23 @@ the same database table, which is almost certainly not what you want. ...@@ -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
``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.
To work around this problem, when you are using :attr:`~django.db.models.ForeignKey.related_name` in an If you are using :attr:`~django.db.models.ForeignKey.related_name` or
abstract base class (only), part of the name should contain :attr:`~django.db.models.ForeignKey.related_query_name` on a ``ForeignKey`` or
``'%(app_label)s'`` and ``'%(class)s'``. ``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 - ``'%(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``:: ...@@ -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``:: ...@@ -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 ...@@ -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
......
...@@ -56,7 +56,12 @@ class Post(models.Model): ...@@ -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:
......
...@@ -72,6 +72,15 @@ class ModelInheritanceTests(TestCase): ...@@ -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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment