diff --git a/tests/test_validators.py b/tests/test_validators.py index 29b097ef39..e40ed5dc82 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -552,6 +552,45 @@ class Meta: ] +class FancyConditionModel(models.Model): + id = models.IntegerField(primary_key=True) + + +class UniqueConstraintForeignKeyModel(models.Model): + race_name = models.CharField(max_length=100) + position = models.IntegerField() + global_id = models.IntegerField() + fancy_conditions = models.ForeignKey(FancyConditionModel, on_delete=models.CASCADE) + + class Meta: + constraints = [ + models.UniqueConstraint( + name="unique_constraint_foreign_key_model_global_id_uniq", + fields=('global_id',), + ), + models.UniqueConstraint( + name="unique_constraint_foreign_key_model_fancy_1_uniq", + fields=('fancy_conditions',), + condition=models.Q(global_id__lte=1) + ), + models.UniqueConstraint( + name="unique_constraint_foreign_key_model_fancy_3_uniq", + fields=('fancy_conditions',), + condition=models.Q(global_id__gte=3) + ), + models.UniqueConstraint( + name="unique_constraint_foreign_key_model_together_uniq", + fields=('race_name', 'position'), + condition=models.Q(race_name='example'), + ), + models.UniqueConstraint( + name='unique_constraint_foreign_key_model_together_uniq2', + fields=('race_name', 'position'), + condition=models.Q(fancy_conditions__gte=10), + ), + ] + + class UniqueConstraintNullableModel(models.Model): title = models.CharField(max_length=100) age = models.IntegerField(null=True) @@ -570,6 +609,12 @@ class Meta: fields = '__all__' +class UniqueConstraintForeignKeySerializer(serializers.ModelSerializer): + class Meta: + model = UniqueConstraintForeignKeyModel + fields = '__all__' + + class UniqueConstraintNullableSerializer(serializers.ModelSerializer): class Meta: model = UniqueConstraintNullableModel @@ -684,6 +729,118 @@ def test_nullable_unique_constraint_fields_are_not_required(self): self.assertIsInstance(result, UniqueConstraintNullableModel) +class TestUniqueConstraintForeignKeyValidation(TestCase): + def setUp(self): + fancy_model_condition = FancyConditionModel.objects.create(id=1) + self.instance = UniqueConstraintForeignKeyModel.objects.create( + race_name='example', + position=1, + global_id=1, + fancy_conditions=fancy_model_condition + ) + UniqueConstraintForeignKeyModel.objects.create( + race_name='example', + position=2, + global_id=2, + fancy_conditions=fancy_model_condition + ) + UniqueConstraintForeignKeyModel.objects.create( + race_name='other', + position=1, + global_id=3, + fancy_conditions=fancy_model_condition + ) + + def test_repr(self): + serializer = UniqueConstraintForeignKeySerializer() + # the order of validators isn't deterministic so delete + # fancy_conditions field that has two of them + del serializer.fields['fancy_conditions'] + expected = dedent(r""" + UniqueConstraintForeignKeySerializer\(\): + id = IntegerField\(label='ID', read_only=True\) + race_name = CharField\(max_length=100, required=True\) + position = IntegerField\(.*required=True\) + global_id = IntegerField\(.*validators=\[\]\) + class Meta: + validators = \[\)>\] + """) + assert re.search(expected, repr(serializer)) is not None + + def test_unique_together_condition(self): + """ + Fields used in UniqueConstraint's condition must be included + into queryset existence check + """ + fancy_model_condition_9 = FancyConditionModel.objects.create(id=9) + fancy_model_condition_10 = FancyConditionModel.objects.create(id=10) + fancy_model_condition_11 = FancyConditionModel.objects.create(id=11) + UniqueConstraintForeignKeyModel.objects.create( + race_name='condition', + position=1, + global_id=10, + fancy_conditions=fancy_model_condition_10, + ) + serializer = UniqueConstraintForeignKeySerializer(data={ + 'race_name': 'condition', + 'position': 1, + 'global_id': 11, + 'fancy_conditions': fancy_model_condition_9, + }) + assert serializer.is_valid() + serializer = UniqueConstraintForeignKeySerializer(data={ + 'race_name': 'condition', + 'position': 1, + 'global_id': 11, + 'fancy_conditions': fancy_model_condition_11, + }) + assert not serializer.is_valid() + + def test_unique_together_condition_fields_required(self): + """ + Fields used in UniqueConstraint's condition must be present in serializer + """ + serializer = UniqueConstraintForeignKeySerializer(data={ + 'race_name': 'condition', + 'position': 1, + 'global_id': 11, + }) + assert not serializer.is_valid() + assert serializer.errors == {'fancy_conditions': ['This field is required.']} + + class NoFieldsSerializer(serializers.ModelSerializer): + class Meta: + model = UniqueConstraintForeignKeyModel + fields = ('race_name', 'position', 'global_id') + + serializer = NoFieldsSerializer() + assert len(serializer.validators) == 1 + + def test_single_field_uniq_validators(self): + """ + UniqueConstraint with single field must be transformed into + field's UniqueValidator + """ + # Django 5 includes Max and Min values validators for IntegerField + extra_validators_qty = 2 if django_version[0] >= 5 else 0 + serializer = UniqueConstraintForeignKeySerializer() + assert len(serializer.validators) == 2 + validators = serializer.fields['global_id'].validators + assert len(validators) == 1 + extra_validators_qty + assert validators[0].queryset == UniqueConstraintForeignKeyModel.objects + + validators = serializer.fields['fancy_conditions'].validators + assert len(validators) == 2 + extra_validators_qty + ids_in_qs = {frozenset(v.queryset.values_list(flat=True)) for v in validators if hasattr(v, "queryset")} + assert ids_in_qs == {frozenset([1]), frozenset([3])} + + def test_nullable_unique_constraint_fields_are_not_required(self): + serializer = UniqueConstraintNullableSerializer(data={'title': 'Bob'}) + self.assertTrue(serializer.is_valid(), serializer.errors) + result = serializer.save() + self.assertIsInstance(result, UniqueConstraintNullableModel) + + # Tests for `UniqueForDateValidator` # ---------------------------------- pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy