Loading django/db/models/fields/related.py +8 −11 Original line number Diff line number Diff line Loading @@ -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: Loading @@ -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 Loading @@ -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), Loading Loading @@ -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), Loading docs/releases/1.8.10.txt +3 −0 Original line number Diff line number Diff line Loading @@ -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`). tests/invalid_models_tests/test_relative_fields.py +30 −6 Original line number Diff line number Diff line Loading @@ -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 Loading
django/db/models/fields/related.py +8 −11 Original line number Diff line number Diff line Loading @@ -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: Loading @@ -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 Loading @@ -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), Loading Loading @@ -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), Loading
docs/releases/1.8.10.txt +3 −0 Original line number Diff line number Diff line Loading @@ -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`).
tests/invalid_models_tests/test_relative_fields.py +30 −6 Original line number Diff line number Diff line Loading @@ -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