The Python Oracle

How to Filter in DRF Serializer using a Self Referencing Recursive Field

--------------------------------------------------
Rise to the top 3% as a developer or hire one of them at Toptal: https://topt.al/25cXVn
--------------------------------------------------

Music by Eric Matyas
https://www.soundimage.org
Track title: Puzzle Game 5 Looping

--

Chapters
00:00 How To Filter In Drf Serializer Using A Self Referencing Recursive Field
01:03 Accepted Answer Score 5
02:00 Answer 2 Score 0
03:36 Thank you

--

Full question
https://stackoverflow.com/questions/4623...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python #django #python3x #djangorestframework

#avk47



ACCEPTED ANSWER

Score 5


If I understand well, your are trying to serialize Menu objects in a hierarchical way. To do that, I guess you serialize recursively your top level Menu objects, don't you? (or else you will get all Menu objects at the top level).

To be able to filter active children only, I would suggest to create a active_children property on your model:

class Menu(MPTTModel, TimeStampedModel):
    name = models.CharField(max_length=100)
    active = models.BooleanField(default=1)
    parent = TreeForeignKey('self', null=True, blank=True, related_name='children')

    @property
    def active_children(self):
        return self.children.filter(active=True)

Then you can use that as a source for your children field in you serializer:

class MenuListSerializer(serializers.ModelSerializer):
    url = serializers.HyperlinkedIdentityField(view_name='menu_detail')
    children = RecursiveField(many=True, required=False, source='active_children')

Now you should only have active children when serializing.

Note that you should also filter top level objects in your queryset as the filtering above only works for the children in Menu objects.




ANSWER 2

Score 0


There is a more dynamic solution for this issue. When you send the many=True kwarg to the RecursiveField (Serializer), it uses the ListSerializer with the same args and kwargs (with some limitations) for getting the objects and then uses the RecursiveField for all children. In this flow the ListSerializer is the default listing serializer, you can change it by the Meta of the RecursiveField. (you can see in the code)

The ordering will be applied in the to_representation method of the ListSerializer, we have to provide the ordering value, and the only way to send the ordering value is by sending it to RecursiveField. Kwargs are sent to ListSerializer in the __new__ method so, we can pop the ordering value in __init__, thus RecursiveField will not raise an unexpected kwarg error.

The ListSerializer kwargs are limited with constants in the rest_framework.serializers.LIST_SERIALIZER_KWARGS, we have to add ordering kwarg there too, in order to work with ordering value in the ListSerializer. Then we can order the data without any scenario specific code.

import rest_framework

rest_framework.serializers.LIST_SERIALIZER_KWARGS += ('ordering',)


class RecursiveListField(serializers.ListSerializer):

    def __init__(self, *args, **kwargs):
        self.ordering = kwargs.pop('ordering', None)
        super(RecursiveListField, self).__init__(*args, **kwargs)

    def to_representation(self, data):
        data = self.ordering and data.order_by(*self.ordering) or data
        return super(RecursiveListField, self).to_representation(data)


class RecursiveField(serializers.Serializer):
    def __init__(self, *args, **kwargs):
        kwargs.pop('ordering', None)
        super(RecursiveField, self).__init__(*args, **kwargs)

    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data

    class Meta:
        list_serializer_class = RecursiveListField

You can use the recursive field like this, anywhere you want:

class SomeSerializer(serializers.ModelSerializer):
    children = RecursiveField(many=True, ordering=('id'))