mirror of https://github.com/django/django.git
Fixed #31185 -- Fixed detecting of unique fields in ForeignKey/ForeignObject checks when using Meta.constraints.
This commit is contained in:
parent
41ebe60728
commit
5bf28ac2ed
|
@ -528,6 +528,10 @@ class ForeignObject(RelatedField):
|
||||||
frozenset(ut)
|
frozenset(ut)
|
||||||
for ut in self.remote_field.model._meta.unique_together
|
for ut in self.remote_field.model._meta.unique_together
|
||||||
})
|
})
|
||||||
|
unique_foreign_fields.update({
|
||||||
|
frozenset(uc.fields)
|
||||||
|
for uc in self.remote_field.model._meta.total_unique_constraints
|
||||||
|
})
|
||||||
foreign_fields = {f.name for f in self.foreign_related_fields}
|
foreign_fields = {f.name for f in self.foreign_related_fields}
|
||||||
has_unique_constraint = any(u <= foreign_fields for u in unique_foreign_fields)
|
has_unique_constraint = any(u <= foreign_fields for u in unique_foreign_fields)
|
||||||
|
|
||||||
|
@ -541,8 +545,10 @@ class ForeignObject(RelatedField):
|
||||||
"No subset of the fields %s on model '%s' is unique."
|
"No subset of the fields %s on model '%s' is unique."
|
||||||
% (field_combination, model_name),
|
% (field_combination, model_name),
|
||||||
hint=(
|
hint=(
|
||||||
"Add unique=True on any of those fields or add at "
|
'Mark a single field as unique=True or add a set of '
|
||||||
"least a subset of them to a unique_together constraint."
|
'fields to a unique constraint (via unique_together '
|
||||||
|
'or a UniqueConstraint (without condition) in the '
|
||||||
|
'model Meta.constraints).'
|
||||||
),
|
),
|
||||||
obj=self,
|
obj=self,
|
||||||
id='fields.E310',
|
id='fields.E310',
|
||||||
|
@ -553,8 +559,13 @@ class ForeignObject(RelatedField):
|
||||||
model_name = self.remote_field.model.__name__
|
model_name = self.remote_field.model.__name__
|
||||||
return [
|
return [
|
||||||
checks.Error(
|
checks.Error(
|
||||||
"'%s.%s' must set unique=True because it is referenced by "
|
"'%s.%s' must be unique because it is referenced by "
|
||||||
"a foreign key." % (model_name, field_name),
|
"a foreign key." % (model_name, field_name),
|
||||||
|
hint=(
|
||||||
|
'Add unique=True to this field or add a '
|
||||||
|
'UniqueConstraint (without condition) in the model '
|
||||||
|
'Meta.constraints.'
|
||||||
|
),
|
||||||
obj=self,
|
obj=self,
|
||||||
id='fields.E311',
|
id='fields.E311',
|
||||||
)
|
)
|
||||||
|
|
|
@ -220,7 +220,7 @@ Related fields
|
||||||
``'__'``.
|
``'__'``.
|
||||||
* **fields.E310**: No subset of the fields ``<field1>``, ``<field2>``, ... on
|
* **fields.E310**: No subset of the fields ``<field1>``, ``<field2>``, ... on
|
||||||
model ``<model>`` is unique.
|
model ``<model>`` is unique.
|
||||||
* **fields.E311**: ``<model>`` must set ``unique=True`` because it is
|
* **fields.E311**: ``<model>.<field name>`` must be unique because it is
|
||||||
referenced by a ``ForeignKey``.
|
referenced by a ``ForeignKey``.
|
||||||
* **fields.E312**: The ``to_field`` ``<field name>`` doesn't exist on the
|
* **fields.E312**: The ``to_field`` ``<field name>`` doesn't exist on the
|
||||||
related model ``<app label>.<model>``.
|
related model ``<app label>.<model>``.
|
||||||
|
|
|
@ -352,7 +352,11 @@ class RelativeFieldTests(SimpleTestCase):
|
||||||
field = Model._meta.get_field('foreign_key')
|
field = Model._meta.get_field('foreign_key')
|
||||||
self.assertEqual(field.check(), [
|
self.assertEqual(field.check(), [
|
||||||
Error(
|
Error(
|
||||||
"'Target.bad' must set unique=True because it is referenced by a foreign key.",
|
"'Target.bad' must be unique because it is referenced by a foreign key.",
|
||||||
|
hint=(
|
||||||
|
'Add unique=True to this field or add a UniqueConstraint '
|
||||||
|
'(without condition) in the model Meta.constraints.'
|
||||||
|
),
|
||||||
obj=field,
|
obj=field,
|
||||||
id='fields.E311',
|
id='fields.E311',
|
||||||
),
|
),
|
||||||
|
@ -368,12 +372,64 @@ class RelativeFieldTests(SimpleTestCase):
|
||||||
field = Model._meta.get_field('field')
|
field = Model._meta.get_field('field')
|
||||||
self.assertEqual(field.check(), [
|
self.assertEqual(field.check(), [
|
||||||
Error(
|
Error(
|
||||||
"'Target.bad' must set unique=True because it is referenced by a foreign key.",
|
"'Target.bad' must be unique because it is referenced by a foreign key.",
|
||||||
|
hint=(
|
||||||
|
'Add unique=True to this field or add a UniqueConstraint '
|
||||||
|
'(without condition) in the model Meta.constraints.'
|
||||||
|
),
|
||||||
obj=field,
|
obj=field,
|
||||||
id='fields.E311',
|
id='fields.E311',
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_foreign_key_to_partially_unique_field(self):
|
||||||
|
class Target(models.Model):
|
||||||
|
source = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=['source'],
|
||||||
|
name='tfktpuf_partial_unique',
|
||||||
|
condition=models.Q(pk__gt=2),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class Model(models.Model):
|
||||||
|
field = models.ForeignKey(Target, models.CASCADE, to_field='source')
|
||||||
|
|
||||||
|
field = Model._meta.get_field('field')
|
||||||
|
self.assertEqual(field.check(), [
|
||||||
|
Error(
|
||||||
|
"'Target.source' must be unique because it is referenced by a "
|
||||||
|
"foreign key.",
|
||||||
|
hint=(
|
||||||
|
'Add unique=True to this field or add a UniqueConstraint '
|
||||||
|
'(without condition) in the model Meta.constraints.'
|
||||||
|
),
|
||||||
|
obj=field,
|
||||||
|
id='fields.E311',
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_foreign_key_to_unique_field_with_meta_constraint(self):
|
||||||
|
class Target(models.Model):
|
||||||
|
source = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=['source'],
|
||||||
|
name='tfktufwmc_unique',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class Model(models.Model):
|
||||||
|
field = models.ForeignKey(Target, models.CASCADE, to_field='source')
|
||||||
|
|
||||||
|
field = Model._meta.get_field('field')
|
||||||
|
self.assertEqual(field.check(), [])
|
||||||
|
|
||||||
def test_foreign_object_to_non_unique_fields(self):
|
def test_foreign_object_to_non_unique_fields(self):
|
||||||
class Person(models.Model):
|
class Person(models.Model):
|
||||||
# Note that both fields are not unique.
|
# Note that both fields are not unique.
|
||||||
|
@ -396,14 +452,82 @@ class RelativeFieldTests(SimpleTestCase):
|
||||||
Error(
|
Error(
|
||||||
"No subset of the fields 'country_id', 'city_id' on model 'Person' is unique.",
|
"No subset of the fields 'country_id', 'city_id' on model 'Person' is unique.",
|
||||||
hint=(
|
hint=(
|
||||||
"Add unique=True on any of those fields or add at least "
|
'Mark a single field as unique=True or add a set of '
|
||||||
"a subset of them to a unique_together constraint."
|
'fields to a unique constraint (via unique_together or a '
|
||||||
|
'UniqueConstraint (without condition) in the model '
|
||||||
|
'Meta.constraints).'
|
||||||
),
|
),
|
||||||
obj=field,
|
obj=field,
|
||||||
id='fields.E310',
|
id='fields.E310',
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_foreign_object_to_partially_unique_field(self):
|
||||||
|
class Person(models.Model):
|
||||||
|
country_id = models.IntegerField()
|
||||||
|
city_id = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=['country_id', 'city_id'],
|
||||||
|
name='tfotpuf_partial_unique',
|
||||||
|
condition=models.Q(pk__gt=2),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class MMembership(models.Model):
|
||||||
|
person_country_id = models.IntegerField()
|
||||||
|
person_city_id = models.IntegerField()
|
||||||
|
person = models.ForeignObject(
|
||||||
|
Person,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
from_fields=['person_country_id', 'person_city_id'],
|
||||||
|
to_fields=['country_id', 'city_id'],
|
||||||
|
)
|
||||||
|
|
||||||
|
field = MMembership._meta.get_field('person')
|
||||||
|
self.assertEqual(field.check(), [
|
||||||
|
Error(
|
||||||
|
"No subset of the fields 'country_id', 'city_id' on model "
|
||||||
|
"'Person' is unique.",
|
||||||
|
hint=(
|
||||||
|
'Mark a single field as unique=True or add a set of '
|
||||||
|
'fields to a unique constraint (via unique_together or a '
|
||||||
|
'UniqueConstraint (without condition) in the model '
|
||||||
|
'Meta.constraints).'
|
||||||
|
),
|
||||||
|
obj=field,
|
||||||
|
id='fields.E310',
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_foreign_object_to_unique_field_with_meta_constraint(self):
|
||||||
|
class Person(models.Model):
|
||||||
|
country_id = models.IntegerField()
|
||||||
|
city_id = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=['country_id', 'city_id'],
|
||||||
|
name='tfotpuf_unique',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class MMembership(models.Model):
|
||||||
|
person_country_id = models.IntegerField()
|
||||||
|
person_city_id = models.IntegerField()
|
||||||
|
person = models.ForeignObject(
|
||||||
|
Person,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
from_fields=['person_country_id', 'person_city_id'],
|
||||||
|
to_fields=['country_id', 'city_id'],
|
||||||
|
)
|
||||||
|
|
||||||
|
field = MMembership._meta.get_field('person')
|
||||||
|
self.assertEqual(field.check(), [])
|
||||||
|
|
||||||
def test_on_delete_set_null_on_non_nullable_field(self):
|
def test_on_delete_set_null_on_non_nullable_field(self):
|
||||||
class Person(models.Model):
|
class Person(models.Model):
|
||||||
pass
|
pass
|
||||||
|
@ -1453,8 +1577,10 @@ class M2mThroughFieldsTests(SimpleTestCase):
|
||||||
Error(
|
Error(
|
||||||
"No subset of the fields 'a', 'b' on model 'Parent' is unique.",
|
"No subset of the fields 'a', 'b' on model 'Parent' is unique.",
|
||||||
hint=(
|
hint=(
|
||||||
"Add unique=True on any of those fields or add at least "
|
'Mark a single field as unique=True or add a set of '
|
||||||
"a subset of them to a unique_together constraint."
|
'fields to a unique constraint (via unique_together or a '
|
||||||
|
'UniqueConstraint (without condition) in the model '
|
||||||
|
'Meta.constraints).'
|
||||||
),
|
),
|
||||||
obj=field,
|
obj=field,
|
||||||
id='fields.E310',
|
id='fields.E310',
|
||||||
|
@ -1489,8 +1615,10 @@ class M2mThroughFieldsTests(SimpleTestCase):
|
||||||
Error(
|
Error(
|
||||||
"No subset of the fields 'a', 'b', 'd' on model 'Parent' is unique.",
|
"No subset of the fields 'a', 'b', 'd' on model 'Parent' is unique.",
|
||||||
hint=(
|
hint=(
|
||||||
"Add unique=True on any of those fields or add at least "
|
'Mark a single field as unique=True or add a set of '
|
||||||
"a subset of them to a unique_together constraint."
|
'fields to a unique constraint (via unique_together or a '
|
||||||
|
'UniqueConstraint (without condition) in the model '
|
||||||
|
'Meta.constraints).'
|
||||||
),
|
),
|
||||||
obj=field,
|
obj=field,
|
||||||
id='fields.E310',
|
id='fields.E310',
|
||||||
|
|
Loading…
Reference in New Issue