aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/topics/3.0-announcement.md45
-rw-r--r--rest_framework/serializers.py34
-rw-r--r--tests/test_serializer.py26
3 files changed, 94 insertions, 11 deletions
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md
index 658b50d3..9aeb5df6 100644
--- a/docs/topics/3.0-announcement.md
+++ b/docs/topics/3.0-announcement.md
@@ -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()
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index d29dc684..59f38a73 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -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():
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
index 4df1b736..77d5c319 100644
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -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):