Search code examples
pythondjangodjango-rest-frameworkdjango-querysetdjango-serializer

Passing partial=True down to nested serializer in DRF


I have two serializers organised like this:

class OuterSerializer():
  inner_obj = InnerSerializer(many=True, required=False)
  other fields ......
class InnerSerializer():
  field_1 = CharField()
  field_2 = CharField()

Now my use case is to partial update the outer serializer's model. How I'm doing that is:

   def partial_update(self, request, *args, **kwargs):
        serializer = OuterSerializer(data=request.data, context={'request': self.request}, partial=True)
        serializer.is_valid(raise_exception=True)
        data = serializer.data
        outerobj = self.service_layer.update(kwargs['pk'], data, request.user)
        response_serializer = OpportunitySerializer(instance=outerobj, context={'request': self.request})
        return Response(response_serializer.data, HTTPStatus.OK) 

The issue is this partial flag does not get passed down to the InnerSerializer. For example if my request body looks like below, I want it to work:

{"inner_obj":
  {
    "field_1" : "abc"
  }
}

Currently I get a 400 error for this saying the field is required.

What I've tried :

  1. Setting the partial variable within the OuterSerializer in the init method by modifying it as such
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # We pass the "current serializer" context to the "nested one"
        self.fields['inner_obj'].context.update(self.context)
        self.fields['inner_obj'].partial = kwargs.get('partial')  

However this doesn't travel down.


Solution

  • Try to modify the InnerSerializer so that it could accept the partial argument and pass it to its parent, like following:

    class InnerSerializer(serializers.Serializer):
        field_1 = CharField()
        field_2 = CharField()
    
        def __init__(self, *args, **kwargs):
            self.partial = kwargs.pop('partial', False)
            super().__init__(*args, **kwargs)
    
    class OuterSerializer(serializers.Serializer):
        inner_obj = InnerSerializer(many=True, required=False)
        other fields ......
    
        def __init__(self, *args, **kwargs):
            partial = kwargs.get('partial')
            super().__init__(*args, **kwargs)
            self.fields['inner_obj'].child.partial = partial
    

    Another possible solution.

    You can also override the to_internal_value() method in the InnerSerializer to make it accept partial updates so:

    class InnerSerializer(serializers.Serializer):
        field_1 = CharField()
        field_2 = CharField()
    
        def to_internal_value(self, data):
            if self.partial:
                return {field: data.get(field, getattr(self.instance, field)) for field in data}
            return super().to_internal_value(data)
    
    class OuterSerializer(serializers.Serializer):
        inner_obj = InnerSerializer(many=True, required=False)
        other fields ......
    

    Edit:

    For the error:

    KeyError: "Got KeyError when attempting to get a value for field field_2on serializerInnerSerializer`.

    The error message you're encountering suggests that the serializer is trying to access the value for field_2 from the data, but it's not present.

    Currently to solve the error, you should override the to_representation() method in the InnerSerializer to only include the fields that are present so:

    class InnerSerializer(serializers.Serializer):
        field_1 = CharField()
        field_2 = CharField()
    
        def to_representation(self, instance):
            data = super().to_representation(instance)
            return {field: value for field, value in data.items() if value is not None}