From ca5b99486d15e7392754178ab0948de2a60763a3 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 22 Nov 2012 22:36:37 +0100 Subject: Added _post_clean() behaviour by adding a .perform_model_validation() method. Fixed some tests that were failing due to extra strict validation. --- rest_framework/serializers.py | 57 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 53dcec16..6d5b4cb5 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -428,10 +428,6 @@ class ModelSerializer(Serializer): kwargs['choices'] = model_field.flatchoices return ChoiceField(**kwargs) - max_length = getattr(model_field, 'max_length', None) - if max_length: - kwargs['max_length'] = max_length - field_mapping = { models.FloatField: FloatField, models.IntegerField: IntegerField, @@ -455,6 +451,59 @@ class ModelSerializer(Serializer): except KeyError: return ModelField(model_field=model_field, **kwargs) + def from_native(self, data, files): + restored_object = super(ModelSerializer, self).from_native(data, files) + + if restored_object is None: + return + + self.perform_model_validation(restored_object) + return restored_object + + def perform_model_validation(self, restored_object): + + # if hasattr(restored_object, '__iter__'): # Iterables are not model instances + # return restored_object + #self._errors[field_name] = list(err.messages) + +# opts = self._meta + # Update the model instance with self.cleaned_data. +# instance = construct_instance(self, self.instance, opts.fields, opts.exclude) + +# exclude = self._get_validation_exclusions() + + # Foreign Keys being used to represent inline relationships + # are excluded from basic field value validation. This is for two + # reasons: firstly, the value may not be supplied (#12507; the + # case of providing new values to the admin); secondly the + # object being referred to may not yet fully exist (#12749). + # However, these fields *must* be included in uniqueness checks, + # so this can't be part of _get_validation_exclusions(). +# for f_name, field in self.fields.items(): +# if isinstance(field, InlineForeignKeyField): +# exclude.append(f_name) + + # Clean the model instance's fields. + try: + restored_object.clean_fields() # exclude=exclude) + except ValidationError as e: + for field_name, error_messages in e.message_dict.items(): + self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) + + # Call the model instance's clean method. + try: + restored_object.clean() + except ValidationError as e: + for field_name, error_messages in e.message_dict.items(): + self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) + + # Validate uniqueness if needed. + # exclude = self._get_validation_exclusions() +# try: +# restored_object.validate_unique() # exclude=exclude) +# except ValidationError as e: +# model_errors.append(e.message_dict) + def restore_object(self, attrs, instance=None): """ Restore the model instance. -- cgit v1.2.3 From bd8c742df2cc72896fa975196fdf56961e89cd94 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 22 Nov 2012 23:39:16 +0100 Subject: Cleanup. --- rest_framework/serializers.py | 40 ++-------------------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 6d5b4cb5..bb15faa8 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -461,49 +461,13 @@ class ModelSerializer(Serializer): return restored_object def perform_model_validation(self, restored_object): - - # if hasattr(restored_object, '__iter__'): # Iterables are not model instances - # return restored_object - #self._errors[field_name] = list(err.messages) - -# opts = self._meta - # Update the model instance with self.cleaned_data. -# instance = construct_instance(self, self.instance, opts.fields, opts.exclude) - -# exclude = self._get_validation_exclusions() - - # Foreign Keys being used to represent inline relationships - # are excluded from basic field value validation. This is for two - # reasons: firstly, the value may not be supplied (#12507; the - # case of providing new values to the admin); secondly the - # object being referred to may not yet fully exist (#12749). - # However, these fields *must* be included in uniqueness checks, - # so this can't be part of _get_validation_exclusions(). -# for f_name, field in self.fields.items(): -# if isinstance(field, InlineForeignKeyField): -# exclude.append(f_name) - - # Clean the model instance's fields. try: - restored_object.clean_fields() # exclude=exclude) + # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique() + restored_object.full_clean(exclude=list(self.opts.exclude)) except ValidationError as e: for field_name, error_messages in e.message_dict.items(): self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) - # Call the model instance's clean method. - try: - restored_object.clean() - except ValidationError as e: - for field_name, error_messages in e.message_dict.items(): - self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) - - # Validate uniqueness if needed. - # exclude = self._get_validation_exclusions() -# try: -# restored_object.validate_unique() # exclude=exclude) -# except ValidationError as e: -# model_errors.append(e.message_dict) - def restore_object(self, attrs, instance=None): """ Restore the model instance. -- cgit v1.2.3 From f104f7434052bedf6dd970806ff54b73489b339b Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Tue, 27 Nov 2012 23:21:12 +0100 Subject: Moved model validation from .perform_validation() to .validate() --- rest_framework/serializers.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index bb15faa8..5046c7b1 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -221,10 +221,17 @@ class BaseSerializer(Field): except ValidationError as err: self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) - try: - attrs = self.validate(attrs) - except ValidationError as err: - self._errors['non_field_errors'] = err.messages + # We don't run .validate() because field-validation failed and thus `attrs` may not be complete. + # which in turn can cause inconsistent validation errors. + if not self._errors: + try: + attrs = self.validate(attrs) + 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) + elif hasattr(err, 'messages'): + self._errors['non_field_errors'] = err.messages return attrs @@ -451,22 +458,15 @@ class ModelSerializer(Serializer): except KeyError: return ModelField(model_field=model_field, **kwargs) - def from_native(self, data, files): - restored_object = super(ModelSerializer, self).from_native(data, files) - - if restored_object is None: - return - + def validate(self, attrs): + copied_attrs = copy.deepcopy(attrs) + restored_object = self.restore_object(copied_attrs, instance=getattr(self, 'object', None)) self.perform_model_validation(restored_object) - return restored_object + return attrs def perform_model_validation(self, restored_object): - try: - # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique() - restored_object.full_clean(exclude=list(self.opts.exclude)) - except ValidationError as e: - for field_name, error_messages in e.message_dict.items(): - self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) + # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique() + restored_object.full_clean(exclude=list(self.opts.exclude)) def restore_object(self, attrs, instance=None): """ -- cgit v1.2.3 From 899f96ae9186e68009dba5d54246232d34457354 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Tue, 27 Nov 2012 23:49:27 +0100 Subject: Added a get_excluded_fieldnames() method. Model validation now excludes fields not listed in Meta fields (if set). --- rest_framework/serializers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5046c7b1..775a8a1e 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -119,6 +119,17 @@ class BaseSerializer(Field): """ return {} + def get_excluded_fieldnames(self): + """ + Returns the fieldnames that should not be validated. + """ + excluded_fields = list(self.opts.exclude) + for field in self.fields.keys() + self.default_fields.keys(): + if self.opts.fields: + if field not in self.opts.fields + self.opts.exclude: + excluded_fields.append(field) + return excluded_fields + def get_fields(self): """ Returns the complete set of fields for the object as a dict. @@ -466,7 +477,7 @@ class ModelSerializer(Serializer): def perform_model_validation(self, restored_object): # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique() - restored_object.full_clean(exclude=list(self.opts.exclude)) + restored_object.full_clean(exclude=list(self.get_excluded_fieldnames())) def restore_object(self, attrs, instance=None): """ -- cgit v1.2.3 From 919aff329ee1bd214831095e4d96af71795ed572 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Fri, 7 Dec 2012 00:08:27 +0100 Subject: Fix AttributeError caused by accessing a non-existing default_keys attribute. --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 984f3ac5..43bfda83 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -123,7 +123,7 @@ class BaseSerializer(Field): Returns the fieldnames that should not be validated. """ excluded_fields = list(self.opts.exclude) - for field in self.fields.keys() + self.default_fields.keys(): + for field in self.fields.keys() + self.get_default_fields().keys(): if self.opts.fields: if field not in self.opts.fields + self.opts.exclude: excluded_fields.append(field) -- cgit v1.2.3