'How to dynamically remove fields from serializer output

I'm developing an API with Django Rest framework, and I would like to dynamically remove the fields from a serializer. The problem is that I need to remove them depending on the value of another field. How could I do that? I have a serializer like:

class DynamicSerliazer(serializers.ModelSerializer):
    type = serializers.SerializerMethodField()
    url = serializers.SerializerMethodField()
    title = serializers.SerializerMethodField()
    elements = serializers.SerializerMethodField()

    def __init__(self, *args, **kwargs):
        super(DynamicSerliazer, self).__init__(*args, **kwargs)
        if self.fields and is_mobile_platform(self.context.get('request', None)) and "url" in self.fields:
            self.fields.pop("url")

As you can see, I'm already removing the field "url" depending whether the request has been done from a mobile platform. But, I would like to remove the "elements" field depending on the "type" value. How should I do that?

Thanks in advance



Solution 1:[1]

You can customize the serialization behavior by overriding the to_representation() method in your serializer.

class DynamicSerliazer(serializers.ModelSerializer):

    def to_representation(self, obj):
        # get the original representation
        ret = super(DynamicSerializer, self).to_representation(obj)

        # remove 'url' field if mobile request
        if is_mobile_platform(self.context.get('request', None)):
            ret.pop('url')

        # here write the logic to check whether `elements` field is to be removed 
        # pop 'elements' from 'ret' if condition is True

        # return the modified representation
        return ret 

Solution 2:[2]

You can create multiple serializers and choose the proper one in view

class IndexView(APIView):
    def get_serializer_class(self):
        if self.request.GET['flag']:
            return SerializerA
        return SerializerB

use inheritance to make serializers DRY.

Solution 3:[3]

My problem was somewhat similar to yours and I solved it with inheritance.

class StaticSerializer(serializers.ModelSerializer):

    class Meta:
        model = StaticModel
        fields = (
            'first_name', 'last_name', 'password', 'username',
            'email'
        )


class DynamicSerializer(StaticSerializer):

    class Meta:
        model = StaticModel
        fields = (
            'first_name',
        )

Solution 4:[4]

Solution (ViewSet mixin)

I have solved this problem by writing my own ViewSet mixin. It provides quite easy and DRY way to override serializers depending on request action.

class ActionBasedSerializerClassMixin(viewsets.ModelViewSet):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def get_serializer_class(self):
        attr_name = f'{self.action}_serializer_class'
        if hasattr(self, attr_name):
            serializer_class = getattr(self, attr_name)
            self.serializer_class = serializer_class
        return super().get_serializer_class()

Usage

To use this mixin inherit from it at your viewset (It must be before ModelViewSet parent). The default serializer is always used as fallback To use different serializer on list action just set attribute list_serializer_class at your viewset:

    class MyViewSet(ViewSet):
        serializer_class = MySerializer
        list_serializer_class = MyListSerializer

With this code you will have MyListSerializer when action is 'list' and MySerializer for all other actions. The same patterns works for all other action types: list, create, retrieve, update, partial_update, destroy. You just need to append _serializer_class to get desired attribute name.

How serailizers should look like

class MySerializer(serializers.ModelSerializer):
    some_reverse_rel = MyOtherSerializer(many=True, read_only=True)

    class Meta:
        model = MyModel
        fields = ['field1', 'field2', 'foo', 'bar', 'some_reverse_rel']

class MyListSerailizer(MySerializer):  # Note that we inherit from previous serializer
    some_reverse_rel = None  # Getting rid of reverse relationship

    class Meta(MySerializer.Meta):
        fields = ['foo', 'bar', 'field1']

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rahul Gupta
Solution 2 ivall
Solution 3 spedy
Solution 4