summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClifford Gama <cliffygamy@gmail.com>2026-03-10 17:47:18 +0200
committerJacob Walls <jacobtylerwalls@gmail.com>2026-04-02 15:54:23 -0400
commitfcf916884d25ed430bd7cedaea2b10035c2aa3b6 (patch)
tree8449c9aebcc44c43f27e9c4ee5a57a93f7ed17f9
parent04bcc9913319e50b376a27c29cf9aa4e7b8247bf (diff)
Fixed #36973 -- Made fields.E348 check detect further clashes between managers and related_names.
Clashes were only detected for self-referential relationships, i.e. ForeignKey("self"). Refs #22977. Bug in 6888375c53476011754f778deabc6cdbfa327011. Thanks JaeHyuckSa for the thorough review!
-rw-r--r--django/db/models/fields/related.py40
-rw-r--r--docs/releases/6.0.4.txt5
-rw-r--r--tests/invalid_models_tests/test_relative_fields.py32
3 files changed, 52 insertions, 25 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index f1bc664007..332a168a78 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -348,6 +348,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):
@@ -589,7 +607,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):
@@ -719,27 +736,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 51b1de1494..a6edcc4e04 100644
--- a/tests/invalid_models_tests/test_relative_fields.py
+++ b/tests/invalid_models_tests/test_relative_fields.py
@@ -1579,11 +1579,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",
@@ -1591,6 +1592,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):