Dynamically exclude or include a field in Django REST framework serializer

21,990

Solution 1

Have you tried this technique

class QuestionSerializer(serializers.Serializer):
    def __init__(self, *args, **kwargs):
        remove_fields = kwargs.pop('remove_fields', None)
        super(QuestionSerializer, self).__init__(*args, **kwargs)

        if remove_fields:
            # for multiple fields in a list
            for field_name in remove_fields:
                self.fields.pop(field_name)

class QuestionWithoutTopicView(generics.RetrieveAPIView):
        serializer_class = QuestionSerializer(remove_fields=['field_to_remove1' 'field_to_remove2'])

If not, once try it.

Solution 2

Creating a new serializer is the way to go. By conditionally removing fields in a serializer you are adding extra complexity and making you code harder to quickly diagnose. You should try to avoid mixing the responsibilities of a single class.

Following basic object oriented design principles is the way to go.

QuestionWithTopicView is a QuestionWithoutTopicView but with an additional field.

class QuestionSerializer(serializers.Serializer):
        id = serializers.CharField()
        question_text = QuestionTextSerializer()
        topic = TopicSerializer()

class TopicQuestionSerializer(QuestionSerializer):
       topic = TopicSerializer()

Solution 3

Extending above answer to a more generic one

class QuestionSerializer(serializers.Serializer):
    def __init__(self, *args, **kwargs):
        fields = kwargs.pop('fields', None)
        super(QuestionSerializer, self).__init__(*args, **kwargs)
        if fields is not None:
            allowed = set(fields.split(','))
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)

class QuestionWithoutTopicView(generics.RetrieveAPIView):
    def get_serializer(self, *args, **kwargs):
        kwargs['context'] = self.get_serializer_context()
        fields = self.request.GET.get('display')
        serializer_class = self.get_serializer_class()
        return serializer_class(fields=fields,*args, **kwargs)
    def get_serializer_class(self):
        return QuestionSerializer
    

Now we can give a query parameter called display to output any custom display format http://localhost:8000/questions?display=param1,param2

Solution 4

You can set fields and exclude properties of Meta

Here is an Example:

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        exclude = ['id', 'email', 'mobile']

    def __init__(self, *args, **kwargs):
        super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
        #  @note: For example based on user,  we will send different fields
        if self.context['request'].user == self.instance.user:
            # Or set self.Meta.fields = ['first_name', 'last_name', 'email', 'mobile',]
            self.Meta.exclude = ['id']

Share:
21,990
Sudip Kafle
Author by

Sudip Kafle

Python / Django developer Twitter: @kaflesudip Linkedin: @kaflesudip

Updated on July 14, 2021

Comments

  • Sudip Kafle
    Sudip Kafle almost 3 years

    I have a serializer in Django REST framework defined as follows:

    class QuestionSerializer(serializers.Serializer):
        id = serializers.CharField()
        question_text = QuestionTextSerializer()
        topic = TopicSerializer()
    

    Now I have two API views that use the above serializer:

    class QuestionWithTopicView(generics.RetrieveAPIView):
        # I wish to include all three fields - id, question_text
        # and topic in this API.
        serializer_class = QuestionSerializer
    
    class QuestionWithoutTopicView(generics.RetrieveAPIView):
        # I want to exclude topic in this API.
        serializer_class = ExamHistorySerializer
    

    One solution is to write two different serializers. But there must be a easier solution to conditionally exclude a field from a given serializer.