From 301ab5f88e013b60d4f608663e3f2456f4fd284c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFs=20BURG?= Date: Tue, 19 Nov 2013 14:22:09 +0100 Subject: [PATCH] Add hooks to customize error output --- rest_framework/serializers.py | 68 ++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 163abf4f0..d6a9ece9d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -231,6 +231,50 @@ class BaseSerializer(WritableField): return ret + ##### + # Methods to insert an error in the errors dict. + + def get_non_field_error_key(self): + """ + Return the key under which to store non field related errors. + """ + return "non_field_errors" + + def format_full_clean_errors(self, errors, instance, full_clean_error): + """ + Handling formatting errors generated by Django's full_clean method. + """ + return full_clean_error.message_dict + + def format_non_field_error(self, errors, data, non_field_error): + """ + Handles formatting non field related errors. + """ + current_list = errors.setdefault(self.get_non_field_error_key(), []) + if isinstance(non_field_error, list): + current_list.extend(non_field_error) + else: + current_list.append(non_field_error) + return errors + + def format_field_error(self, errors, data, field_name, field_error): + """ + Handles formatting field specific errors. + """ + current_list = errors.setdefault(field_name, []) + if isinstance(field_error, list): + current_list.extend(field_error) + else: + current_list.append(field_error) + return errors + + def format_many_error(self, errors, data, data_errors): + """ + Handles formatting bulk operations errors. + """ + errors.append(data_errors) + return errors + ##### # Methods to convert or revert from objects <--> primitive representations. @@ -248,7 +292,7 @@ class BaseSerializer(WritableField): reverted_data = {} if data is not None and not isinstance(data, dict): - self._errors['non_field_errors'] = ['Invalid data'] + self._errors = self.format_non_field_error(self._errors, None, 'Invalid data') return None for field_name, field in self.fields.items(): @@ -256,11 +300,11 @@ class BaseSerializer(WritableField): try: field.field_from_native(data, files, field_name, reverted_data) except ValidationError as err: - self._errors[field_name] = list(err.messages) + self._errors = self.format_field_error(self._errors, data, field_name, list(err.messages)) return reverted_data - def perform_validation(self, attrs): + def perform_validation(self, attrs, data): """ Run `validate_()` and `validate()` methods on the serializer """ @@ -276,7 +320,7 @@ class BaseSerializer(WritableField): if validate_method: attrs = validate_method(attrs, source) except ValidationError as err: - self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) + self._errors = self.format_field_error(self._errors, data, field_name, list(err.messages)) # If there are already errors, we don't run .validate() because # field-validation failed and thus `attrs` may not be complete. @@ -287,9 +331,9 @@ class BaseSerializer(WritableField): except ValidationError as err: if hasattr(err, 'message_dict'): for field_name, error_messages in err.message_dict.items(): - self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) + self._errors = self.format_field_error(self._errors, data, field_name, list(error_messages)) elif hasattr(err, 'messages'): - self._errors['non_field_errors'] = err.messages + self._errors = self.format_non_field_error(self._errors, data, err.messages) return attrs @@ -340,9 +384,9 @@ class BaseSerializer(WritableField): if data is not None or files is not None: attrs = self.restore_fields(data, files) if attrs is not None: - attrs = self.perform_validation(attrs) + attrs = self.perform_validation(attrs, data) else: - self._errors['non_field_errors'] = ['No input provided'] + self._errors = self.format_non_field_error(self._errors, None, 'No input provided') if not self._errors: return self.restore_object(attrs, instance=getattr(self, 'object', None)) @@ -491,18 +535,18 @@ class BaseSerializer(WritableField): self.object = identity_to_objects.pop(identity, None) if self.object is None and not self.allow_add_remove: ret.append(None) - errors.append({'non_field_errors': ['Cannot create a new item, only existing items may be updated.']}) + errors.append(self.format_non_field_error({}, item, 'Cannot create a new item, only existing items may be updated.')) continue ret.append(self.from_native(item, None)) - errors.append(self._errors) + self.format_many_error(errors, item, self._errors) if update and self.allow_add_remove: ret._deleted = identity_to_objects.values() self._errors = any(errors) and errors or [] else: - self._errors = {'non_field_errors': ['Expected a list of items.']} + self._errors = self.format_non_field_error({}, None, 'Expected a list of items.') else: ret = self.from_native(data, files) @@ -857,7 +901,7 @@ class ModelSerializer(Serializer): try: instance.full_clean(exclude=self.get_validation_exclusions()) except ValidationError as err: - self._errors = err.message_dict + self._errors = self.format_full_clean_errors(self._errors, instance, err) return None return instance