Fixed #25354 -- Added class/app_label interpolation for related_query_name.
This commit is contained in:
parent
5453aa66cf
commit
f05722a08a
|
@ -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
|
||||||
# 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
|
||||||
* :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.
|
||||||
|
|
||||||
.. _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
|
||||||
|
|
|
@ -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):
|
||||||
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.
|
||||||
|
|
Loading…
Reference in New Issue