mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 01:47:59 +03:00 
			
		
		
		
	Merge pull request #2232 from tomchristie/validate-in-list-serializer
Added ListSerializer.validate().
This commit is contained in:
		
						commit
						62cca1eec7
					
				| 
						 | 
					@ -294,6 +294,34 @@ class Field(object):
 | 
				
			||||||
            return self.default()
 | 
					            return self.default()
 | 
				
			||||||
        return self.default
 | 
					        return self.default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate_empty_values(self, data):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Validate empty values, and either:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        * Raise `ValidationError`, indicating invalid data.
 | 
				
			||||||
 | 
					        * Raise `SkipField`, indicating that the field should be ignored.
 | 
				
			||||||
 | 
					        * Return (True, data), indicating an empty value that should be
 | 
				
			||||||
 | 
					          returned without any furhter validation being applied.
 | 
				
			||||||
 | 
					        * Return (False, data), indicating a non-empty value, that should
 | 
				
			||||||
 | 
					          have validation applied as normal.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if self.read_only:
 | 
				
			||||||
 | 
					            return (True, self.get_default())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if data is empty:
 | 
				
			||||||
 | 
					            if getattr(self.root, 'partial', False):
 | 
				
			||||||
 | 
					                raise SkipField()
 | 
				
			||||||
 | 
					            if self.required:
 | 
				
			||||||
 | 
					                self.fail('required')
 | 
				
			||||||
 | 
					            return (True, self.get_default())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if data is None:
 | 
				
			||||||
 | 
					            if not self.allow_null:
 | 
				
			||||||
 | 
					                self.fail('null')
 | 
				
			||||||
 | 
					            return (True, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (False, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run_validation(self, data=empty):
 | 
					    def run_validation(self, data=empty):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Validate a simple representation and return the internal value.
 | 
					        Validate a simple representation and return the internal value.
 | 
				
			||||||
| 
						 | 
					@ -304,21 +332,9 @@ class Field(object):
 | 
				
			||||||
        May raise `SkipField` if the field should not be included in the
 | 
					        May raise `SkipField` if the field should not be included in the
 | 
				
			||||||
        validated data.
 | 
					        validated data.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if self.read_only:
 | 
					        (is_empty_value, data) = self.validate_empty_values(data)
 | 
				
			||||||
            return self.get_default()
 | 
					        if is_empty_value:
 | 
				
			||||||
 | 
					            return data
 | 
				
			||||||
        if data is empty:
 | 
					 | 
				
			||||||
            if getattr(self.root, 'partial', False):
 | 
					 | 
				
			||||||
                raise SkipField()
 | 
					 | 
				
			||||||
            if self.required:
 | 
					 | 
				
			||||||
                self.fail('required')
 | 
					 | 
				
			||||||
            return self.get_default()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if data is None:
 | 
					 | 
				
			||||||
            if not self.allow_null:
 | 
					 | 
				
			||||||
                self.fail('null')
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        value = self.to_internal_value(data)
 | 
					        value = self.to_internal_value(data)
 | 
				
			||||||
        self.run_validators(value)
 | 
					        self.run_validators(value)
 | 
				
			||||||
        return value
 | 
					        return value
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -229,6 +229,35 @@ class SerializerMetaclass(type):
 | 
				
			||||||
        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
 | 
					        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_validation_error_detail(exc):
 | 
				
			||||||
 | 
					    assert isinstance(exc, (ValidationError, DjangoValidationError))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if isinstance(exc, DjangoValidationError):
 | 
				
			||||||
 | 
					        # Normally you should raise `serializers.ValidationError`
 | 
				
			||||||
 | 
					        # inside your codebase, but we handle Django's validation
 | 
				
			||||||
 | 
					        # exception class as well for simpler compat.
 | 
				
			||||||
 | 
					        # Eg. Calling Model.clean() explicitly inside Serializer.validate()
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    elif isinstance(exc.detail, dict):
 | 
				
			||||||
 | 
					        # If errors may be a dict we use the standard {key: list of values}.
 | 
				
			||||||
 | 
					        # Here we ensure that all the values are *lists* of errors.
 | 
				
			||||||
 | 
					        return dict([
 | 
				
			||||||
 | 
					            (key, value if isinstance(value, list) else [value])
 | 
				
			||||||
 | 
					            for key, value in exc.detail.items()
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					    elif isinstance(exc.detail, list):
 | 
				
			||||||
 | 
					        # Errors raised as a list are non-field errors.
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            api_settings.NON_FIELD_ERRORS_KEY: exc.detail
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    # Errors raised as a string are non-field errors.
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@six.add_metaclass(SerializerMetaclass)
 | 
					@six.add_metaclass(SerializerMetaclass)
 | 
				
			||||||
class Serializer(BaseSerializer):
 | 
					class Serializer(BaseSerializer):
 | 
				
			||||||
    default_error_messages = {
 | 
					    default_error_messages = {
 | 
				
			||||||
| 
						 | 
					@ -293,18 +322,24 @@ class Serializer(BaseSerializer):
 | 
				
			||||||
        performed by validators and the `.validate()` method should
 | 
					        performed by validators and the `.validate()` method should
 | 
				
			||||||
        be coerced into an error dictionary with a 'non_fields_error' key.
 | 
					        be coerced into an error dictionary with a 'non_fields_error' key.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if data is empty:
 | 
					        (is_empty_value, data) = self.validate_empty_values(data)
 | 
				
			||||||
            if getattr(self.root, 'partial', False):
 | 
					        if is_empty_value:
 | 
				
			||||||
                raise SkipField()
 | 
					            return data
 | 
				
			||||||
            if self.required:
 | 
					 | 
				
			||||||
                self.fail('required')
 | 
					 | 
				
			||||||
            return self.get_default()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if data is None:
 | 
					        value = self.to_internal_value(data)
 | 
				
			||||||
            if not self.allow_null:
 | 
					        try:
 | 
				
			||||||
                self.fail('null')
 | 
					            self.run_validators(value)
 | 
				
			||||||
            return None
 | 
					            value = self.validate(value)
 | 
				
			||||||
 | 
					            assert value is not None, '.validate() should return the validated data'
 | 
				
			||||||
 | 
					        except (ValidationError, DjangoValidationError) as exc:
 | 
				
			||||||
 | 
					            raise ValidationError(detail=get_validation_error_detail(exc))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def to_internal_value(self, data):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Dict of native values <- Dict of primitive datatypes.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        if not isinstance(data, dict):
 | 
					        if not isinstance(data, dict):
 | 
				
			||||||
            message = self.error_messages['invalid'].format(
 | 
					            message = self.error_messages['invalid'].format(
 | 
				
			||||||
                datatype=type(data).__name__
 | 
					                datatype=type(data).__name__
 | 
				
			||||||
| 
						 | 
					@ -313,42 +348,6 @@ class Serializer(BaseSerializer):
 | 
				
			||||||
                api_settings.NON_FIELD_ERRORS_KEY: [message]
 | 
					                api_settings.NON_FIELD_ERRORS_KEY: [message]
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        value = self.to_internal_value(data)
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            self.run_validators(value)
 | 
					 | 
				
			||||||
            value = self.validate(value)
 | 
					 | 
				
			||||||
            assert value is not None, '.validate() should return the validated data'
 | 
					 | 
				
			||||||
        except ValidationError as exc:
 | 
					 | 
				
			||||||
            if isinstance(exc.detail, dict):
 | 
					 | 
				
			||||||
                # .validate() errors may be a dict, in which case, use
 | 
					 | 
				
			||||||
                # standard {key: list of values} style.
 | 
					 | 
				
			||||||
                raise ValidationError(dict([
 | 
					 | 
				
			||||||
                    (key, value if isinstance(value, list) else [value])
 | 
					 | 
				
			||||||
                    for key, value in exc.detail.items()
 | 
					 | 
				
			||||||
                ]))
 | 
					 | 
				
			||||||
            elif isinstance(exc.detail, list):
 | 
					 | 
				
			||||||
                raise ValidationError({
 | 
					 | 
				
			||||||
                    api_settings.NON_FIELD_ERRORS_KEY: exc.detail
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                raise ValidationError({
 | 
					 | 
				
			||||||
                    api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
        except DjangoValidationError as exc:
 | 
					 | 
				
			||||||
            # Normally you should raise `serializers.ValidationError`
 | 
					 | 
				
			||||||
            # inside your codebase, but we handle Django's validation
 | 
					 | 
				
			||||||
            # exception class as well for simpler compat.
 | 
					 | 
				
			||||||
            # Eg. Calling Model.clean() explicitly inside Serializer.validate()
 | 
					 | 
				
			||||||
            raise ValidationError({
 | 
					 | 
				
			||||||
                api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return value
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def to_internal_value(self, data):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Dict of native values <- Dict of primitive datatypes.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        ret = OrderedDict()
 | 
					        ret = OrderedDict()
 | 
				
			||||||
        errors = OrderedDict()
 | 
					        errors = OrderedDict()
 | 
				
			||||||
        fields = [
 | 
					        fields = [
 | 
				
			||||||
| 
						 | 
					@ -462,6 +461,26 @@ class ListSerializer(BaseSerializer):
 | 
				
			||||||
            return html.parse_html_list(dictionary, prefix=self.field_name)
 | 
					            return html.parse_html_list(dictionary, prefix=self.field_name)
 | 
				
			||||||
        return dictionary.get(self.field_name, empty)
 | 
					        return dictionary.get(self.field_name, empty)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run_validation(self, data=empty):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        We override the default `run_validation`, because the validation
 | 
				
			||||||
 | 
					        performed by validators and the `.validate()` method should
 | 
				
			||||||
 | 
					        be coerced into an error dictionary with a 'non_fields_error' key.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        (is_empty_value, data) = self.validate_empty_values(data)
 | 
				
			||||||
 | 
					        if is_empty_value:
 | 
				
			||||||
 | 
					            return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        value = self.to_internal_value(data)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.run_validators(value)
 | 
				
			||||||
 | 
					            value = self.validate(value)
 | 
				
			||||||
 | 
					            assert value is not None, '.validate() should return the validated data'
 | 
				
			||||||
 | 
					        except (ValidationError, DjangoValidationError) as exc:
 | 
				
			||||||
 | 
					            raise ValidationError(detail=get_validation_error_detail(exc))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def to_internal_value(self, data):
 | 
					    def to_internal_value(self, data):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        List of dicts of native values <- List of dicts of primitive datatypes.
 | 
					        List of dicts of native values <- List of dicts of primitive datatypes.
 | 
				
			||||||
| 
						 | 
					@ -503,6 +522,9 @@ class ListSerializer(BaseSerializer):
 | 
				
			||||||
            self.child.to_representation(item) for item in iterable
 | 
					            self.child.to_representation(item) for item in iterable
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate(self, attrs):
 | 
				
			||||||
 | 
					        return attrs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update(self, instance, validated_data):
 | 
					    def update(self, instance, validated_data):
 | 
				
			||||||
        raise NotImplementedError(
 | 
					        raise NotImplementedError(
 | 
				
			||||||
            "Serializers with many=True do not support multiple update by "
 | 
					            "Serializers with many=True do not support multiple update by "
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -272,3 +272,19 @@ class TestNestedListOfListsSerializer:
 | 
				
			||||||
        serializer = self.Serializer(data=input_data)
 | 
					        serializer = self.Serializer(data=input_data)
 | 
				
			||||||
        assert serializer.is_valid()
 | 
					        assert serializer.is_valid()
 | 
				
			||||||
        assert serializer.validated_data == expected_output
 | 
					        assert serializer.validated_data == expected_output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestListSerializerClass:
 | 
				
			||||||
 | 
					    """Tests for a custom list_serializer_class."""
 | 
				
			||||||
 | 
					    def test_list_serializer_class_validate(self):
 | 
				
			||||||
 | 
					        class CustomListSerializer(serializers.ListSerializer):
 | 
				
			||||||
 | 
					            def validate(self, attrs):
 | 
				
			||||||
 | 
					                raise serializers.ValidationError('Non field error')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class TestSerializer(serializers.Serializer):
 | 
				
			||||||
 | 
					            class Meta:
 | 
				
			||||||
 | 
					                list_serializer_class = CustomListSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        serializer = TestSerializer(data=[], many=True)
 | 
				
			||||||
 | 
					        assert not serializer.is_valid()
 | 
				
			||||||
 | 
					        assert serializer.errors == {'non_field_errors': ['Non field error']}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user