From bf25a6ecbcf86212976e2f5c605dc8d710e2f55d Mon Sep 17 00:00:00 2001 From: Ion Scerbatiuc Date: Wed, 25 Mar 2015 16:51:40 -0700 Subject: [PATCH 1/3] Test case for using `allow_null` with `many=True` and a fix for it --- rest_framework/serializers.py | 2 +- tests/test_serializer_nested.py | 85 +++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) 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 From 085c3e8a2b5a557d3fd5340eb1c1045d839ebd0c Mon Sep 17 00:00:00 2001 From: Ion Scerbatiuc Date: Thu, 26 Mar 2015 06:32:41 -0700 Subject: [PATCH 2/3] Fixed python 2.6 compatibility --- tests/test_serializer_nested.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index 113f05a01..3053ca218 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -121,7 +121,7 @@ class TestNestedSerializerWithMany: serializer = self.get_serializer(input_data) assert not serializer.is_valid() - assert set(serializer.errors) == {'nested'} + assert set(serializer.errors) == set(['nested']) assert serializer.errors['nested'][0] == serializer.error_messages['null'] def test_run_the_field_validation_even_if_the_field_is_null(self): @@ -132,7 +132,7 @@ class TestNestedSerializerWithMany: serializer = self.get_serializer(input_data, condition_to_allow_null=False) assert not serializer.is_valid() - assert set(serializer.errors) == {'payment_info'} + assert set(serializer.errors) == set(['payment_info']) assert serializer.errors['payment_info'][0] == serializer.ERROR_MESSAGE def test_expected_results_if_not_null(self): From bbd44ae94b4b334da2b454488a33ec398b217ff5 Mon Sep 17 00:00:00 2001 From: Ion Scerbatiuc Date: Sat, 25 Jul 2015 07:52:05 -0700 Subject: [PATCH 3/3] Updated the test cases based on the CR comments --- tests/test_serializer_nested.py | 81 +++++++++++---------------------- 1 file changed, 26 insertions(+), 55 deletions(-) diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index 3053ca218..22d4deca1 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -73,84 +73,55 @@ class TestNotRequiredNestedSerializer: 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() + example = serializers.IntegerField(max_value=10) - class AcceptRequestSerializer(serializers.Serializer): - ERROR_MESSAGE = '`payment_info` is required under this condition' + class TestSerializer(serializers.Serializer): + allow_null = NestedSerializer(many=True, allow_null=True) + not_allow_null = NestedSerializer(many=True) - 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}) + self.Serializer = TestSerializer def test_null_allowed_if_allow_null_is_set(self): input_data = { - 'nested': [{'foo': 'bar'}], - 'payment_info': None + 'allow_null': None, + 'not_allow_null': [{'example': '2'}, {'example': '3'}] } expected_data = { - 'nested': [{'foo': 'bar'}], - 'payment_info': None + 'allow_null': None, + 'not_allow_null': [{'example': 2}, {'example': 3}] } - serializer = self.get_serializer(input_data) + serializer = self.Serializer(data=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 + 'allow_null': None, + 'not_allow_null': None } - serializer = self.get_serializer(input_data) + serializer = self.Serializer(data=input_data) assert not serializer.is_valid() - assert set(serializer.errors) == set(['nested']) - assert serializer.errors['nested'][0] == serializer.error_messages['null'] + + expected_errors = {'not_allow_null': [serializer.error_messages['null']]} + assert serializer.errors == expected_errors 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) + class TestSerializer(self.Serializer): + validation_was_run = False - assert not serializer.is_valid() - assert set(serializer.errors) == set(['payment_info']) - assert serializer.errors['payment_info'][0] == serializer.ERROR_MESSAGE + def validate_allow_null(self, value): + TestSerializer.validation_was_run = True + return value - 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'}, - ] + 'allow_null': None, + 'not_allow_null': [{'example': 2}] } - 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) + serializer = TestSerializer(data=input_data) assert serializer.is_valid() - assert serializer.validated_data == expected_data + assert serializer.validated_data == input_data + assert TestSerializer.validation_was_run