1
0
mirror of https://github.com/django/django.git synced 2024-12-23 01:25:58 +00:00

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
This commit is contained in:
Anthony Joseph 2024-12-10 06:55:18 +11:00
parent 1860a1afc9
commit 0cb31fda4e
No known key found for this signature in database
GPG Key ID: 5D2516AE3AC85D11
3 changed files with 48 additions and 0 deletions

View File

@ -581,6 +581,7 @@ class ForeignObject(RelatedField):
*super().check(**kwargs), *super().check(**kwargs),
*self._check_to_fields_exist(), *self._check_to_fields_exist(),
*self._check_unique_target(), *self._check_unique_target(),
*self._check_conflict_with_managers(),
] ]
def _check_to_fields_exist(self): def _check_to_fields_exist(self):
@ -679,6 +680,28 @@ class ForeignObject(RelatedField):
] ]
return [] 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): def deconstruct(self):
name, path, args, kwargs = super().deconstruct() name, path, args, kwargs = super().deconstruct()
kwargs["on_delete"] = self.remote_field.on_delete kwargs["on_delete"] = self.remote_field.on_delete

View File

@ -327,6 +327,8 @@ Related fields
* **fields.E339**: ``<model>.<field name>`` is not a foreign key to ``<model>``. * **fields.E339**: ``<model>.<field name>`` is not a foreign key to ``<model>``.
* **fields.E340**: The field's intermediary table ``<table name>`` clashes with * **fields.E340**: The field's intermediary table ``<table name>`` clashes with
the table name of ``<model>``/``<model>.<field name>``. the table name of ``<model>``/``<model>.<field name>``.
* **fields.E341**: ``<model>``.``related_name`` must be different than the
``<model>``'s manager name.
* **fields.W340**: ``null`` has no effect on ``ManyToManyField``. * **fields.W340**: ``null`` has no effect on ``ManyToManyField``.
* **fields.W341**: ``ManyToManyField`` does not support ``validators``. * **fields.W341**: ``ManyToManyField`` does not support ``validators``.
* **fields.W342**: Setting ``unique=True`` on a ``ForeignKey`` has the same * **fields.W342**: Setting ``unique=True`` on a ``ForeignKey`` has the same

View File

@ -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") @isolate_apps("invalid_models_tests")
class SelfReferentialM2MClashTests(SimpleTestCase): class SelfReferentialM2MClashTests(SimpleTestCase):
def test_clash_between_accessors(self): def test_clash_between_accessors(self):