diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index d5f060e7a..4317b83c0 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -29,6 +29,7 @@ You can determine your currently installed version using `pip freeze`: ### Master * Added a `post_save()` hook to the generic views. +* Allow serializers to handle dicts as well as objects. * Bugfix: Fix styling on browsable API login. * Bugfix: Fix issue with deserializing empty to-many relations. * Bugfix: Ensure model field validation is still applied for ModelSerializer subclasses with an custom `.restore_object()` method. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 2c3e59b58..aa6fa3abc 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -30,6 +30,21 @@ def is_simple_callable(obj): ) +def get_component(obj, attr_name): + """ + Given an object, and an attribute name, + return that attribute on the object. + """ + if isinstance(obj, dict): + val = obj[attr_name] + else: + val = getattr(obj, attr_name) + + if is_simple_callable(val): + return val() + return val + + class Field(object): read_only = True creation_counter = 0 @@ -82,11 +97,9 @@ class Field(object): if self.source: value = obj for component in self.source.split('.'): - value = getattr(value, component) - if is_simple_callable(value): - value = value() + value = get_component(value, component) else: - value = getattr(obj, field_name) + value = get_component(obj, field_name) return self.to_native(value) def to_native(self, value): diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 7daeac417..a6dbf5d77 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -325,7 +325,7 @@ class BaseSerializer(Field): if self.many is not None: many = self.many else: - many = hasattr(obj, '__iter__') and not isinstance(obj, Page) + many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict)) if many: return [self.to_native(item) for item in obj] @@ -343,7 +343,7 @@ class BaseSerializer(Field): if self.many is not None: many = self.many else: - many = hasattr(data, '__iter__') and not isinstance(data, dict) + many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict)) # TODO: error data when deserializing lists if many: @@ -368,7 +368,7 @@ class BaseSerializer(Field): if self.many is not None: many = self.many else: - many = hasattr(obj, '__iter__') and not isinstance(obj, Page) + many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict)) if many: self._data = [self.to_native(item) for item in obj] diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 62de16abb..2d17e99df 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -185,6 +185,33 @@ class BasicTests(TestCase): self.assertEquals(instance.age, self.person_data['age']) +class DictStyleSerializer(serializers.Serializer): + """ + Note that we don't have any `restore_object` method, so the default + case of simply returning a dict will apply. + """ + email = serializers.EmailField() + + +class DictStyleSerializerTests(TestCase): + def test_dict_style_deserialize(self): + """ + Ensure serializers can deserialize into a dict. + """ + data = {'email': 'foo@example.com'} + serializer = DictStyleSerializer(data=data) + self.assertTrue(serializer.is_valid()) + self.assertEquals(serializer.data, data) + + def test_dict_style_serialize(self): + """ + Ensure serializers can serialize dict objects. + """ + data = {'email': 'foo@example.com'} + serializer = DictStyleSerializer(data) + self.assertEquals(serializer.data, data) + + class ValidationTests(TestCase): def setUp(self): self.comment = Comment(