diff --git a/rest_framework/fields.py b/rest_framework/fields.py index cc9410aa7..a74ce0d78 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1196,6 +1196,38 @@ class HiddenField(Field): def to_internal_value(self, data): return data +class RecursiveField(Field): + """ + A field that gets its representation from its parent. + + This method could be used to serialize a tree structure, a linked list, or + even a directed acyclic graph. As with all recursive things, it is + important to keep the base case in mind. In the case of the tree serializer + example below, the base case is a node with an empty list of children. In + the case of the list serializer below, the base case is when `next==None`. + Above all, beware of cyclical references. + + Examples: + + class TreeSerializer(self): + children = ListField(child=RecursiveField()) + + class ListSerializer(self): + next = RecursiveField(allow_null=True) + """ + + def _get_parent(self): + if hasattr(self.parent, 'child') and self.parent.child is self: + # Recursive field nested inside of some kind of composite list field + return self.parent.parent + else: + return self.parent + + def to_representation(self, value): + return self._get_parent().to_representation(value) + + def to_internal_value(self, data): + return self._get_parent().to_internal_value(data) class SerializerMethodField(Field): """ diff --git a/tests/test_fields.py b/tests/test_fields.py index 775d46184..72455b3a1 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -292,6 +292,35 @@ class TestCreateOnlyDefault: } +# Tests for RecursiveField. +# ------------------------- +class TestRecursiveField: + def setup(self): + class ListSerializer(serializers.Serializer): + name = serializers.CharField() + next = serializers.RecursiveField(allow_null=True) + self.list_serializer = ListSerializer + + class TreeSerializer(serializers.Serializer): + name = serializers.CharField() + children = serializers.ListField(child=serializers.RecursiveField()) + self.tree_serializer = TreeSerializer + + def test_serialize_list(self): + value = { + 'name':'first', + 'next': { + 'name':'second', + 'next':{ + 'name':'third', + 'next':None, + } + } + } + + serializer = self.list_serializer(value) + assert serializer.data == value + # Tests for field input and output values. # ---------------------------------------- @@ -1045,7 +1074,6 @@ class TestListField(FieldValues): ] field = serializers.ListField(child=serializers.IntegerField()) - # Tests for FieldField. # ---------------------