Fixed #36530 -- Extended fields.E347 to check for ManyToManyField involving CompositePrimaryKey on either side.

Thanks to Jacob Walls for the report.
This commit is contained in:
jkhall81 2025-07-28 08:59:07 -07:00 committed by nessita
parent e664c5afa9
commit 2013092b69
4 changed files with 68 additions and 53 deletions

View file

@ -626,9 +626,10 @@ class ForeignObject(RelatedField):
if isinstance(field, CompositePrimaryKey): if isinstance(field, CompositePrimaryKey):
errors.append( errors.append(
checks.Error( checks.Error(
"Field defines a relation to the CompositePrimaryKey of " "Field defines a relation involving model "
f"model {self.remote_field.model._meta.object_name!r} " f"{self.remote_field.model._meta.object_name!r} which has "
"which is not supported.", "a CompositePrimaryKey and such relations are not "
"supported.",
obj=self, obj=self,
id="fields.E347", id="fields.E347",
) )
@ -1538,20 +1539,24 @@ class ManyToManyField(RelatedField):
to_model_name = to_model to_model_name = to_model
else: else:
to_model_name = to_model._meta.object_name to_model_name = to_model._meta.object_name
if ( if self.remote_field.through_fields is None and not isinstance(
self.remote_field.through_fields is None to_model, str
and not isinstance(to_model, str)
and isinstance(to_model._meta.pk, CompositePrimaryKey)
): ):
errors.append( model_name = None
checks.Error( if isinstance(to_model._meta.pk, CompositePrimaryKey):
"Field defines a relation to the CompositePrimaryKey of model " model_name = self.remote_field.model._meta.object_name
f"{self.remote_field.model._meta.object_name!r} which is not " elif isinstance(from_model._meta.pk, CompositePrimaryKey):
"supported.", model_name = from_model_name
obj=self, if model_name:
id="fields.E347", errors.append(
checks.Error(
f"Field defines a relation involving model {model_name!r} "
"which has a CompositePrimaryKey and such relations are "
"not supported.",
obj=self,
id="fields.E347",
)
) )
)
relationship_model_name = self.remote_field.through._meta.object_name relationship_model_name = self.remote_field.through._meta.object_name
self_referential = from_model == to_model self_referential = from_model == to_model
# Count foreign keys in intermediate model # Count foreign keys in intermediate model

View file

@ -338,8 +338,8 @@ Related fields
* **fields.W345**: ``related_name`` has no effect on ``ManyToManyField`` with a * **fields.W345**: ``related_name`` has no effect on ``ManyToManyField`` with a
symmetrical relationship, e.g. to "self". symmetrical relationship, e.g. to "self".
* **fields.W346**: ``db_comment`` has no effect on ``ManyToManyField``. * **fields.W346**: ``db_comment`` has no effect on ``ManyToManyField``.
* **fields.E347**: Field defines a relation to the ``CompositePrimaryKey`` of * **fields.E347**: Field defines a relation involving model ``<model>`` which
model ``<model>`` which is not supported. has a ``CompositePrimaryKey`` and such relations are not supported.
* **fields.E348**: Related name ``<related_name>`` for ``<model>.<field name>`` * **fields.E348**: Related name ``<related_name>`` for ``<model>.<field name>``
clashes with the name of a model manager. clashes with the name of a model manager.

View file

@ -21,3 +21,7 @@ Bugfixes
(:ticket:`36518`). (:ticket:`36518`).
* Added compatibility for ``docutils`` 0.22 (:ticket:`36535`). * Added compatibility for ``docutils`` 0.22 (:ticket:`36535`).
* Fixed a crash in Django 5.2 when using a ``ManyToManyField`` on a model with
a composite primary key, by extending the ``fields.E347`` system check
(:ticket:`36530`).

View file

@ -454,29 +454,19 @@ class RelativeFieldTests(SimpleTestCase):
"Parent", on_delete=models.CASCADE, related_name="child_string_set" "Parent", on_delete=models.CASCADE, related_name="child_string_set"
) )
error = (
"Field defines a relation involving model 'Parent' which has a "
"CompositePrimaryKey and such relations are not supported."
)
field = Child._meta.get_field("rel_string_parent") field = Child._meta.get_field("rel_string_parent")
self.assertEqual( self.assertEqual(
field.check(), field.check(),
[ [Error(error, obj=field, id="fields.E347")],
Error(
"Field defines a relation to the CompositePrimaryKey of model "
"'Parent' which is not supported.",
obj=field,
id="fields.E347",
),
],
) )
field = Child._meta.get_field("rel_class_parent") field = Child._meta.get_field("rel_class_parent")
self.assertEqual( self.assertEqual(
field.check(), field.check(),
[ [Error(error, obj=field, id="fields.E347")],
Error(
"Field defines a relation to the CompositePrimaryKey of model "
"'Parent' which is not supported.",
obj=field,
id="fields.E347",
),
],
) )
def test_many_to_many_to_model_with_composite_primary_key(self): def test_many_to_many_to_model_with_composite_primary_key(self):
@ -493,29 +483,45 @@ class RelativeFieldTests(SimpleTestCase):
"Parent", related_name="child_string_set" "Parent", related_name="child_string_set"
) )
error = (
"Field defines a relation involving model 'Parent' which has a "
"CompositePrimaryKey and such relations are not supported."
)
field = Child._meta.get_field("rel_string_parent") field = Child._meta.get_field("rel_string_parent")
self.assertEqual( self.assertEqual(
field.check(from_model=Child), field.check(from_model=Child),
[ [Error(error, obj=field, id="fields.E347")],
Error(
"Field defines a relation to the CompositePrimaryKey of model "
"'Parent' which is not supported.",
obj=field,
id="fields.E347",
),
],
) )
field = Child._meta.get_field("rel_class_parent") field = Child._meta.get_field("rel_class_parent")
self.assertEqual( self.assertEqual(
field.check(from_model=Child), field.check(from_model=Child),
[ [Error(error, obj=field, id="fields.E347")],
Error( )
"Field defines a relation to the CompositePrimaryKey of model "
"'Parent' which is not supported.", def test_many_to_many_from_model_with_composite_primary_key(self):
obj=field, class Parent(models.Model):
id="fields.E347", name = models.CharField(max_length=20)
),
], class Meta:
app_label = "invalid_models_tests"
class Child(models.Model):
pk = models.CompositePrimaryKey("version", "name")
version = models.IntegerField()
name = models.CharField(max_length=20)
parents = models.ManyToManyField(Parent)
class Meta:
app_label = "invalid_models_tests"
error = (
"Field defines a relation involving model 'Child' which has a "
"CompositePrimaryKey and such relations are not supported."
)
field = Child._meta.get_field("parents")
self.assertEqual(
field.check(from_model=Child),
[Error(error, obj=field, id="fields.E347")],
) )
def test_foreign_key_to_non_unique_field(self): def test_foreign_key_to_non_unique_field(self):
@ -1038,8 +1044,8 @@ class RelativeFieldTests(SimpleTestCase):
field.check(), field.check(),
[ [
Error( Error(
"Field defines a relation to the CompositePrimaryKey of model " "Field defines a relation involving model 'Parent' which has a "
"'Parent' which is not supported.", "CompositePrimaryKey and such relations are not supported.",
obj=field, obj=field,
id="fields.E347", id="fields.E347",
), ),
@ -1060,8 +1066,8 @@ class RelativeFieldTests(SimpleTestCase):
field.check(), field.check(),
[ [
Error( Error(
"Field defines a relation to the CompositePrimaryKey of model " "Field defines a relation involving model 'Parent' which has a "
"'Parent' which is not supported.", "CompositePrimaryKey and such relations are not supported.",
obj=field, obj=field,
id="fields.E347", id="fields.E347",
), ),