Skip to content

Commit 62cca1e

Browse files
committed
Merge pull request #2232 from tomchristie/validate-in-list-serializer
Added ListSerializer.validate().
2 parents ef89c15 + eee02a4 commit 62cca1e

File tree

3 files changed

+108
-54
lines changed

3 files changed

+108
-54
lines changed

rest_framework/fields.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -294,31 +294,47 @@ def get_default(self):
294294
return self.default()
295295
return self.default
296296

297-
def run_validation(self, data=empty):
297+
def validate_empty_values(self, data):
298298
"""
299-
Validate a simple representation and return the internal value.
300-
301-
The provided data may be `empty` if no representation was included
302-
in the input.
303-
304-
May raise `SkipField` if the field should not be included in the
305-
validated data.
299+
Validate empty values, and either:
300+
301+
* Raise `ValidationError`, indicating invalid data.
302+
* Raise `SkipField`, indicating that the field should be ignored.
303+
* Return (True, data), indicating an empty value that should be
304+
returned without any furhter validation being applied.
305+
* Return (False, data), indicating a non-empty value, that should
306+
have validation applied as normal.
306307
"""
307308
if self.read_only:
308-
return self.get_default()
309+
return (True, self.get_default())
309310

310311
if data is empty:
311312
if getattr(self.root, 'partial', False):
312313
raise SkipField()
313314
if self.required:
314315
self.fail('required')
315-
return self.get_default()
316+
return (True, self.get_default())
316317

317318
if data is None:
318319
if not self.allow_null:
319320
self.fail('null')
320-
return None
321+
return (True, None)
322+
323+
return (False, data)
321324

325+
def run_validation(self, data=empty):
326+
"""
327+
Validate a simple representation and return the internal value.
328+
329+
The provided data may be `empty` if no representation was included
330+
in the input.
331+
332+
May raise `SkipField` if the field should not be included in the
333+
validated data.
334+
"""
335+
(is_empty_value, data) = self.validate_empty_values(data)
336+
if is_empty_value:
337+
return data
322338
value = self.to_internal_value(data)
323339
self.run_validators(value)
324340
return value

rest_framework/serializers.py

Lines changed: 65 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,35 @@ def __new__(cls, name, bases, attrs):
229229
return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
230230

231231

232+
def get_validation_error_detail(exc):
233+
assert isinstance(exc, (ValidationError, DjangoValidationError))
234+
235+
if isinstance(exc, DjangoValidationError):
236+
# Normally you should raise `serializers.ValidationError`
237+
# inside your codebase, but we handle Django's validation
238+
# exception class as well for simpler compat.
239+
# Eg. Calling Model.clean() explicitly inside Serializer.validate()
240+
return {
241+
api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
242+
}
243+
elif isinstance(exc.detail, dict):
244+
# If errors may be a dict we use the standard {key: list of values}.
245+
# Here we ensure that all the values are *lists* of errors.
246+
return dict([
247+
(key, value if isinstance(value, list) else [value])
248+
for key, value in exc.detail.items()
249+
])
250+
elif isinstance(exc.detail, list):
251+
# Errors raised as a list are non-field errors.
252+
return {
253+
api_settings.NON_FIELD_ERRORS_KEY: exc.detail
254+
}
255+
# Errors raised as a string are non-field errors.
256+
return {
257+
api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
258+
}
259+
260+
232261
@six.add_metaclass(SerializerMetaclass)
233262
class Serializer(BaseSerializer):
234263
default_error_messages = {
@@ -293,62 +322,32 @@ def run_validation(self, data=empty):
293322
performed by validators and the `.validate()` method should
294323
be coerced into an error dictionary with a 'non_fields_error' key.
295324
"""
296-
if data is empty:
297-
if getattr(self.root, 'partial', False):
298-
raise SkipField()
299-
if self.required:
300-
self.fail('required')
301-
return self.get_default()
302-
303-
if data is None:
304-
if not self.allow_null:
305-
self.fail('null')
306-
return None
307-
308-
if not isinstance(data, dict):
309-
message = self.error_messages['invalid'].format(
310-
datatype=type(data).__name__
311-
)
312-
raise ValidationError({
313-
api_settings.NON_FIELD_ERRORS_KEY: [message]
314-
})
325+
(is_empty_value, data) = self.validate_empty_values(data)
326+
if is_empty_value:
327+
return data
315328

316329
value = self.to_internal_value(data)
317330
try:
318331
self.run_validators(value)
319332
value = self.validate(value)
320333
assert value is not None, '.validate() should return the validated data'
321-
except ValidationError as exc:
322-
if isinstance(exc.detail, dict):
323-
# .validate() errors may be a dict, in which case, use
324-
# standard {key: list of values} style.
325-
raise ValidationError(dict([
326-
(key, value if isinstance(value, list) else [value])
327-
for key, value in exc.detail.items()
328-
]))
329-
elif isinstance(exc.detail, list):
330-
raise ValidationError({
331-
api_settings.NON_FIELD_ERRORS_KEY: exc.detail
332-
})
333-
else:
334-
raise ValidationError({
335-
api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
336-
})
337-
except DjangoValidationError as exc:
338-
# Normally you should raise `serializers.ValidationError`
339-
# inside your codebase, but we handle Django's validation
340-
# exception class as well for simpler compat.
341-
# Eg. Calling Model.clean() explicitly inside Serializer.validate()
342-
raise ValidationError({
343-
api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
344-
})
334+
except (ValidationError, DjangoValidationError) as exc:
335+
raise ValidationError(detail=get_validation_error_detail(exc))
345336

346337
return value
347338

348339
def to_internal_value(self, data):
349340
"""
350341
Dict of native values <- Dict of primitive datatypes.
351342
"""
343+
if not isinstance(data, dict):
344+
message = self.error_messages['invalid'].format(
345+
datatype=type(data).__name__
346+
)
347+
raise ValidationError({
348+
api_settings.NON_FIELD_ERRORS_KEY: [message]
349+
})
350+
352351
ret = OrderedDict()
353352
errors = OrderedDict()
354353
fields = [
@@ -462,6 +461,26 @@ def get_value(self, dictionary):
462461
return html.parse_html_list(dictionary, prefix=self.field_name)
463462
return dictionary.get(self.field_name, empty)
464463

464+
def run_validation(self, data=empty):
465+
"""
466+
We override the default `run_validation`, because the validation
467+
performed by validators and the `.validate()` method should
468+
be coerced into an error dictionary with a 'non_fields_error' key.
469+
"""
470+
(is_empty_value, data) = self.validate_empty_values(data)
471+
if is_empty_value:
472+
return data
473+
474+
value = self.to_internal_value(data)
475+
try:
476+
self.run_validators(value)
477+
value = self.validate(value)
478+
assert value is not None, '.validate() should return the validated data'
479+
except (ValidationError, DjangoValidationError) as exc:
480+
raise ValidationError(detail=get_validation_error_detail(exc))
481+
482+
return value
483+
465484
def to_internal_value(self, data):
466485
"""
467486
List of dicts of native values <- List of dicts of primitive datatypes.
@@ -503,6 +522,9 @@ def to_representation(self, data):
503522
self.child.to_representation(item) for item in iterable
504523
]
505524

525+
def validate(self, attrs):
526+
return attrs
527+
506528
def update(self, instance, validated_data):
507529
raise NotImplementedError(
508530
"Serializers with many=True do not support multiple update by "

tests/test_serializer_lists.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,19 @@ def test_validate_html_input(self):
272272
serializer = self.Serializer(data=input_data)
273273
assert serializer.is_valid()
274274
assert serializer.validated_data == expected_output
275+
276+
277+
class TestListSerializerClass:
278+
"""Tests for a custom list_serializer_class."""
279+
def test_list_serializer_class_validate(self):
280+
class CustomListSerializer(serializers.ListSerializer):
281+
def validate(self, attrs):
282+
raise serializers.ValidationError('Non field error')
283+
284+
class TestSerializer(serializers.Serializer):
285+
class Meta:
286+
list_serializer_class = CustomListSerializer
287+
288+
serializer = TestSerializer(data=[], many=True)
289+
assert not serializer.is_valid()
290+
assert serializer.errors == {'non_field_errors': ['Non field error']}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy