diff options
| -rw-r--r-- | django/db/models/fields/related.py | 40 | ||||
| -rw-r--r-- | docs/releases/6.0.4.txt | 5 | ||||
| -rw-r--r-- | tests/invalid_models_tests/test_relative_fields.py | 32 |
3 files changed, 52 insertions, 25 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index a71ae2f401..ac15a00f80 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -339,6 +339,24 @@ class RelatedField(FieldCacheMixin, Field): ) ) + # Check clash between reverse accessor and manager names on + # the target model. + if not rel_is_hidden: + manager_names = {m.name for m in rel_opts.managers} + if rel_name in manager_names: + errors.append( + checks.Error( + f"Related name '{rel_name}' for '{field_name}' " + f"clashes with the name of a model manager.", + hint=( + "Rename the model manager or change the related_name " + "argument in the definition for field '%s'." % field_name + ), + obj=self, + id="fields.E348", + ) + ) + return errors def db_type(self, connection): @@ -580,7 +598,6 @@ class ForeignObject(RelatedField): *self._check_to_fields_exist(), *self._check_to_fields_composite_pk(), *self._check_unique_target(), - *self._check_conflict_with_managers(), ] def _check_to_fields_exist(self): @@ -710,27 +727,6 @@ class ForeignObject(RelatedField): ] return [] - def _check_conflict_with_managers(self): - errors = [] - manager_names = {manager.name for manager in self.opts.managers} - for rel_objs in self.model._meta.related_objects: - related_object_name = rel_objs.name - if related_object_name in manager_names: - field_name = f"{self.model._meta.object_name}.{self.name}" - errors.append( - checks.Error( - f"Related name '{related_object_name}' for '{field_name}' " - "clashes with the name of a model manager.", - hint=( - "Rename the model manager or change the related_name " - f"argument in the definition for field '{field_name}'." - ), - obj=self, - id="fields.E348", - ) - ) - return errors - def deconstruct(self): name, path, args, kwargs = super().deconstruct() kwargs["on_delete"] = self.remote_field.on_delete diff --git a/docs/releases/6.0.4.txt b/docs/releases/6.0.4.txt index 1967edacc8..de75dc7d13 100644 --- a/docs/releases/6.0.4.txt +++ b/docs/releases/6.0.4.txt @@ -18,3 +18,8 @@ Bugfixes * Fixed a regression in Django 6.0 in admin forms where ``RelatedFieldWidgetWrapper`` incorrectly wrapped all widgets in a ``<fieldset>`` (:ticket:`36949`). + +* Fixed a bug in Django 6.0 where the ``fields.E348`` system check did not + detect name clashes between model managers and + :attr:`~django.db.models.ForeignKey.related_name`\s for non-self-referential + relationships (:ticket:`36973`). diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index ed6d39f7c6..f1ecd178f1 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -1555,11 +1555,12 @@ class RelatedQueryNameClashWithManagerTests(SimpleTestCase): Author.check(), [ Error( - "Related name 'authors' for 'Author.mentor' clashes with the name " - "of a model manager.", + "Related name 'authors' for 'invalid_models_tests.Author.mentor' " + "clashes with the name of a model manager.", hint=( "Rename the model manager or change the related_name argument " - "in the definition for field 'Author.mentor'." + "in the definition for field " + "'invalid_models_tests.Author.mentor'." ), obj=Author._meta.get_field("mentor"), id="fields.E348", @@ -1567,6 +1568,31 @@ class RelatedQueryNameClashWithManagerTests(SimpleTestCase): ], ) + def test_clash_between_rel_name_and_manager_non_self_referential(self): + class Thing(models.Model): + items = models.Manager() + + class Item(models.Model): + thing = models.ForeignKey(Thing, models.CASCADE, related_name="items") + something = models.ForeignKey(Thing, models.CASCADE, related_name="+") + + self.assertEqual( + Item.check(), + [ + Error( + "Related name 'items' for 'invalid_models_tests.Item.thing' " + "clashes with the name of a model manager.", + hint=( + "Rename the model manager or change the related_name argument " + "in the definition for field 'invalid_models_tests.Item.thing'." + ), + obj=Item._meta.get_field("thing"), + id="fields.E348", + ) + ], + ) + self.assertEqual(Thing.check(), []) + @isolate_apps("invalid_models_tests") class SelfReferentialM2MClashTests(SimpleTestCase): |
