From 0cb31fda4e5d2965d3877de9559f07a966bbdf95 Mon Sep 17 00:00:00 2001 From: Anthony Joseph Date: Tue, 10 Dec 2024 06:55:18 +1100 Subject: [PATCH] Fixed #22977 -- Added check for clash between model manager and model field's `related_name`. New check added on model related fields to check if the `related_name` conflicts with the respective model's manager name. With thanks to kswiat, loic, felixxm and russellm --- django/db/models/fields/related.py | 23 +++++++++++++++++++ docs/ref/checks.txt | 2 ++ .../test_relative_fields.py | 23 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 9ef2d29024..9c478a204b 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -581,6 +581,7 @@ class ForeignObject(RelatedField): *super().check(**kwargs), *self._check_to_fields_exist(), *self._check_unique_target(), + *self._check_conflict_with_managers(), ] def _check_to_fields_exist(self): @@ -679,6 +680,28 @@ class ForeignObject(RelatedField): ] return [] + def _check_conflict_with_managers(self): + errors = [] + for manager in self.opts.managers: + for rel_objs in self.model._meta.related_objects: + manager_name = manager.name + related_object_name = rel_objs.name + if manager_name == related_object_name: + field_name = f"{self.model._meta.object_name}.{self.name}" + errors.append( + checks.Error( + f"Related name for '{field_name}' clashes with " + f"manager: '{related_object_name}' name.", + hint=( + "Rename manager name or related_name " + "in conflicted field" + ), + obj=self, + id="fields.E341", + ) + ) + return errors + def deconstruct(self): name, path, args, kwargs = super().deconstruct() kwargs["on_delete"] = self.remote_field.on_delete diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index b0a98bde28..6b8dd379c1 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -327,6 +327,8 @@ Related fields * **fields.E339**: ``.`` is not a foreign key to ````. * **fields.E340**: The field's intermediary table ```` clashes with the table name of ````/``.``. +* **fields.E341**: ````.``related_name`` must be different than the + ````'s manager name. * **fields.W340**: ``null`` has no effect on ``ManyToManyField``. * **fields.W341**: ``ManyToManyField`` does not support ``validators``. * **fields.W342**: Setting ``unique=True`` on a ``ForeignKey`` has the same diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index 9b69ae4151..87a185e575 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -1407,6 +1407,29 @@ class ExplicitRelatedQueryNameClashTests(SimpleTestCase): ) +@isolate_apps("invalid_models_tests") +class RelatedQueryNameClashWithManagerTests(SimpleTestCase): + def test_clash_between_related_query_name_and_manager(self): + class Author(models.Model): + authors = models.Manager() + mentor = models.ForeignKey( + "self", related_name="authors", on_delete=models.CASCADE + ) + + self.assertEqual( + Author.check(), + [ + Error( + "Related name for 'Author.mentor' clashes with manager: " + "'authors' name.", + hint="Rename manager name or related_name in conflicted field", + obj=Author._meta.get_field("mentor"), + id="fields.E341", + ) + ], + ) + + @isolate_apps("invalid_models_tests") class SelfReferentialM2MClashTests(SimpleTestCase): def test_clash_between_accessors(self):