diff options
| author | Xavier Ordoquy | 2013-01-02 16:09:21 +0100 | 
|---|---|---|
| committer | Xavier Ordoquy | 2013-01-02 16:09:21 +0100 | 
| commit | 737349d2389197d23886b72c1cb44f53c501ac9a (patch) | |
| tree | 0c542b6c73deee0280705c8253334126c5f5d254 /rest_framework/serializers.py | |
| parent | 5fad46d7e213afed503b1533515cab96875a5936 (diff) | |
| parent | d379997aba5b1e41309bbed8740ed704c0feb58b (diff) | |
| download | django-rest-framework-737349d2389197d23886b72c1cb44f53c501ac9a.tar.bz2 | |
Merge remote-tracking branch 'reference/py3k' into p3k
Diffstat (limited to 'rest_framework/serializers.py')
| -rw-r--r-- | rest_framework/serializers.py | 123 | 
1 files changed, 92 insertions, 31 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index cbb6b4df..91fbe176 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -16,7 +16,7 @@ from rest_framework.compat import get_concrete_model  # This helps keep the seperation between model fields, form fields, and  # serializer fields more explicit. - +from rest_framework.relations import *  from rest_framework.fields import * @@ -24,7 +24,16 @@ class DictWithMetadata(dict):      """      A dict-like object, that can have additional properties attached.      """ -    pass +    def __getstate__(self): +        """ +        Used by pickle (e.g., caching). +        Overriden to remove metadata from the dict, since it shouldn't be pickled +        and may in some instances be unpickleable. +        """ +        # return an instance of the first dict in MRO that isn't a DictWithMetadata +        for base in self.__class__.__mro__: +            if not isinstance(base, DictWithMetadata) and isinstance(base, dict): +                return base(self)  class SortedDictWithMetadata(SortedDict, DictWithMetadata): @@ -93,7 +102,8 @@ class BaseSerializer(Field):      _options_class = SerializerOptions      _dict_class = SortedDictWithMetadata  # Set to unsorted dict for backwards compatibility with unsorted implementations. -    def __init__(self, instance=None, data=None, files=None, context=None, partial=False, **kwargs): +    def __init__(self, instance=None, data=None, files=None, +                 context=None, partial=False, **kwargs):          super(BaseSerializer, self).__init__(**kwargs)          self.opts = self._options_class(self.Meta)          self.parent = None @@ -133,8 +143,6 @@ class BaseSerializer(Field):          base_fields = copy.deepcopy(self.base_fields)          for key, field in base_fields.items():              ret[key] = field -            # Set up the field -            field.initialize(parent=self, field_name=key)          # Add in the default fields          default_fields = self.get_default_fields() @@ -154,6 +162,9 @@ class BaseSerializer(Field):              for key in self.opts.exclude:                  ret.pop(key, None) +        for key, field in ret.items(): +            field.initialize(parent=self, field_name=key) +          return ret      ##### @@ -186,6 +197,7 @@ class BaseSerializer(Field):          ret.fields = {}          for field_name, field in self.fields.items(): +            field.initialize(parent=self, field_name=field_name)              key = self.get_field_key(field_name)              value = field.field_to_native(obj, field_name)              ret[key] = value @@ -199,6 +211,7 @@ class BaseSerializer(Field):          """          reverted_data = {}          for field_name, field in self.fields.items(): +            field.initialize(parent=self, field_name=field_name)              try:                  field.field_from_native(data, files, field_name, reverted_data)              except ValidationError as err: @@ -219,10 +232,18 @@ 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 +        # If there are already errors, 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 @@ -274,15 +295,23 @@ class BaseSerializer(Field):          Override default so that we can apply ModelSerializer as a nested          field to relationships.          """ -        obj = getattr(obj, self.source or field_name) - -        if is_simple_callable(obj): -            obj = obj() +        if self.source: +            for component in self.source.split('.'): +                obj = getattr(obj, component) +                if is_simple_callable(obj): +                    obj = obj() +        else: +            obj = getattr(obj, field_name) +            if is_simple_callable(obj): +                obj = value()          # If the object has an "all" method, assume it's a relationship          if is_simple_callable(getattr(obj, 'all', None)):              return [self.to_native(item) for item in obj.all()] +        if obj is None: +            return None +          return self.to_native(obj)      @property @@ -366,7 +395,6 @@ class ModelSerializer(Serializer):                  field = self.get_field(model_field)              if field: -                field.initialize(parent=self, field_name=model_field.name)                  ret[model_field.name] = field          for field_name in self.opts.read_only_fields: @@ -398,10 +426,14 @@ class ModelSerializer(Serializer):          """          # TODO: filter queryset using:          # .using(db).complex_filter(self.rel.limit_choices_to) -        queryset = model_field.rel.to._default_manager +        kwargs = { +            'null': model_field.null, +            'queryset': model_field.rel.to._default_manager +        } +          if to_many: -            return ManyPrimaryKeyRelatedField(queryset=queryset) -        return PrimaryKeyRelatedField(queryset=queryset) +            return ManyPrimaryKeyRelatedField(**kwargs) +        return PrimaryKeyRelatedField(**kwargs)      def get_field(self, model_field):          """ @@ -411,7 +443,7 @@ class ModelSerializer(Serializer):          kwargs['blank'] = model_field.blank -        if model_field.null: +        if model_field.null or model_field.blank:              kwargs['required'] = False          if model_field.has_default(): @@ -426,10 +458,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, @@ -453,28 +481,56 @@ class ModelSerializer(Serializer):          except KeyError:              return ModelField(model_field=model_field, **kwargs) +    def get_validation_exclusions(self): +        """ +        Return a list of field names to exclude from model validation. +        """ +        cls = self.opts.model +        opts = get_concrete_model(cls)._meta +        exclusions = [field.name for field in opts.fields + opts.many_to_many] +        for field_name, field in self.fields.items(): +            if field_name in exclusions and not field.read_only: +                exclusions.remove(field_name) +        return exclusions +      def restore_object(self, attrs, instance=None):          """          Restore the model instance.          """          self.m2m_data = {} +        self.related_data = {} -        if instance is not None: -            for key, val in attrs.items(): -                setattr(instance, key, val) -            return instance +        # Reverse fk relations +        for (obj, model) in self.opts.model._meta.get_all_related_objects_with_model(): +            field_name = obj.field.related_query_name() +            if field_name in attrs: +                self.related_data[field_name] = attrs.pop(field_name) -        # Reverse relations +        # Reverse m2m relations          for (obj, model) in self.opts.model._meta.get_all_related_m2m_objects_with_model():              field_name = obj.field.related_query_name()              if field_name in attrs:                  self.m2m_data[field_name] = attrs.pop(field_name) -        # Forward relations +        # Forward m2m relations          for field in self.opts.model._meta.many_to_many:              if field.name in attrs:                  self.m2m_data[field.name] = attrs.pop(field.name) -        return self.opts.model(**attrs) + +        if instance is not None: +            for key, val in attrs.items(): +                setattr(instance, key, val) + +        else: +            instance = self.opts.model(**attrs) + +        try: +            instance.full_clean(exclude=self.get_validation_exclusions()) +        except ValidationError, err: +            self._errors = err.message_dict +            return None + +        return instance      def save(self, save_m2m=True):          """ @@ -487,6 +543,11 @@ class ModelSerializer(Serializer):                  setattr(self.object, accessor_name, object_list)              self.m2m_data = {} +        if getattr(self, 'related_data', None): +            for accessor_name, object_list in self.related_data.items(): +                setattr(self.object, accessor_name, object_list) +            self.related_data = {} +          return self.object @@ -533,9 +594,9 @@ class HyperlinkedModelSerializer(ModelSerializer):          # TODO: filter queryset using:          # .using(db).complex_filter(self.rel.limit_choices_to)          rel = model_field.rel.to -        queryset = rel._default_manager          kwargs = { -            'queryset': queryset, +            'null': model_field.null, +            'queryset': rel._default_manager,              'view_name': self._get_default_view_name(rel)          }          if to_many:  | 
