mirror of
https://github.com/django/django.git
synced 2025-01-08 01:15:47 +00:00
Fixed #36034 -- Added system check for ForeignKey/ForeignObject/ManyToManyField to CompositePrimaryKeys.
This commit is contained in:
parent
2a61b5f97c
commit
b322319f9d
@ -580,6 +580,7 @@ class ForeignObject(RelatedField):
|
|||||||
return [
|
return [
|
||||||
*super().check(**kwargs),
|
*super().check(**kwargs),
|
||||||
*self._check_to_fields_exist(),
|
*self._check_to_fields_exist(),
|
||||||
|
*self._check_to_fields_composite_pk(),
|
||||||
*self._check_unique_target(),
|
*self._check_unique_target(),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -605,6 +606,36 @@ class ForeignObject(RelatedField):
|
|||||||
)
|
)
|
||||||
return errors
|
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):
|
def _check_unique_target(self):
|
||||||
rel_is_string = isinstance(self.remote_field.model, str)
|
rel_is_string = isinstance(self.remote_field.model, str)
|
||||||
if rel_is_string or not self.requires_unique_target:
|
if rel_is_string or not self.requires_unique_target:
|
||||||
@ -1470,6 +1501,8 @@ class ManyToManyField(RelatedField):
|
|||||||
return warnings
|
return warnings
|
||||||
|
|
||||||
def _check_relationship_model(self, from_model=None, **kwargs):
|
def _check_relationship_model(self, from_model=None, **kwargs):
|
||||||
|
from django.db.models.fields.composite import CompositePrimaryKey
|
||||||
|
|
||||||
if hasattr(self.remote_field.through, "_meta"):
|
if hasattr(self.remote_field.through, "_meta"):
|
||||||
qualified_model_name = "%s.%s" % (
|
qualified_model_name = "%s.%s" % (
|
||||||
self.remote_field.through._meta.app_label,
|
self.remote_field.through._meta.app_label,
|
||||||
@ -1506,6 +1539,20 @@ 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 (
|
||||||
|
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
|
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
|
||||||
|
@ -338,6 +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
|
||||||
|
model ``<model>`` which is not supported.
|
||||||
|
|
||||||
Models
|
Models
|
||||||
------
|
------
|
||||||
|
@ -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):
|
def test_foreign_key_to_non_unique_field(self):
|
||||||
class Target(models.Model):
|
class Target(models.Model):
|
||||||
bad = models.IntegerField() # No unique=True
|
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):
|
def test_invalid_related_query_name(self):
|
||||||
class Target(models.Model):
|
class Target(models.Model):
|
||||||
pass
|
pass
|
||||||
|
Loading…
Reference in New Issue
Block a user