Passing partial=True down to nested serializer in DRF

Question:

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.

Asked By: njari

||

Answers:

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}
Answered By: Sunderam Dubey