Skip to content

Commit 4920fd5

Browse files
Add TestUniqueConstraintForeignKeyValidation test failing on foreign key field.
1 parent 68af940 commit 4920fd5

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed

tests/test_validators.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,45 @@ class Meta:
552552
]
553553

554554

555+
class FancyConditionModel(models.Model):
556+
id = models.IntegerField(primary_key=True)
557+
558+
559+
class UniqueConstraintForeignKeyModel(models.Model):
560+
race_name = models.CharField(max_length=100)
561+
position = models.IntegerField()
562+
global_id = models.IntegerField()
563+
fancy_conditions = models.ForeignKey(FancyConditionModel, on_delete=models.CASCADE)
564+
565+
class Meta:
566+
constraints = [
567+
models.UniqueConstraint(
568+
name="unique_constraint_foreign_key_model_global_id_uniq",
569+
fields=('global_id',),
570+
),
571+
models.UniqueConstraint(
572+
name="unique_constraint_foreign_key_model_fancy_1_uniq",
573+
fields=('fancy_conditions',),
574+
condition=models.Q(global_id__lte=1)
575+
),
576+
models.UniqueConstraint(
577+
name="unique_constraint_foreign_key_model_fancy_3_uniq",
578+
fields=('fancy_conditions',),
579+
condition=models.Q(global_id__gte=3)
580+
),
581+
models.UniqueConstraint(
582+
name="unique_constraint_foreign_key_model_together_uniq",
583+
fields=('race_name', 'position'),
584+
condition=models.Q(race_name='example'),
585+
),
586+
models.UniqueConstraint(
587+
name='unique_constraint_foreign_key_model_together_uniq2',
588+
fields=('race_name', 'position'),
589+
condition=models.Q(fancy_conditions__gte=10),
590+
),
591+
]
592+
593+
555594
class UniqueConstraintNullableModel(models.Model):
556595
title = models.CharField(max_length=100)
557596
age = models.IntegerField(null=True)
@@ -570,6 +609,12 @@ class Meta:
570609
fields = '__all__'
571610

572611

612+
class UniqueConstraintForeignKeySerializer(serializers.ModelSerializer):
613+
class Meta:
614+
model = UniqueConstraintForeignKeyModel
615+
fields = '__all__'
616+
617+
573618
class UniqueConstraintNullableSerializer(serializers.ModelSerializer):
574619
class Meta:
575620
model = UniqueConstraintNullableModel
@@ -684,6 +729,118 @@ def test_nullable_unique_constraint_fields_are_not_required(self):
684729
self.assertIsInstance(result, UniqueConstraintNullableModel)
685730

686731

732+
class TestUniqueConstraintForeignKeyValidation(TestCase):
733+
def setUp(self):
734+
fancy_model_condition = FancyConditionModel.objects.create(id=1)
735+
self.instance = UniqueConstraintForeignKeyModel.objects.create(
736+
race_name='example',
737+
position=1,
738+
global_id=1,
739+
fancy_conditions=fancy_model_condition
740+
)
741+
UniqueConstraintForeignKeyModel.objects.create(
742+
race_name='example',
743+
position=2,
744+
global_id=2,
745+
fancy_conditions=fancy_model_condition
746+
)
747+
UniqueConstraintForeignKeyModel.objects.create(
748+
race_name='other',
749+
position=1,
750+
global_id=3,
751+
fancy_conditions=fancy_model_condition
752+
)
753+
754+
def test_repr(self):
755+
serializer = UniqueConstraintForeignKeySerializer()
756+
# the order of validators isn't deterministic so delete
757+
# fancy_conditions field that has two of them
758+
del serializer.fields['fancy_conditions']
759+
expected = dedent(r"""
760+
UniqueConstraintForeignKeySerializer\(\):
761+
id = IntegerField\(label='ID', read_only=True\)
762+
race_name = CharField\(max_length=100, required=True\)
763+
position = IntegerField\(.*required=True\)
764+
global_id = IntegerField\(.*validators=\[<UniqueValidator\(queryset=UniqueConstraintForeignKeyModel.objects.all\(\)\)>\]\)
765+
class Meta:
766+
validators = \[<UniqueTogetherValidator\(queryset=UniqueConstraintForeignKeyModel.objects.all\(\), fields=\('race_name', 'position'\), condition=<Q: \(AND: \('race_name', 'example'\)\)>\)>\]
767+
""")
768+
assert re.search(expected, repr(serializer)) is not None
769+
770+
def test_unique_together_condition(self):
771+
"""
772+
Fields used in UniqueConstraint's condition must be included
773+
into queryset existence check
774+
"""
775+
fancy_model_condition_9 = FancyConditionModel.objects.create(id=9)
776+
fancy_model_condition_10 = FancyConditionModel.objects.create(id=10)
777+
fancy_model_condition_11 = FancyConditionModel.objects.create(id=11)
778+
UniqueConstraintForeignKeyModel.objects.create(
779+
race_name='condition',
780+
position=1,
781+
global_id=10,
782+
fancy_conditions=fancy_model_condition_10,
783+
)
784+
serializer = UniqueConstraintForeignKeySerializer(data={
785+
'race_name': 'condition',
786+
'position': 1,
787+
'global_id': 11,
788+
'fancy_conditions': fancy_model_condition_9,
789+
})
790+
assert serializer.is_valid()
791+
serializer = UniqueConstraintForeignKeySerializer(data={
792+
'race_name': 'condition',
793+
'position': 1,
794+
'global_id': 11,
795+
'fancy_conditions': fancy_model_condition_11,
796+
})
797+
assert not serializer.is_valid()
798+
799+
def test_unique_together_condition_fields_required(self):
800+
"""
801+
Fields used in UniqueConstraint's condition must be present in serializer
802+
"""
803+
serializer = UniqueConstraintForeignKeySerializer(data={
804+
'race_name': 'condition',
805+
'position': 1,
806+
'global_id': 11,
807+
})
808+
assert not serializer.is_valid()
809+
assert serializer.errors == {'fancy_conditions': ['This field is required.']}
810+
811+
class NoFieldsSerializer(serializers.ModelSerializer):
812+
class Meta:
813+
model = UniqueConstraintForeignKeyModel
814+
fields = ('race_name', 'position', 'global_id')
815+
816+
serializer = NoFieldsSerializer()
817+
assert len(serializer.validators) == 1
818+
819+
def test_single_field_uniq_validators(self):
820+
"""
821+
UniqueConstraint with single field must be transformed into
822+
field's UniqueValidator
823+
"""
824+
# Django 5 includes Max and Min values validators for IntegerField
825+
extra_validators_qty = 2 if django_version[0] >= 5 else 0
826+
serializer = UniqueConstraintForeignKeySerializer()
827+
assert len(serializer.validators) == 2
828+
validators = serializer.fields['global_id'].validators
829+
assert len(validators) == 1 + extra_validators_qty
830+
assert validators[0].queryset == UniqueConstraintForeignKeyModel.objects
831+
832+
validators = serializer.fields['fancy_conditions'].validators
833+
assert len(validators) == 2 + extra_validators_qty
834+
ids_in_qs = {frozenset(v.queryset.values_list(flat=True)) for v in validators if hasattr(v, "queryset")}
835+
assert ids_in_qs == {frozenset([1]), frozenset([3])}
836+
837+
def test_nullable_unique_constraint_fields_are_not_required(self):
838+
serializer = UniqueConstraintNullableSerializer(data={'title': 'Bob'})
839+
self.assertTrue(serializer.is_valid(), serializer.errors)
840+
result = serializer.save()
841+
self.assertIsInstance(result, UniqueConstraintNullableModel)
842+
843+
687844
# Tests for `UniqueForDateValidator`
688845
# ----------------------------------
689846

0 commit comments

Comments
 (0)