Fixed #26230 -- Made default_related_name affect related_query_name.
This commit is contained in:
parent
5fb9756eba
commit
b84f5ab4ec
|
@ -290,8 +290,13 @@ class RelatedField(Field):
|
||||||
|
|
||||||
if not cls._meta.abstract:
|
if not cls._meta.abstract:
|
||||||
if self.remote_field.related_name:
|
if self.remote_field.related_name:
|
||||||
related_name = force_text(self.remote_field.related_name) % {
|
related_name = self.remote_field.related_name
|
||||||
|
else:
|
||||||
|
related_name = self.opts.default_related_name
|
||||||
|
if related_name:
|
||||||
|
related_name = force_text(related_name) % {
|
||||||
'class': cls.__name__.lower(),
|
'class': cls.__name__.lower(),
|
||||||
|
'model_name': cls._meta.model_name.lower(),
|
||||||
'app_label': cls._meta.app_label.lower()
|
'app_label': cls._meta.app_label.lower()
|
||||||
}
|
}
|
||||||
self.remote_field.related_name = related_name
|
self.remote_field.related_name = related_name
|
||||||
|
|
|
@ -187,11 +187,6 @@ class ForeignObjectRel(object):
|
||||||
return None
|
return None
|
||||||
if self.related_name:
|
if self.related_name:
|
||||||
return self.related_name
|
return self.related_name
|
||||||
if opts.default_related_name:
|
|
||||||
return opts.default_related_name % {
|
|
||||||
'model_name': opts.model_name.lower(),
|
|
||||||
'app_label': opts.app_label.lower(),
|
|
||||||
}
|
|
||||||
return opts.model_name + ('_set' if self.multiple else '')
|
return opts.model_name + ('_set' if self.multiple else '')
|
||||||
|
|
||||||
def get_cache_name(self):
|
def get_cache_name(self):
|
||||||
|
|
|
@ -7,6 +7,7 @@ databases). The abstraction barrier only works one way: this module has to know
|
||||||
all about the internals of models in order to get the information it needs.
|
all about the internals of models in order to get the information it needs.
|
||||||
"""
|
"""
|
||||||
import copy
|
import copy
|
||||||
|
import warnings
|
||||||
from collections import Counter, Iterator, Mapping, OrderedDict
|
from collections import Counter, Iterator, Mapping, OrderedDict
|
||||||
from itertools import chain, count, product
|
from itertools import chain, count, product
|
||||||
from string import ascii_uppercase
|
from string import ascii_uppercase
|
||||||
|
@ -30,6 +31,7 @@ from django.db.models.sql.where import (
|
||||||
AND, OR, ExtraWhere, NothingNode, WhereNode,
|
AND, OR, ExtraWhere, NothingNode, WhereNode,
|
||||||
)
|
)
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.tree import Node
|
from django.utils.tree import Node
|
||||||
|
|
||||||
|
@ -1288,6 +1290,19 @@ class Query(object):
|
||||||
except FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
if name in self.annotation_select:
|
if name in self.annotation_select:
|
||||||
field = self.annotation_select[name].output_field
|
field = self.annotation_select[name].output_field
|
||||||
|
elif pos == 0:
|
||||||
|
for rel in opts.related_objects:
|
||||||
|
if (name == rel.related_model._meta.model_name and
|
||||||
|
rel.related_name == rel.related_model._meta.default_related_name):
|
||||||
|
related_name = rel.related_name
|
||||||
|
field = opts.get_field(related_name)
|
||||||
|
warnings.warn(
|
||||||
|
"Query lookup '%s' is deprecated in favor of "
|
||||||
|
"Meta.default_related_name '%s'."
|
||||||
|
% (name, related_name),
|
||||||
|
RemovedInDjango20Warning, 2
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
if field is not None:
|
if field is not None:
|
||||||
# Fields that contain one-to-many relations with a generic
|
# Fields that contain one-to-many relations with a generic
|
||||||
|
|
|
@ -138,6 +138,9 @@ details on these changes.
|
||||||
* Support for the ``django.core.files.storage.Storage.accessed_time()``,
|
* Support for the ``django.core.files.storage.Storage.accessed_time()``,
|
||||||
``created_time()``, and ``modified_time()`` methods will be removed.
|
``created_time()``, and ``modified_time()`` methods will be removed.
|
||||||
|
|
||||||
|
* Support for query lookups using the model name when
|
||||||
|
``Meta.default_related_name`` is set will be removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-1.10:
|
.. _deprecation-removed-in-1.10:
|
||||||
|
|
||||||
1.10
|
1.10
|
||||||
|
|
|
@ -1333,8 +1333,9 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
|
||||||
|
|
||||||
.. attribute:: ForeignKey.related_query_name
|
.. attribute:: ForeignKey.related_query_name
|
||||||
|
|
||||||
The name to use for the reverse filter name from the target model.
|
The name to use for the reverse filter name from the target model. It
|
||||||
Defaults to the value of :attr:`related_name` if it is set, otherwise it
|
defaults to the value of :attr:`related_name` or
|
||||||
|
:attr:`~django.db.models.Options.default_related_name` if set, otherwise it
|
||||||
defaults to the name of the model::
|
defaults to the name of the model::
|
||||||
|
|
||||||
# Declare the ForeignKey with related_query_name
|
# Declare the ForeignKey with related_query_name
|
||||||
|
|
|
@ -103,6 +103,8 @@ Django quotes column and table names behind the scenes.
|
||||||
The name that will be used by default for the relation from a related object
|
The name that will be used by default for the relation from a related object
|
||||||
back to this one. The default is ``<model_name>_set``.
|
back to this one. The default is ``<model_name>_set``.
|
||||||
|
|
||||||
|
This option also sets :attr:`~ForeignKey.related_query_name`.
|
||||||
|
|
||||||
As the reverse name for a field should be unique, be careful if you intend
|
As the reverse name for a field should be unique, be careful if you intend
|
||||||
to subclass your model. To work around name collisions, part of the name
|
to subclass your model. To work around name collisions, part of the name
|
||||||
should contain ``'%(app_label)s'`` and ``'%(model_name)s'``, which are
|
should contain ``'%(app_label)s'`` and ``'%(model_name)s'``, which are
|
||||||
|
@ -110,6 +112,30 @@ Django quotes column and table names behind the scenes.
|
||||||
and the name of the model, both lowercased. See the paragraph on
|
and the name of the model, both lowercased. See the paragraph on
|
||||||
:ref:`related names for abstract models <abstract-related-name>`.
|
:ref:`related names for abstract models <abstract-related-name>`.
|
||||||
|
|
||||||
|
.. deprecated:: 1.10
|
||||||
|
|
||||||
|
This attribute now affects ``related_query_name``. The old query lookup
|
||||||
|
name is deprecated::
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Foo(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Bar(models.Model):
|
||||||
|
foo = models.ForeignKey(Foo)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
default_related_name = 'bars'
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> bar = Bar.objects.get(pk=1)
|
||||||
|
>>> # Using model name "bar" as lookup string is deprecated.
|
||||||
|
>>> Foo.object.get(bar=bar)
|
||||||
|
>>> # You should use default_related_name "bars".
|
||||||
|
>>> Foo.object.get(bars=bar)
|
||||||
|
|
||||||
``get_latest_by``
|
``get_latest_by``
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|
|
@ -704,6 +704,34 @@ longer than the 4000 byte limit of ``NVARCHAR2``, you should use ``TextField``
|
||||||
field (e.g. annotating the model with an aggregation or using ``distinct()``)
|
field (e.g. annotating the model with an aggregation or using ``distinct()``)
|
||||||
you'll need to change them (to defer the field).
|
you'll need to change them (to defer the field).
|
||||||
|
|
||||||
|
Using a model name as a query lookup when ``default_related_name`` is set
|
||||||
|
-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Assume the following models::
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Foo(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Bar(models.Model):
|
||||||
|
foo = models.ForeignKey(Foo)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
default_related_name = 'bars'
|
||||||
|
|
||||||
|
In older versions, :attr:`~django.db.models.Options.default_related_name`
|
||||||
|
couldn't be used as a query lookup. This is fixed and support for the old
|
||||||
|
lookup name is deprecated. For example, since ``default_related_name`` is set
|
||||||
|
in model ``Bar``, instead of using the model name ``bar`` as the lookup::
|
||||||
|
|
||||||
|
>>> bar = Bar.objects.get(pk=1)
|
||||||
|
>>> Foo.object.get(bar=bar)
|
||||||
|
|
||||||
|
use the default_related_name ``bars``::
|
||||||
|
|
||||||
|
>>> Foo.object.get(bars=bar)
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
|
|
||||||
from .models.default_related_name import Author, Book, Editor
|
from .models.default_related_name import Author, Book, Editor
|
||||||
|
|
||||||
|
@ -18,6 +21,19 @@ class DefaultRelatedNameTests(TestCase):
|
||||||
def test_default_related_name(self):
|
def test_default_related_name(self):
|
||||||
self.assertEqual(list(self.author.books.all()), [self.book])
|
self.assertEqual(list(self.author.books.all()), [self.book])
|
||||||
|
|
||||||
|
def test_default_related_name_in_queryset_lookup(self):
|
||||||
|
self.assertEqual(Author.objects.get(books=self.book), self.author)
|
||||||
|
|
||||||
|
def test_show_deprecated_message_when_model_name_in_queryset_lookup(self):
|
||||||
|
msg = "Query lookup 'book' is deprecated in favor of Meta.default_related_name 'books'."
|
||||||
|
with warnings.catch_warnings(record=True) as warns:
|
||||||
|
warnings.simplefilter('once')
|
||||||
|
Author.objects.get(book=self.book)
|
||||||
|
self.assertEqual(len(warns), 1)
|
||||||
|
warning = warns.pop()
|
||||||
|
self.assertEqual(warning.category, RemovedInDjango20Warning)
|
||||||
|
self.assertEqual(str(warning.message), msg)
|
||||||
|
|
||||||
def test_related_name_overrides_default_related_name(self):
|
def test_related_name_overrides_default_related_name(self):
|
||||||
self.assertEqual(list(self.editor.edited_books.all()), [self.book])
|
self.assertEqual(list(self.editor.edited_books.all()), [self.book])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue