mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-29 13:04:03 +03:00
.validate() can raise field errors or non-field errors
This commit is contained in:
parent
05cbec9dd7
commit
c5d1be8eac
|
@ -163,7 +163,7 @@ The `validate_<field_name>` method hooks that can be attached to serializer clas
|
||||||
raise serializers.ValidationError('This field should be a multiple of ten.')
|
raise serializers.ValidationError('This field should be a multiple of ten.')
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
This is now simplified slightly, and the method hooks simply take the value to be validated, and return it's validated value.
|
This is now simplified slightly, and the method hooks simply take the value to be validated, and return the validated value.
|
||||||
|
|
||||||
def validate_score(self, value):
|
def validate_score(self, value):
|
||||||
if value % 10 != 0:
|
if value % 10 != 0:
|
||||||
|
@ -172,6 +172,22 @@ This is now simplified slightly, and the method hooks simply take the value to b
|
||||||
|
|
||||||
Any ad-hoc validation that applies to more than one field should go in the `.validate(self, attrs)` method as usual.
|
Any ad-hoc validation that applies to more than one field should go in the `.validate(self, attrs)` method as usual.
|
||||||
|
|
||||||
|
Because `.validate_<field_name>` would previously accept the complete dictionary of attributes, it could be used to validate a field depending on the input in another field. Now if you need to do this you should use `.validate()` instead.
|
||||||
|
|
||||||
|
You can either return `non_field_errors` from the validate method by raising a simple `ValidationError`
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
# serializer.errors == {'non_field_errors': ['A non field error']}
|
||||||
|
raise serailizers.ValidationError('A non field error')
|
||||||
|
|
||||||
|
Alternatively if you want the errors to be against a specific field, use a dictionary of when instantiating the `ValidationError`, like so:
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
# serializer.errors == {'my_field': ['A field error']}
|
||||||
|
raise serailizers.ValidationError({'my_field': 'A field error'})
|
||||||
|
|
||||||
|
This ensures you can still write validation that compares all the input fields, but that marks the error against a particular field.
|
||||||
|
|
||||||
#### Limitations of ModelSerializer validation.
|
#### Limitations of ModelSerializer validation.
|
||||||
|
|
||||||
This change also means that we no longer use the `.full_clean()` method on model instances, but instead perform all validation explicitly on the serializer. This gives a cleaner separation, and ensures that there's no automatic validation behavior on `ModelSerializer` classes that can't also be easily replicated on regular `Serializer` classes.
|
This change also means that we no longer use the `.full_clean()` method on model instances, but instead perform all validation explicitly on the serializer. This gives a cleaner separation, and ensures that there's no automatic validation behavior on `ModelSerializer` classes that can't also be easily replicated on regular `Serializer` classes.
|
||||||
|
@ -189,7 +205,32 @@ REST framework 2.x attempted to automatically support writable nested serializat
|
||||||
* It's unclear what behavior the user should expect when related models are passed `None` data.
|
* It's unclear what behavior the user should expect when related models are passed `None` data.
|
||||||
* It's unclear how the user should expect to-many relationships to handle updates, creations and deletions of multiple records.
|
* It's unclear how the user should expect to-many relationships to handle updates, creations and deletions of multiple records.
|
||||||
|
|
||||||
Using the `depth` option on `ModelSerializer` will now create **read-only nested serializers** by default. To use writable nested serialization you'll want to declare a nested field on the serializer class, and write the `create()` and/or `update()` methods explicitly.
|
Using the `depth` option on `ModelSerializer` will now create **read-only nested serializers** by default.
|
||||||
|
|
||||||
|
If you try to use a writable nested serializer without writing a custom `create()` and/or `update()` method you'll see an assertion error when you attempt to save the serializer. For example:
|
||||||
|
|
||||||
|
>>> class ProfileSerializer(serializers.ModelSerializer):
|
||||||
|
>>> class Meta:
|
||||||
|
>>> model = Profile
|
||||||
|
>>> fields = ('address', 'phone')
|
||||||
|
>>>
|
||||||
|
>>> class UserSerializer(serializers.ModelSerializer):
|
||||||
|
>>> profile = ProfileSerializer()
|
||||||
|
>>> class Meta:
|
||||||
|
>>> model = User
|
||||||
|
>>> fields = ('username', 'email', 'profile')
|
||||||
|
>>>
|
||||||
|
>>> data = {
|
||||||
|
>>> 'username': 'lizzy',
|
||||||
|
>>> 'email': 'lizzy@example.com',
|
||||||
|
>>> 'profile': {'address': '123 Acacia Avenue', 'phone': '01273 100200'}
|
||||||
|
>>> }
|
||||||
|
>>>
|
||||||
|
>>> serializer = UserSerializer(data=data)
|
||||||
|
>>> serializer.save()
|
||||||
|
AssertionError: The `.create()` method does not suport nested writable fields by default. Write an explicit `.create()` method for serializer `UserSerializer`, or set `read_only=True` on nested serializer fields.
|
||||||
|
|
||||||
|
To use writable nested serialization you'll want to declare a nested field on the serializer class, and write the `create()` and/or `update()` methods explicitly.
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
profile = ProfileSerializer()
|
profile = ProfileSerializer()
|
||||||
|
|
|
@ -99,10 +99,10 @@ class BaseSerializer(Field):
|
||||||
|
|
||||||
def is_valid(self, raise_exception=False):
|
def is_valid(self, raise_exception=False):
|
||||||
assert not hasattr(self, 'restore_object'), (
|
assert not hasattr(self, 'restore_object'), (
|
||||||
'Serializer %s has old-style version 2 `.restore_object()` '
|
'Serializer `%s.%s` has old-style version 2 `.restore_object()` '
|
||||||
'that is no longer compatible with REST framework 3. '
|
'that is no longer compatible with REST framework 3. '
|
||||||
'Use the new-style `.create()` and `.update()` methods instead.' %
|
'Use the new-style `.create()` and `.update()` methods instead.' %
|
||||||
self.__class__.__name__
|
(self.__class__.__module__, self.__class__.__name__)
|
||||||
)
|
)
|
||||||
|
|
||||||
if not hasattr(self, '_validated_data'):
|
if not hasattr(self, '_validated_data'):
|
||||||
|
@ -341,9 +341,22 @@ class Serializer(BaseSerializer):
|
||||||
value = self.validate(value)
|
value = self.validate(value)
|
||||||
assert value is not None, '.validate() should return the validated data'
|
assert value is not None, '.validate() should return the validated data'
|
||||||
except ValidationError as exc:
|
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({
|
raise ValidationError({
|
||||||
api_settings.NON_FIELD_ERRORS_KEY: exc.detail
|
api_settings.NON_FIELD_ERRORS_KEY: exc.detail
|
||||||
})
|
})
|
||||||
|
else:
|
||||||
|
raise ValidationError({
|
||||||
|
api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
|
||||||
|
})
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
|
@ -507,14 +520,17 @@ class ModelSerializer(Serializer):
|
||||||
self._kwargs['validators'] = validators
|
self._kwargs['validators'] = validators
|
||||||
|
|
||||||
def create(self, validated_attrs):
|
def create(self, validated_attrs):
|
||||||
|
# Check that the user isn't trying to handle a writable nested field.
|
||||||
|
# If we don't do this explicitly they'd likely get a confusing
|
||||||
|
# error at the point of calling `Model.objects.create()`.
|
||||||
assert not any(
|
assert not any(
|
||||||
isinstance(field, BaseSerializer) and not field.read_only
|
isinstance(field, BaseSerializer) and not field.read_only
|
||||||
for field in self.fields.values()
|
for field in self.fields.values()
|
||||||
), (
|
), (
|
||||||
'The `.create()` method does not suport nested writable fields '
|
'The `.create()` method does not suport nested writable fields '
|
||||||
'by default. Write an explicit `.create()` method for serializer '
|
'by default. Write an explicit `.create()` method for serializer '
|
||||||
'%s, or set `read_only=True` on nested serializer fields.' %
|
'`%s.%s`, or set `read_only=True` on nested serializer fields.' %
|
||||||
self.__class__.__name__
|
(self.__class__.__module__, self.__class__.__name__)
|
||||||
)
|
)
|
||||||
|
|
||||||
ModelClass = self.Meta.model
|
ModelClass = self.Meta.model
|
||||||
|
@ -544,8 +560,8 @@ class ModelSerializer(Serializer):
|
||||||
), (
|
), (
|
||||||
'The `.update()` method does not suport nested writable fields '
|
'The `.update()` method does not suport nested writable fields '
|
||||||
'by default. Write an explicit `.update()` method for serializer '
|
'by default. Write an explicit `.update()` method for serializer '
|
||||||
'%s, or set `read_only=True` on nested serializer fields.' %
|
'`%s.%s`, or set `read_only=True` on nested serializer fields.' %
|
||||||
self.__class__.__name__
|
(self.__class__.__module__, self.__class__.__name__)
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr, value in validated_attrs.items():
|
for attr, value in validated_attrs.items():
|
||||||
|
|
|
@ -43,6 +43,32 @@ class TestSerializer:
|
||||||
serializer.data
|
serializer.data
|
||||||
|
|
||||||
|
|
||||||
|
class TestValidateMethod:
|
||||||
|
def test_non_field_error_validate_method(self):
|
||||||
|
class ExampleSerializer(serializers.Serializer):
|
||||||
|
char = serializers.CharField()
|
||||||
|
integer = serializers.IntegerField()
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
raise serializers.ValidationError('Non field error')
|
||||||
|
|
||||||
|
serializer = ExampleSerializer(data={'char': 'abc', 'integer': 123})
|
||||||
|
assert not serializer.is_valid()
|
||||||
|
assert serializer.errors == {'non_field_errors': ['Non field error']}
|
||||||
|
|
||||||
|
def test_field_error_validate_method(self):
|
||||||
|
class ExampleSerializer(serializers.Serializer):
|
||||||
|
char = serializers.CharField()
|
||||||
|
integer = serializers.IntegerField()
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
raise serializers.ValidationError({'char': 'Field error'})
|
||||||
|
|
||||||
|
serializer = ExampleSerializer(data={'char': 'abc', 'integer': 123})
|
||||||
|
assert not serializer.is_valid()
|
||||||
|
assert serializer.errors == {'char': ['Field error']}
|
||||||
|
|
||||||
|
|
||||||
class TestBaseSerializer:
|
class TestBaseSerializer:
|
||||||
def setup(self):
|
def setup(self):
|
||||||
class ExampleSerializer(serializers.BaseSerializer):
|
class ExampleSerializer(serializers.BaseSerializer):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user