diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 60ee7e5cc..668c52566 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -50,7 +50,7 @@ from rest_framework.relations import * # NOQA # isort:skip LIST_SERIALIZER_KWARGS = ( 'read_only', 'write_only', 'required', 'default', 'initial', 'source', 'label', 'help_text', 'style', 'error_messages', 'allow_empty', - 'instance', 'data', 'partial', 'context' + 'instance', 'data', 'partial', 'context', 'allow_null' ) diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index b14a58c9e..113f05a01 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -69,3 +69,88 @@ class TestNotRequiredNestedSerializer: input_data = QueryDict('nested[one]=1') serializer = self.Serializer(data=input_data) assert serializer.is_valid() + + +class TestNestedSerializerWithMany: + def setup(self): + class PaymentInfoSerializer(serializers.Serializer): + url = serializers.URLField() + amount = serializers.DecimalField(max_digits=6, decimal_places=2) + + class NestedSerializer(serializers.Serializer): + foo = serializers.CharField() + + class AcceptRequestSerializer(serializers.Serializer): + ERROR_MESSAGE = '`payment_info` is required under this condition' + + nested = NestedSerializer(many=True) + payment_info = PaymentInfoSerializer(many=True, allow_null=True) + + def validate_payment_info(self, value): + if value is None and not self.context['condition_to_allow_null']: + raise serializers.ValidationError(self.ERROR_MESSAGE) + + return value + + self.Serializer = AcceptRequestSerializer + + def get_serializer(self, data, condition_to_allow_null=True): + return self.Serializer( + data=data, + context={'condition_to_allow_null': condition_to_allow_null}) + + def test_null_allowed_if_allow_null_is_set(self): + input_data = { + 'nested': [{'foo': 'bar'}], + 'payment_info': None + } + expected_data = { + 'nested': [{'foo': 'bar'}], + 'payment_info': None + } + serializer = self.get_serializer(input_data) + + assert serializer.is_valid(), serializer.errors + assert serializer.validated_data == expected_data + + def test_null_is_not_allowed_if_allow_null_is_not_set(self): + input_data = { + 'nested': None, + 'payment_info': None + } + serializer = self.get_serializer(input_data) + + assert not serializer.is_valid() + assert set(serializer.errors) == {'nested'} + assert serializer.errors['nested'][0] == serializer.error_messages['null'] + + def test_run_the_field_validation_even_if_the_field_is_null(self): + input_data = { + 'nested': [{'foo': 'bar'}], + 'payment_info': None, + } + serializer = self.get_serializer(input_data, condition_to_allow_null=False) + + assert not serializer.is_valid() + assert set(serializer.errors) == {'payment_info'} + assert serializer.errors['payment_info'][0] == serializer.ERROR_MESSAGE + + def test_expected_results_if_not_null(self): + input_data = { + 'nested': [{'foo': 'bar'}], + 'payment_info': [ + {'url': 'https://domain.org/api/payment-method/1/', 'amount': '22'}, + {'url': 'https://domain.org/api/payment-method/2/', 'amount': '33'}, + ] + } + expected_data = { + 'nested': [{'foo': 'bar'}], + 'payment_info': [ + {'url': 'https://domain.org/api/payment-method/1/', 'amount': 22}, + {'url': 'https://domain.org/api/payment-method/2/', 'amount': 33}, + ] + } + serializer = self.get_serializer(input_data) + + assert serializer.is_valid() + assert serializer.validated_data == expected_data