diff options
Diffstat (limited to 'rest_framework/serializers.py')
| -rw-r--r-- | rest_framework/serializers.py | 348 | 
1 files changed, 348 insertions, 0 deletions
| diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py new file mode 100644 index 00000000..afc9f511 --- /dev/null +++ b/rest_framework/serializers.py @@ -0,0 +1,348 @@ +from decimal import Decimal +from django.core.serializers.base import DeserializedObject +from django.utils.datastructures import SortedDict +import copy +import datetime +import types +from rest_framework.fields import * + + +class DictWithMetadata(dict): +    """ +    A dict-like object, that can have additional properties attached. +    """ +    pass + + +class SortedDictWithMetadata(SortedDict, DictWithMetadata): +    """ +    A sorted dict-like object, that can have additional properties attached. +    """ +    pass + + +class RecursionOccured(BaseException): +    pass + + +def _is_protected_type(obj): +    """ +    True if the object is a native datatype that does not need to +    be serialized further. +    """ +    return isinstance(obj, ( +        types.NoneType, +       int, long, +       datetime.datetime, datetime.date, datetime.time, +       float, Decimal, +       basestring) +    ) + + +def _get_declared_fields(bases, attrs): +    """ +    Create a list of serializer field instances from the passed in 'attrs', +    plus any fields on the base classes (in 'bases'). + +    Note that all fields from the base classes are used. +    """ +    fields = [(field_name, attrs.pop(field_name)) +              for field_name, obj in attrs.items() +              if isinstance(obj, Field)] +    fields.sort(key=lambda x: x[1].creation_counter) + +    # If this class is subclassing another Serializer, add that Serializer's +    # fields.  Note that we loop over the bases in *reverse*. This is necessary +    # in order to the correct order of fields. +    for base in bases[::-1]: +        if hasattr(base, 'base_fields'): +            fields = base.base_fields.items() + fields + +    return SortedDict(fields) + + +class SerializerMetaclass(type): +    def __new__(cls, name, bases, attrs): +        attrs['base_fields'] = _get_declared_fields(bases, attrs) +        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) + + +class SerializerOptions(object): +    """ +    Meta class options for ModelSerializer +    """ +    def __init__(self, meta): +        self.nested = getattr(meta, 'nested', False) +        self.fields = getattr(meta, 'fields', ()) +        self.exclude = getattr(meta, 'exclude', ()) + + +class BaseSerializer(Field): +    class Meta(object): +        pass + +    _options_class = SerializerOptions +    _dict_class = SortedDictWithMetadata  # Set to unsorted dict for backwards compatability with unsorted implementations. + +    def __init__(self, data=None, instance=None, context=None, **kwargs): +        super(BaseSerializer, self).__init__(**kwargs) +        self.fields = copy.deepcopy(self.base_fields) +        self.opts = self._options_class(self.Meta) +        self.parent = None +        self.root = None + +        self.stack = [] +        self.context = context or {} + +        self.init_data = data +        self.instance = instance + +        self._data = None +        self._errors = None + +    ##### +    # Methods to determine which fields to use when (de)serializing objects. + +    def default_fields(self, serialize, obj=None, data=None, nested=False): +        """ +        Return the complete set of default fields for the object, as a dict. +        """ +        return {} + +    def get_fields(self, serialize, obj=None, data=None, nested=False): +        """ +        Returns the complete set of fields for the object as a dict. + +        This will be the set of any explicitly declared fields, +        plus the set of fields returned by get_default_fields(). +        """ +        ret = SortedDict() + +        # Get the explicitly declared fields +        for key, field in self.fields.items(): +            ret[key] = field +            # Determine if the declared field corrosponds to a model field. +            try: +                if key == 'pk': +                    model_field = obj._meta.pk +                else: +                    model_field = obj._meta.get_field_by_name(key)[0] +            except: +                model_field = None +            # Set up the field +            field.initialize(parent=self, model_field=model_field) + +        # Add in the default fields +        fields = self.default_fields(serialize, obj, data, nested) +        for key, val in fields.items(): +            if key not in ret: +                ret[key] = val + +        # If 'fields' is specified, use those fields, in that order. +        if self.opts.fields: +            new = SortedDict() +            for key in self.opts.fields: +                new[key] = ret[key] +            ret = new + +        # Remove anything in 'exclude' +        if self.opts.exclude: +            for key in self.opts.exclude: +                ret.pop(key, None) + +        return ret + +    ##### +    # Field methods - used when the serializer class is itself used as a field. + +    def initialize(self, parent, model_field=None): +        """ +        Same behaviour as usual Field, except that we need to keep track +        of state so that we can deal with handling maximum depth and recursion. +        """ +        super(BaseSerializer, self).initialize(parent, model_field) +        self.stack = parent.stack[:] +        if parent.opts.nested and not isinstance(parent.opts.nested, bool): +            self.opts.nested = parent.opts.nested - 1 +        else: +            self.opts.nested = parent.opts.nested + +    ##### +    # Methods to convert or revert from objects <--> primative representations. + +    def get_field_key(self, field_name): +        """ +        Return the key that should be used for a given field. +        """ +        return field_name + +    def convert_object(self, obj): +        """ +        Core of serialization. +        Convert an object into a dictionary of serialized field values. +        """ +        if obj in self.stack and not self.source == '*': +            raise RecursionOccured() +        self.stack.append(obj) + +        ret = self._dict_class() +        ret.fields = {} + +        fields = self.get_fields(serialize=True, obj=obj, nested=self.opts.nested) +        for field_name, field in fields.items(): +            key = self.get_field_key(field_name) +            try: +                value = field.field_to_native(obj, field_name) +            except RecursionOccured: +                field = self.get_fields(serialize=True, obj=obj, nested=False)[field_name] +                value = field.field_to_native(obj, field_name) +            ret[key] = value +            ret.fields[key] = field +        return ret + +    def restore_fields(self, data): +        """ +        Core of deserialization, together with `restore_object`. +        Converts a dictionary of data into a dictionary of deserialized fields. +        """ +        fields = self.get_fields(serialize=False, data=data, nested=self.opts.nested) +        reverted_data = {} +        for field_name, field in fields.items(): +            try: +                field.field_from_native(data, field_name, reverted_data) +            except ValidationError as err: +                self._errors[field_name] = list(err.messages) + +        return reverted_data + +    def restore_object(self, attrs, instance=None): +        """ +        Deserialize a dictionary of attributes into an object instance. +        You should override this method to control how deserialized objects +        are instantiated. +        """ +        if instance is not None: +            instance.update(attrs) +            return instance +        return attrs + +    def to_native(self, obj): +        """ +        Serialize objects -> primatives. +        """ +        if isinstance(obj, dict): +            return dict([(key, self.to_native(val)) +                         for (key, val) in obj.items()]) +        elif hasattr(obj, '__iter__'): +            return (self.to_native(item) for item in obj) +        return self.convert_object(obj) + +    def from_native(self, data): +        """ +        Deserialize primatives -> objects. +        """ +        if hasattr(data, '__iter__') and not isinstance(data, dict): +            # TODO: error data when deserializing lists +            return (self.from_native(item) for item in data) +        self._errors = {} +        attrs = self.restore_fields(data) +        if not self._errors: +            return self.restore_object(attrs, instance=getattr(self, 'instance', None)) + +    @property +    def errors(self): +        """ +        Run deserialization and return error data, +        setting self.object if no errors occured. +        """ +        if self._errors is None: +            obj = self.from_native(self.init_data) +            if not self._errors: +                self.object = obj +        return self._errors + +    def is_valid(self): +        return not self.errors + +    @property +    def data(self): +        if self._data is None: +            self._data = self.to_native(self.instance) +        return self._data + + +class Serializer(BaseSerializer): +    __metaclass__ = SerializerMetaclass + + +class ModelSerializerOptions(SerializerOptions): +    """ +    Meta class options for ModelSerializer +    """ +    def __init__(self, meta): +        super(ModelSerializerOptions, self).__init__(meta) +        self.model = getattr(meta, 'model', None) + + +class ModelSerializer(RelatedField, Serializer): +    """ +    A serializer that deals with model instances and querysets. +    """ +    _options_class = ModelSerializerOptions + +    def default_fields(self, serialize, obj=None, data=None, nested=False): +        """ +        Return all the fields that should be serialized for the model. +        """ +        if serialize: +            cls = obj.__class__ +        else: +            cls = self.opts.model + +        opts = cls._meta.concrete_model._meta +        pk_field = opts.pk +        while pk_field.rel: +            pk_field = pk_field.rel.to._meta.pk +        fields = [pk_field] +        fields += [field for field in opts.fields if field.serialize] +        fields += [field for field in opts.many_to_many if field.serialize] + +        ret = SortedDict() +        for model_field in fields: +            if model_field.rel and nested: +                field = self.get_nested_field(model_field) +            elif model_field.rel: +                field = self.get_related_field(model_field) +            else: +                field = self.get_field(model_field) +            field.initialize(parent=self, model_field=model_field) +            ret[model_field.name] = field +        return ret + +    def get_nested_field(self, model_field): +        """ +        Creates a default instance of a nested relational field. +        """ +        return ModelSerializer() + +    def get_related_field(self, model_field): +        """ +        Creates a default instance of a flat relational field. +        """ +        return PrimaryKeyRelatedField() + +    def get_field(self, model_field): +        """ +        Creates a default instance of a basic field. +        """ +        return Field() + +    def restore_object(self, attrs, instance=None): +        """ +        Restore the model instance. +        """ +        m2m_data = {} +        for field in self.opts.model._meta.many_to_many: +            if field.name in attrs: +                m2m_data[field.name] = attrs.pop(field.name) +        return DeserializedObject(self.opts.model(**attrs), m2m_data) | 
