[1.8.x] Fixed #26162 -- Checked query name clashes of hidden relationships.
Although reverse accessor clashes should be skipped query name can't be hidden.
Thanks to Ian Foote and Tim Graham for the review.
Backport of a325fb1f9b
from master
This commit is contained in:
parent
2f0de9b0a1
commit
edff550392
django/db/models/fields
docs/releases
tests/invalid_models_tests
|
@ -194,12 +194,6 @@ class RelatedField(Field):
|
|||
if not isinstance(self.rel.to, ModelBase):
|
||||
return []
|
||||
|
||||
# If the field doesn't install backward relation on the target model (so
|
||||
# `is_hidden` returns True), then there are no clashes to check and we
|
||||
# can skip these fields.
|
||||
if self.rel.is_hidden():
|
||||
return []
|
||||
|
||||
try:
|
||||
self.rel
|
||||
except AttributeError:
|
||||
|
@ -216,12 +210,15 @@ class RelatedField(Field):
|
|||
# foreign = models.ForeignKey(Target)
|
||||
# m2m = models.ManyToManyField(Target)
|
||||
|
||||
rel_opts = self.rel.to._meta
|
||||
# rel_opts.object_name == "Target"
|
||||
rel_opts = self.rel.to._meta
|
||||
# If the field doesn't install a backward relation on the target model
|
||||
# (so `is_hidden` returns True), then there are no clashes to check
|
||||
# and we can skip these fields.
|
||||
rel_is_hidden = self.rel.is_hidden()
|
||||
rel_name = self.rel.get_accessor_name() # i. e. "model_set"
|
||||
rel_query_name = self.related_query_name() # i. e. "model"
|
||||
field_name = "%s.%s" % (opts.object_name,
|
||||
self.name) # i. e. "Model.field"
|
||||
field_name = "%s.%s" % (opts.object_name, self.name) # i. e. "Model.field"
|
||||
|
||||
# Check clashes between accessor or reverse query name of `field`
|
||||
# and any other field name -- i.e. accessor for Model.foreign is
|
||||
|
@ -230,7 +227,7 @@ class RelatedField(Field):
|
|||
for clash_field in potential_clashes:
|
||||
clash_name = "%s.%s" % (rel_opts.object_name,
|
||||
clash_field.name) # i. e. "Target.model_set"
|
||||
if clash_field.name == rel_name:
|
||||
if not rel_is_hidden and clash_field.name == rel_name:
|
||||
errors.append(
|
||||
checks.Error(
|
||||
"Reverse accessor for '%s' clashes with field name '%s'." % (field_name, clash_name),
|
||||
|
@ -260,7 +257,7 @@ class RelatedField(Field):
|
|||
clash_name = "%s.%s" % ( # i. e. "Model.m2m"
|
||||
clash_field.related_model._meta.object_name,
|
||||
clash_field.field.name)
|
||||
if clash_field.get_accessor_name() == rel_name:
|
||||
if not rel_is_hidden and clash_field.get_accessor_name() == rel_name:
|
||||
errors.append(
|
||||
checks.Error(
|
||||
"Reverse accessor for '%s' clashes with reverse accessor for '%s'." % (field_name, clash_name),
|
||||
|
|
|
@ -11,3 +11,6 @@ Bugfixes
|
|||
|
||||
* Fixed a crash on PostgreSQL that prevented using ``TIME_ZONE=None`` and
|
||||
``USE_TZ=False`` (:ticket:`26177`).
|
||||
|
||||
* Added system checks for query name clashes of hidden relationships
|
||||
(:ticket:`26162`).
|
||||
|
|
|
@ -859,42 +859,66 @@ class ExplicitRelatedNameClashTests(IsolatedModelsTestCase):
|
|||
|
||||
class ExplicitRelatedQueryNameClashTests(IsolatedModelsTestCase):
|
||||
|
||||
def test_fk_to_integer(self):
|
||||
def test_fk_to_integer(self, related_name=None):
|
||||
self._test_explicit_related_query_name_clash(
|
||||
target=models.IntegerField(),
|
||||
relative=models.ForeignKey('Target',
|
||||
related_name=related_name,
|
||||
related_query_name='clash'))
|
||||
|
||||
def test_fk_to_fk(self):
|
||||
def test_hidden_fk_to_integer(self, related_name=None):
|
||||
self.test_fk_to_integer(related_name='+')
|
||||
|
||||
def test_fk_to_fk(self, related_name=None):
|
||||
self._test_explicit_related_query_name_clash(
|
||||
target=models.ForeignKey('Another'),
|
||||
relative=models.ForeignKey('Target',
|
||||
related_name=related_name,
|
||||
related_query_name='clash'))
|
||||
|
||||
def test_fk_to_m2m(self):
|
||||
def test_hidden_fk_to_fk(self):
|
||||
self.test_fk_to_fk(related_name='+')
|
||||
|
||||
def test_fk_to_m2m(self, related_name=None):
|
||||
self._test_explicit_related_query_name_clash(
|
||||
target=models.ManyToManyField('Another'),
|
||||
relative=models.ForeignKey('Target',
|
||||
related_name=related_name,
|
||||
related_query_name='clash'))
|
||||
|
||||
def test_m2m_to_integer(self):
|
||||
def test_hidden_fk_to_m2m(self):
|
||||
self.test_fk_to_m2m(related_name='+')
|
||||
|
||||
def test_m2m_to_integer(self, related_name=None):
|
||||
self._test_explicit_related_query_name_clash(
|
||||
target=models.IntegerField(),
|
||||
relative=models.ManyToManyField('Target',
|
||||
related_name=related_name,
|
||||
related_query_name='clash'))
|
||||
|
||||
def test_m2m_to_fk(self):
|
||||
def test_hidden_m2m_to_integer(self):
|
||||
self.test_m2m_to_integer(related_name='+')
|
||||
|
||||
def test_m2m_to_fk(self, related_name=None):
|
||||
self._test_explicit_related_query_name_clash(
|
||||
target=models.ForeignKey('Another'),
|
||||
relative=models.ManyToManyField('Target',
|
||||
related_name=related_name,
|
||||
related_query_name='clash'))
|
||||
|
||||
def test_m2m_to_m2m(self):
|
||||
def test_hidden_m2m_to_fk(self):
|
||||
self.test_m2m_to_fk(related_name='+')
|
||||
|
||||
def test_m2m_to_m2m(self, related_name=None):
|
||||
self._test_explicit_related_query_name_clash(
|
||||
target=models.ManyToManyField('Another'),
|
||||
relative=models.ManyToManyField('Target',
|
||||
related_name=related_name,
|
||||
related_query_name='clash'))
|
||||
|
||||
def test_hidden_m2m_to_m2m(self):
|
||||
self.test_m2m_to_m2m(related_name='+')
|
||||
|
||||
def _test_explicit_related_query_name_clash(self, target, relative):
|
||||
class Another(models.Model):
|
||||
pass
|
||||
|
|
Loading…
Reference in New Issue