Luca D'Amico
Luca D'Amico

Reputation: 3212

Django Rest Framework: using DynamicFieldsModelSerializer for excluding serializer fields

I have a serializer that is used in a couple of endpoints (generics.ListAPIView), but in one of them I need to hide a serializer field. I would prefer to not write a new serializer just to cover this case.

Starting from DRF 3.0 we have dynamic fields for serializers (https://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields), but I'm having some troubles to fully understand how to use them.

I created this serializer:

class TestSerializer(DynamicFieldsModelSerializer):
    user_req = UserSerializer(read_only=True)
    user_tar = UserSerializer(read_only=True)

    class Meta:
        model = TestAssoc
        fields = ("user_req", "user_tar")

and this is my endpoint:

class TestEndpointListAPIView(generics.ListAPIView):
    serializer_class = TestSerializer
    permission_classes = [IsAuthenticated]
    lookup_field = 'test_username'

    def get_queryset(self):
        return ...

Now I need to hide the 'user_tar' field from the output, and according to the documentation I should instantiate the serializer with something like:

TestSerializer(fields=('user_req'))

but how should I do this inside my TestEndpointListAPIView? Should I override get_serializer?

Thanks for the help

EDIT:

I've found the following solution, by overriding the get_serialized function:

def get_serializer(self, *args, **kwargs):
    serializer_class = self.get_serializer_class()
    kwargs['context'] = self.get_serializer_context()
    kwargs['fields'] = ['user_req']
    return serializer_class(*args, **kwargs)

I'd like to know if it is a good solution. Thanks!

Upvotes: 4

Views: 2078

Answers (1)

Ab1gor
Ab1gor

Reputation: 408

Add this piece of code to __init__ method of the serializer class as suggested in the DRF docs:

class TestSerializer(serializers.ModelSerializer):
    user_req = UserSerializer(read_only=True)
    user_tar = UserSerializer(read_only=True)

    class Meta:
        model = TestAssoc
        fields = ("user_req", "user_tar")


    def __init__(self, *args, **kwargs):
        # Don't pass the 'fields' arg up to the superclass
        fields = kwargs.pop('fields', None)

        # Instantiate the superclass normally
        super(TestSerializer, self).__init__(*args, **kwargs)

        if fields is not None:
            # Drop any fields that are not specified in the `fields` argument.
            allowed = set(fields)
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)

And so when you call the serializer from views.py do this :

TestSerializer(queryset, fields=('user_req'))

Alternatively what you can do is define a class

class DynamicFieldsModelSerializer(serializers.ModelSerializer):

    def __init__(self, *args, **kwargs):
        # Don't pass the 'fields' arg up to the superclass
        fields = kwargs.pop('fields', None)

        super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

        if fields is not None:
            allowed = set(fields)
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)

Now import this class if you have defined it in some other file and then inherit it using

class TestSerializer(DynamicFieldsModelSerializer):

This way:

class TestSerializer(DynamicFieldsModelSerializer):
    user_req = UserSerializer(read_only=True)
    user_tar = UserSerializer(read_only=True)

    class Meta:
        model = TestAssoc
        fields = ("user_req", "user_tar")

Now you can do

TestSerializer(queryset, fields=('user_req'))

Update
In the views. Take an example of ListAPIView

class DemoView(ListAPIView):
    queryset = TestAssoc.objects.all()

    def get(self, request):
        try:
            queryset = self.get_queryset()

            data = TestSerializer(queryset, fields=('user_req')).data

            return Response( {"data" : data } ,status=status.HTTP_200_OK)
        except Exception as error:
            return Response( { "error" : str(error) } , status=status.HTTP_500_INTERNAL_SERVER_ERROR)

Upvotes: 3

Related Questions