diff options
| author | Tom Christie | 2012-12-07 12:34:56 -0800 | 
|---|---|---|
| committer | Tom Christie | 2012-12-07 12:34:56 -0800 | 
| commit | a5178e9a363d00f3eef8d86da2d0ec687518f288 (patch) | |
| tree | 1a71714f9d14f7c6c8878ffba7e85f416d2f21b1 /rest_framework | |
| parent | a463ddbb37edb725626b5b819266f3962bafc160 (diff) | |
| parent | 919aff329ee1bd214831095e4d96af71795ed572 (diff) | |
| download | django-rest-framework-a5178e9a363d00f3eef8d86da2d0ec687518f288.tar.bz2 | |
Merge pull request #451 from markotibold/#431
Call model's .full_clean() method, eg. to validate uniqueness
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/serializers.py | 40 | ||||
| -rw-r--r-- | rest_framework/tests/models.py | 4 | ||||
| -rw-r--r-- | rest_framework/tests/serializer.py | 23 | 
3 files changed, 54 insertions, 13 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 67eafdf0..5edd46f5 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -127,6 +127,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.get_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. @@ -226,10 +237,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 @@ -441,10 +459,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, @@ -468,6 +482,16 @@ class ModelSerializer(Serializer):          except KeyError:              return ModelField(model_field=model_field, **kwargs) +    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 attrs + +    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.get_excluded_fieldnames())) +      def restore_object(self, attrs, instance=None):          """          Restore the model instance. diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index 76435df8..428bf130 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -61,7 +61,7 @@ class BasicModel(RESTFrameworkModel):  class SlugBasedModel(RESTFrameworkModel):      text = models.CharField(max_length=100) -    slug = models.SlugField(max_length=32) +    slug = models.SlugField(max_length=32, blank=True)  class DefaultValueModel(RESTFrameworkModel): @@ -160,7 +160,7 @@ class Photo(RESTFrameworkModel):  # Model for issue #324  class BlankFieldModel(RESTFrameworkModel): -    title = models.CharField(max_length=100, blank=True) +    title = models.CharField(max_length=100, blank=True, null=True)  # Model for issue #380 diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 6aa211f4..455fa270 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -1,7 +1,7 @@  import datetime, pickle  from django.test import TestCase  from rest_framework import serializers -from rest_framework.tests.models import (ActionItem, Anchor, BasicModel, +from rest_framework.tests.models import (Album, ActionItem, Anchor, BasicModel,      BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel,      ManyToManyModel, Person, ReadOnlyManyToManyModel) @@ -48,7 +48,7 @@ class BookSerializer(serializers.ModelSerializer):  class ActionItemSerializer(serializers.ModelSerializer): -     +      class Meta:          model = ActionItem @@ -62,6 +62,12 @@ class PersonSerializer(serializers.ModelSerializer):          read_only_fields = ('age',) +class AlbumsSerializer(serializers.ModelSerializer): + +    class Meta: +        model = Album + +  class BasicTests(TestCase):      def setUp(self):          self.comment = Comment( @@ -169,7 +175,7 @@ class ValidationTests(TestCase):              'content': 'x' * 1001,              'created': datetime.datetime(2012, 1, 1)          } -        self.actionitem = ActionItem('Some to do item', +        self.actionitem = ActionItem(title='Some to do item',          )      def test_create(self): @@ -276,6 +282,17 @@ class ValidationTests(TestCase):          self.assertEquals(serializer.is_valid(), False)          self.assertEquals(serializer.errors, {'info': [u'Ensure this value has at most 12 characters (it has 13).']}) +    def test_validate_unique(self): +        """ +        Just check if serializers.ModelSerializer.perform_model_validation() handles unique checks via .full_clean() +        """ +        serializer = AlbumsSerializer(data={'title': 'a'}) +        serializer.is_valid() +        serializer.save() +        second_serializer = AlbumsSerializer(data={'title': 'a'}) +        self.assertFalse(second_serializer.is_valid()) +        self.assertEqual(second_serializer.errors,  {'title': [u'Album with this Title already exists.']}) +  class RegexValidationTest(TestCase):      def test_create_failed(self):  | 
