diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 94fff7e21..b97262704 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -20,7 +20,7 @@ Each serializer field class constructor takes at least these arguments. Some Fi ### `source` -The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `Field(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `Field(source='user.email')`. +The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `Field(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `Field(source='user.email')`. Alternatively, `source` can be a callable that takes a single argument (the object being serialized) and returns the value that will populate this field. The value `source='*'` has a special meaning, and is used to indicate that the entire object should be passed through to the field. This can be useful for creating nested representations. (See the implementation of the `PaginationSerializer` class for an example.) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 535aa2ac8..d018f659b 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -160,6 +160,9 @@ class Field(object): source = self.source or field_name value = obj + if callable(source): + return source(value) + for component in source.split('.'): value = get_component(value, component) if value is None: diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index 8b87a0847..24b25440e 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -803,6 +803,26 @@ class CallableDefaultValueTests(TestCase): self.assertEqual(instance.text, 'overridden') +class CallableSourceTests(TestCase): + def setUp(self): + class CommentSerializer(serializers.Serializer): + email = serializers.EmailField() + content = serializers.CharField(max_length=1000) + length = serializers.IntegerField( + source=lambda comment: len(comment.content), + read_only=True) + self.serializer_class = CommentSerializer + + def test_callable_source(self): + instance = Comment('user@email.com', 'foobar', None) + serializer = self.serializer_class(instance=instance) + self.assertEquals(serializer.data, { + 'email': 'user@email.com', + 'content': 'foobar', + 'length': 6 + }) + + class ManyRelatedTests(TestCase): def test_reverse_relations(self): post = BlogPost.objects.create(title="Test blog post")