diff --git a/django/db/models/base.py b/django/db/models/base.py index 959b72c93bd..0c4a5ddcfc3 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1775,6 +1775,21 @@ class Model(AltersData, metaclass=ModelBase): if f not in used_fields: used_fields[f.name] = f + # Check that parent links in diamond-shaped MTI models don't clash. + for parent_link in cls._meta.parents.values(): + if not parent_link: + continue + clash = used_fields.get(parent_link.name) or None + if clash: + errors.append( + checks.Error( + f"The field '{parent_link.name}' clashes with the field " + f"'{clash.name}' from model '{clash.model._meta}'.", + obj=cls, + id="models.E006", + ) + ) + for f in cls._meta.local_fields: clash = used_fields.get(f.name) or used_fields.get(f.attname) or None # Note that we may detect clash between user-defined non-unique diff --git a/tests/invalid_models_tests/test_models.py b/tests/invalid_models_tests/test_models.py index ffc7579e107..9e2a37b79ae 100644 --- a/tests/invalid_models_tests/test_models.py +++ b/tests/invalid_models_tests/test_models.py @@ -1070,6 +1070,31 @@ class ShadowingFieldsTests(SimpleTestCase): ], ) + def test_diamond_mti_common_parent(self): + class GrandParent(models.Model): + pass + + class Parent(GrandParent): + pass + + class Child(Parent): + pass + + class MTICommonParentModel(Child, GrandParent): + pass + + self.assertEqual( + MTICommonParentModel.check(), + [ + Error( + "The field 'grandparent_ptr' clashes with the field " + "'grandparent_ptr' from model 'invalid_models_tests.parent'.", + obj=MTICommonParentModel, + id="models.E006", + ) + ], + ) + def test_id_clash(self): class Target(models.Model): pass