summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/db/models/fields/related.py47
-rw-r--r--docs/ref/checks.txt2
-rw-r--r--tests/invalid_models_tests/test_relative_fields.py129
3 files changed, 178 insertions, 0 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 9ef2d29024..de8fe9c339 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -580,6 +580,7 @@ class ForeignObject(RelatedField):
return [
*super().check(**kwargs),
*self._check_to_fields_exist(),
+ *self._check_to_fields_composite_pk(),
*self._check_unique_target(),
]
@@ -605,6 +606,36 @@ class ForeignObject(RelatedField):
)
return errors
+ def _check_to_fields_composite_pk(self):
+ from django.db.models.fields.composite import CompositePrimaryKey
+
+ # Skip nonexistent models.
+ if isinstance(self.remote_field.model, str):
+ return []
+
+ errors = []
+ for to_field in self.to_fields:
+ try:
+ field = (
+ self.remote_field.model._meta.pk
+ if to_field is None
+ else self.remote_field.model._meta.get_field(to_field)
+ )
+ except exceptions.FieldDoesNotExist:
+ pass
+ else:
+ 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.",
+ obj=self,
+ id="fields.E347",
+ )
+ )
+ return errors
+
def _check_unique_target(self):
rel_is_string = isinstance(self.remote_field.model, str)
if rel_is_string or not self.requires_unique_target:
@@ -1470,6 +1501,8 @@ class ManyToManyField(RelatedField):
return warnings
def _check_relationship_model(self, from_model=None, **kwargs):
+ from django.db.models.fields.composite import CompositePrimaryKey
+
if hasattr(self.remote_field.through, "_meta"):
qualified_model_name = "%s.%s" % (
self.remote_field.through._meta.app_label,
@@ -1506,6 +1539,20 @@ class ManyToManyField(RelatedField):
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)
+ ):
+ 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",
+ )
+ )
relationship_model_name = self.remote_field.through._meta.object_name
self_referential = from_model == to_model
# Count foreign keys in intermediate model
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index 17570a1923..edadc06e2e 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -338,6 +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.
Models
------
diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py
index 9b69ae4151..4167e0712a 100644
--- a/tests/invalid_models_tests/test_relative_fields.py
+++ b/tests/invalid_models_tests/test_relative_fields.py
@@ -440,6 +440,84 @@ class RelativeFieldTests(SimpleTestCase):
],
)
+ def test_foreignkey_to_model_with_composite_primary_key(self):
+ class Parent(models.Model):
+ pk = models.CompositePrimaryKey("version", "name")
+ version = models.IntegerField()
+ name = models.CharField(max_length=20)
+
+ class Child(models.Model):
+ rel_class_parent = models.ForeignKey(
+ Parent, on_delete=models.CASCADE, related_name="child_class_set"
+ )
+ rel_string_parent = models.ForeignKey(
+ "Parent", on_delete=models.CASCADE, related_name="child_string_set"
+ )
+
+ 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",
+ ),
+ ],
+ )
+ 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",
+ ),
+ ],
+ )
+
+ def test_many_to_many_to_model_with_composite_primary_key(self):
+ class Parent(models.Model):
+ pk = models.CompositePrimaryKey("version", "name")
+ version = models.IntegerField()
+ name = models.CharField(max_length=20)
+
+ class Child(models.Model):
+ rel_class_parent = models.ManyToManyField(
+ Parent, related_name="child_class_set"
+ )
+ rel_string_parent = models.ManyToManyField(
+ "Parent", related_name="child_string_set"
+ )
+
+ 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",
+ ),
+ ],
+ )
+ 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",
+ ),
+ ],
+ )
+
def test_foreign_key_to_non_unique_field(self):
class Target(models.Model):
bad = models.IntegerField() # No unique=True
@@ -939,6 +1017,57 @@ class RelativeFieldTests(SimpleTestCase):
],
)
+ def test_to_fields_with_composite_primary_key(self):
+ class Parent(models.Model):
+ pk = models.CompositePrimaryKey("version", "name")
+ version = models.IntegerField()
+ name = models.CharField(max_length=20)
+
+ class Child(models.Model):
+ a = models.IntegerField()
+ b = models.IntegerField()
+ parent = models.ForeignObject(
+ Parent,
+ on_delete=models.SET_NULL,
+ from_fields=("a", "b"),
+ to_fields=("pk", "version"),
+ )
+
+ field = Child._meta.get_field("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",
+ ),
+ ],
+ )
+
+ def test_to_field_to_composite_primery_key(self):
+ class Parent(models.Model):
+ pk = models.CompositePrimaryKey("version", "name")
+ version = models.IntegerField()
+ name = models.CharField(max_length=20)
+
+ class Child(models.Model):
+ parent = models.ForeignKey(Parent, on_delete=models.CASCADE, to_field="pk")
+
+ field = Child._meta.get_field("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",
+ ),
+ ],
+ )
+
def test_invalid_related_query_name(self):
class Target(models.Model):
pass