Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions django/db/models/fields/related.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,9 +626,10 @@ def _check_to_fields_composite_pk(self):
if isinstance(field, CompositePrimaryKey):
errors.append(
checks.Error(
"Field defines a relation to the CompositePrimaryKey of "
f"model {self.remote_field.model._meta.object_name!r} "
"which is not supported.",
"Field defines a relation involving model "
f"{self.remote_field.model._meta.object_name!r} which has "
"a CompositePrimaryKey and such relations are not "
"supported.",
obj=self,
id="fields.E347",
)
Expand Down Expand Up @@ -1538,20 +1539,24 @@ def _check_relationship_model(self, from_model=None, **kwargs):
to_model_name = to_model
else:
to_model_name = to_model._meta.object_name
if (
self.remote_field.through_fields is None
and not isinstance(to_model, str)
and isinstance(to_model._meta.pk, CompositePrimaryKey)
if self.remote_field.through_fields is None and not isinstance(
to_model, str
):
errors.append(
checks.Error(
"Field defines a relation to the CompositePrimaryKey of model "
f"{self.remote_field.model._meta.object_name!r} which is not "
"supported.",
obj=self,
id="fields.E347",
model_name = None
if isinstance(to_model._meta.pk, CompositePrimaryKey):
model_name = self.remote_field.model._meta.object_name
elif isinstance(from_model._meta.pk, CompositePrimaryKey):
model_name = from_model_name
if model_name:
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
self_referential = from_model == to_model
# Count foreign keys in intermediate model
Expand Down
4 changes: 2 additions & 2 deletions docs/ref/checks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,8 @@ Related fields
* **fields.W345**: ``related_name`` has no effect on ``ManyToManyField`` with a
symmetrical relationship, e.g. to "self".
* **fields.W346**: ``db_comment`` has no effect on ``ManyToManyField``.
* **fields.E347**: Field defines a relation to the ``CompositePrimaryKey`` of
model ``<model>`` which is not supported.
* **fields.E347**: Field defines a relation involving model ``<model>`` which
has a ``CompositePrimaryKey`` and such relations are not supported.
* **fields.E348**: Related name ``<related_name>`` for ``<model>.<field name>``
clashes with the name of a model manager.

Expand Down
4 changes: 4 additions & 0 deletions docs/releases/5.2.5.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ Bugfixes
(:ticket:`36518`).

* 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`).
78 changes: 42 additions & 36 deletions tests/invalid_models_tests/test_relative_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,29 +454,19 @@ class Child(models.Model):
"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")
self.assertEqual(
field.check(),
[
Error(
"Field defines a relation to the CompositePrimaryKey of model "
"'Parent' which is not supported.",
obj=field,
id="fields.E347",
),
],
[Error(error, obj=field, id="fields.E347")],
)
field = Child._meta.get_field("rel_class_parent")
self.assertEqual(
field.check(),
[
Error(
"Field defines a relation to the CompositePrimaryKey of model "
"'Parent' which is not supported.",
obj=field,
id="fields.E347",
),
],
[Error(error, obj=field, id="fields.E347")],
)

def test_many_to_many_to_model_with_composite_primary_key(self):
Expand All @@ -493,29 +483,45 @@ class Child(models.Model):
"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")
self.assertEqual(
field.check(from_model=Child),
[
Error(
"Field defines a relation to the CompositePrimaryKey of model "
"'Parent' which is not supported.",
obj=field,
id="fields.E347",
),
],
[Error(error, obj=field, id="fields.E347")],
)
field = Child._meta.get_field("rel_class_parent")
self.assertEqual(
field.check(from_model=Child),
[
Error(
"Field defines a relation to the CompositePrimaryKey of model "
"'Parent' which is not supported.",
obj=field,
id="fields.E347",
),
],
[Error(error, obj=field, id="fields.E347")],
)

def test_many_to_many_from_model_with_composite_primary_key(self):
class Parent(models.Model):
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):
Expand Down Expand Up @@ -1038,8 +1044,8 @@ class Child(models.Model):
field.check(),
[
Error(
"Field defines a relation to the CompositePrimaryKey of model "
"'Parent' which is not supported.",
"Field defines a relation involving model 'Parent' which has a "
"CompositePrimaryKey and such relations are not supported.",
obj=field,
id="fields.E347",
),
Expand All @@ -1060,8 +1066,8 @@ class Child(models.Model):
field.check(),
[
Error(
"Field defines a relation to the CompositePrimaryKey of model "
"'Parent' which is not supported.",
"Field defines a relation involving model 'Parent' which has a "
"CompositePrimaryKey and such relations are not supported.",
obj=field,
id="fields.E347",
),
Expand Down
40 changes: 20 additions & 20 deletions tests/prefetch_related/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2176,20 +2176,20 @@ def test_add_clears_prefetched_objects_in_parent(self):
.prefetch_related("favorite_authors")
.get(pk=self.m2m_child.pk)
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.favorite_authors.all(),
{self.related1, self.related2, self.related3},
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.authorwithage.favorite_authors.all(),
{self.related1, self.related2, self.related3},
)
gp.authorwithage.favorite_authors.add(self.related4)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.favorite_authors.all(),
{self.related1, self.related2, self.related3, self.related4},
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.authorwithage.favorite_authors.all(),
{self.related1, self.related2, self.related3, self.related4},
)
Expand All @@ -2202,28 +2202,28 @@ def test_add_clears_prefetched_objects_in_grandparent(self):
.prefetch_related("favorite_authors")
.get(pk=self.m2m_child.pk)
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.favorite_authors.all(),
[self.related1, self.related2, self.related3],
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.authorwithage.favorite_authors.all(),
[self.related1, self.related2, self.related3],
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.authorwithage.authorwithagechild.favorite_authors.all(),
[self.related1, self.related2, self.related3],
)
gp.authorwithage.authorwithagechild.favorite_authors.add(self.related4)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.favorite_authors.all(),
[self.related1, self.related2, self.related3, self.related4],
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.authorwithage.favorite_authors.all(),
[self.related1, self.related2, self.related3, self.related4],
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.authorwithage.authorwithagechild.favorite_authors.all(),
[self.related1, self.related2, self.related3, self.related4],
)
Expand All @@ -2234,17 +2234,17 @@ def test_remove_clears_prefetched_objects_in_parent(self):
.prefetch_related("favorite_authors")
.get(pk=self.m2m_child.pk)
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.favorite_authors.all(),
[self.related1, self.related2, self.related3],
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.authorwithage.favorite_authors.all(),
[self.related1, self.related2, self.related3],
)
gp.authorwithage.favorite_authors.clear()
self.assertQuerySetEqual(gp.favorite_authors.all(), [])
self.assertQuerySetEqual(gp.authorwithage.favorite_authors.all(), [])
self.assertSequenceEqual(gp.favorite_authors.all(), [])
self.assertSequenceEqual(gp.authorwithage.favorite_authors.all(), [])

def test_remove_clears_prefetched_objects_in_grandparent(self):
gp = (
Expand All @@ -2254,21 +2254,21 @@ def test_remove_clears_prefetched_objects_in_grandparent(self):
.prefetch_related("favorite_authors")
.get(pk=self.m2m_child.pk)
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.favorite_authors.all(),
[self.related1, self.related2, self.related3],
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.authorwithage.favorite_authors.all(),
[self.related1, self.related2, self.related3],
)
self.assertQuerySetEqual(
self.assertCountEqual(
gp.authorwithage.authorwithagechild.favorite_authors.all(),
[self.related1, self.related2, self.related3],
)
gp.authorwithage.favorite_authors.clear()
self.assertQuerySetEqual(gp.favorite_authors.all(), [])
self.assertQuerySetEqual(gp.authorwithage.favorite_authors.all(), [])
self.assertQuerySetEqual(
self.assertSequenceEqual(gp.favorite_authors.all(), [])
self.assertSequenceEqual(gp.authorwithage.favorite_authors.all(), [])
self.assertSequenceEqual(
gp.authorwithage.authorwithagechild.favorite_authors.all(), []
)
Loading