mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-10 19:56:59 +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.')
|
||||
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):
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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 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):
|
||||
profile = ProfileSerializer()
|
||||
|
|
|
@ -99,10 +99,10 @@ class BaseSerializer(Field):
|
|||
|
||||
def is_valid(self, raise_exception=False):
|
||||
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. '
|
||||
'Use the new-style `.create()` and `.update()` methods instead.' %
|
||||
self.__class__.__name__
|
||||
(self.__class__.__module__, self.__class__.__name__)
|
||||
)
|
||||
|
||||
if not hasattr(self, '_validated_data'):
|
||||
|
@ -341,9 +341,22 @@ class Serializer(BaseSerializer):
|
|||
value = self.validate(value)
|
||||
assert value is not None, '.validate() should return the validated data'
|
||||
except ValidationError as exc:
|
||||
raise ValidationError({
|
||||
api_settings.NON_FIELD_ERRORS_KEY: exc.detail
|
||||
})
|
||||
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]
|
||||
})
|
||||
|
||||
return value
|
||||
|
||||
def to_internal_value(self, data):
|
||||
|
@ -507,14 +520,17 @@ class ModelSerializer(Serializer):
|
|||
self._kwargs['validators'] = validators
|
||||
|
||||
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(
|
||||
isinstance(field, BaseSerializer) and not field.read_only
|
||||
for field in self.fields.values()
|
||||
), (
|
||||
'The `.create()` method does not suport nested writable fields '
|
||||
'by default. Write an explicit `.create()` method for serializer '
|
||||
'%s, or set `read_only=True` on nested serializer fields.' %
|
||||
self.__class__.__name__
|
||||
'`%s.%s`, or set `read_only=True` on nested serializer fields.' %
|
||||
(self.__class__.__module__, self.__class__.__name__)
|
||||
)
|
||||
|
||||
ModelClass = self.Meta.model
|
||||
|
@ -544,8 +560,8 @@ class ModelSerializer(Serializer):
|
|||
), (
|
||||
'The `.update()` method does not suport nested writable fields '
|
||||
'by default. Write an explicit `.update()` method for serializer '
|
||||
'%s, or set `read_only=True` on nested serializer fields.' %
|
||||
self.__class__.__name__
|
||||
'`%s.%s`, or set `read_only=True` on nested serializer fields.' %
|
||||
(self.__class__.__module__, self.__class__.__name__)
|
||||
)
|
||||
|
||||
for attr, value in validated_attrs.items():
|
||||
|
|
|
@ -43,6 +43,32 @@ class TestSerializer:
|
|||
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:
|
||||
def setup(self):
|
||||
class ExampleSerializer(serializers.BaseSerializer):
|
||||
|
|
Loading…
Reference in New Issue
Block a user