aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/serializers.py
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework/serializers.py')
-rw-r--r--rest_framework/serializers.py110
1 files changed, 72 insertions, 38 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 4fb802a7..b635d20d 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
import copy
import datetime
import types
@@ -7,6 +8,7 @@ from django.db import models
from django.forms import widgets
from django.utils.datastructures import SortedDict
from rest_framework.compat import get_concrete_model
+from rest_framework.compat import six
# Note: We do the following so that users of the framework can use this style:
#
@@ -64,7 +66,7 @@ def _get_declared_fields(bases, attrs):
Note that all fields from the base classes are used.
"""
fields = [(field_name, attrs.pop(field_name))
- for field_name, obj in attrs.items()
+ for field_name, obj in list(six.iteritems(attrs))
if isinstance(obj, Field)]
fields.sort(key=lambda x: x[1].creation_counter)
@@ -73,7 +75,7 @@ def _get_declared_fields(bases, attrs):
# in order to maintain the correct order of fields.
for base in bases[::-1]:
if hasattr(base, 'base_fields'):
- fields = base.base_fields.items() + fields
+ fields = list(base.base_fields.items()) + fields
return SortedDict(fields)
@@ -95,19 +97,24 @@ class SerializerOptions(object):
class BaseSerializer(Field):
+ """
+ This is the Serializer implementation.
+ We need to implement it as `BaseSerializer` due to metaclass magicks.
+ """
class Meta(object):
pass
_options_class = SerializerOptions
- _dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatibility with unsorted implementations.
+ _dict_class = SortedDictWithMetadata
def __init__(self, instance=None, data=None, files=None,
- context=None, partial=False, **kwargs):
- super(BaseSerializer, self).__init__(**kwargs)
+ context=None, partial=False, many=None, source=None):
+ super(BaseSerializer, self).__init__(source=source)
self.opts = self._options_class(self.Meta)
self.parent = None
self.root = None
self.partial = partial
+ self.many = many
self.context = context or {}
@@ -187,22 +194,6 @@ class BaseSerializer(Field):
"""
return field_name
- def convert_object(self, obj):
- """
- Core of serialization.
- Convert an object into a dictionary of serialized field values.
- """
- ret = self._dict_class()
- 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
- ret.fields[key] = field
- return ret
-
def restore_fields(self, data, files):
"""
Core of deserialization, together with `restore_object`.
@@ -211,7 +202,7 @@ class BaseSerializer(Field):
reverted_data = {}
if data is not None and not isinstance(data, dict):
- self._errors['non_field_errors'] = [u'Invalid data']
+ self._errors['non_field_errors'] = ['Invalid data']
return None
for field_name, field in self.fields.items():
@@ -274,19 +265,22 @@ class BaseSerializer(Field):
"""
Serialize objects -> primitives.
"""
- # Note: At the moment we have an ugly hack to determine if we should
- # walk over iterables. At some point, serializers will require an
- # explicit `many=True` in order to iterate over a set, and this hack
- # will disappear.
- if hasattr(obj, '__iter__') and not isinstance(obj, Page):
- return [self.convert_object(item) for item in obj]
- return self.convert_object(obj)
+ ret = self._dict_class()
+ 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
+ ret.fields[key] = field
+ return ret
def from_native(self, data, files):
"""
Deserialize primitives -> objects.
"""
- if hasattr(data, '__iter__') and not isinstance(data, dict):
+ if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)):
# TODO: error data when deserializing lists
return [self.from_native(item, None) for item in data]
@@ -328,6 +322,13 @@ class BaseSerializer(Field):
if obj is None:
return None
+ if self.many is not None:
+ many = self.many
+ else:
+ many = hasattr(obj, '__iter__') and not isinstance(obj, Page)
+
+ if many:
+ return [self.to_native(item) for item in obj]
return self.to_native(obj)
@property
@@ -337,9 +338,20 @@ class BaseSerializer(Field):
setting self.object if no errors occurred.
"""
if self._errors is None:
- obj = self.from_native(self.init_data, self.init_files)
+ data, files = self.init_data, self.init_files
+
+ if self.many is not None:
+ many = self.many
+ else:
+ many = hasattr(data, '__iter__') and not isinstance(data, dict)
+
+ # TODO: error data when deserializing lists
+ if many:
+ ret = [self.from_native(item, None) for item in data]
+ ret = self.from_native(data, files)
+
if not self._errors:
- self.object = obj
+ self.object = ret
return self._errors
def is_valid(self):
@@ -347,8 +359,22 @@ class BaseSerializer(Field):
@property
def data(self):
+ """
+ Returns the serialized data on the serializer.
+ """
if self._data is None:
- self._data = self.to_native(self.object)
+ obj = self.object
+
+ if self.many is not None:
+ many = self.many
+ else:
+ many = hasattr(obj, '__iter__') and not isinstance(obj, Page)
+
+ if many:
+ self._data = [self.to_native(item) for item in obj]
+ else:
+ self._data = self.to_native(obj)
+
return self._data
def save(self):
@@ -359,8 +385,8 @@ class BaseSerializer(Field):
return self.object
-class Serializer(BaseSerializer):
- __metaclass__ = SerializerMetaclass
+class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)):
+ pass
class ModelSerializerOptions(SerializerOptions):
@@ -443,7 +469,7 @@ class ModelSerializer(Serializer):
# TODO: filter queryset using:
# .using(db).complex_filter(self.rel.limit_choices_to)
kwargs = {
- 'null': model_field.null or model_field.blank,
+ 'required': not(model_field.null or model_field.blank),
'queryset': model_field.rel.to._default_manager
}
@@ -524,7 +550,7 @@ class ModelSerializer(Serializer):
"""
try:
instance.full_clean(exclude=self.get_validation_exclusions())
- except ValidationError, err:
+ except ValidationError as err:
self._errors = err.message_dict
return None
return instance
@@ -560,6 +586,12 @@ class ModelSerializer(Serializer):
else:
instance = self.opts.model(**attrs)
+ try:
+ instance.full_clean(exclude=self.get_validation_exclusions())
+ except ValidationError as err:
+ self._errors = err.message_dict
+ return None
+
return instance
def from_native(self, data, files):
@@ -600,6 +632,8 @@ class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
class HyperlinkedModelSerializer(ModelSerializer):
"""
+ A subclass of ModelSerializer that uses hyperlinked relationships,
+ instead of primary key relationships.
"""
_options_class = HyperlinkedModelSerializerOptions
_default_view_name = '%(model_name)s-detail'
@@ -633,7 +667,7 @@ class HyperlinkedModelSerializer(ModelSerializer):
# .using(db).complex_filter(self.rel.limit_choices_to)
rel = model_field.rel.to
kwargs = {
- 'null': model_field.null,
+ 'required': not(model_field.null or model_field.blank),
'queryset': rel._default_manager,
'view_name': self._get_default_view_name(rel)
}