shanemgrey
shanemgrey

Reputation: 2378

Django Rest Framework not responding to read_only on nested data

I can't get DRF to allow providing a read_only version of nested data and a writeable version of just the ids in a list in one serializer. It feels like a bug to me, but usually that just means I'm not understanding the framework well enough, and being misled by the error message.

class Individual(models.Model):
  household = models.ForeignKey(
      'household.Household',
      null=True,
      related_name="individuals")
  name = models.CharField(
      max_length=100, default='')

class Household(models.Model):
    address_line1 = models.CharField(max_length=64, default='')

class IndividualListSerializer(serializers.ModelSerializer):
    class Meta:
        model = Individual
        depth = 0
        fields = ('url', 'id', 'name', 'household')
        read_only_fields =  fields

class HouseholdUpdateSerializer(serializers.ModelSerializer):
    individuals_details = IndividualListSerializer(many=True, source='individuals', read_only=True)
    class Meta:
        model = Household
        fields = ('id', 'address_line1', 'individuals', 'individuals_details')
        read_only_fields = ('id', 'individuals_details')

The error comes back as

AssertionError: The `.update()` method does not support writable nested fields by default. Write an explicit `.update()` method for serializer `household.serializers.HouseholdUpdateSerializer`, or set `read_only=True` on nested serializer fields. // Werkzeug Debugger</title>

I have used read_only on the nested field (which is needed in the response to the update). Yet the error still indicates I am not doing so.

If I remove the individuals field entirely, the individuals_details returns the readable data without the error, but since it's ignoring the individuals data being sent, it isn't updating that list.

If I remove the individuals_details field, DRF accepts the individuals list and performs the update on the model. But then the return data needed is not there.

So, while either the read nested or write list work on their own, when the other is added the serializer doesn't function.

This seems a pretty common area people get stuck, and it seems the answers this SO question have become the best practice for a pattern. But it doesn't work in my code for some reason. Perhaps because of the ManyToOne in my models.

I can probably work around this by changing the client to perform the PUT for the update, ignore the response, and then do a separate GET, but that's sloppy and shouldn't be needed if the DRF update can be made to work as expected.

What am I missing in this?

Upvotes: 1

Views: 3586

Answers (1)

Ivan Semochkin
Ivan Semochkin

Reputation: 8897

You ask several questions so I'll start with the representation. Actually you don't need separate field for full Individual view you could just ovveride to_representation method.

class HouseholdUpdateSerializer(serializers.ModelSerializer):

    class Meta:
        model = Household
        fields = ('id', 'address_line1', 'individuals')

    def to_representation(self, instance):
        representation = super(HouseholdUpdateSerializer, self).to_representation(instance)
        representation['individuals'] = IndividualListSerializer(instance.individuals.all(), many=True).data
        return representation

Usually when you want update related fields of your model you should override create and update serializer methods.

class HouseholdUpdateSerializer(serializers.ModelSerializer):
    ....
    def update(instance, validated_data):
        # get and remove individuals from validated_data
        individuals = validated_data.pop('individuals') 
        # delete all related links to individuals
        # You could provide some validation before clear, check if provided pks exists in db table
        instance.individuals.clear() 
        # update related links with new individuals
        instance.individuals.add(*individuals) 
        # call super to provide update for other fields
        return super(HouseholdUpdateSerializer, self).update(validated_data)  

create will probably works well in your case without overriding. If it not just write it similar to update.

Upvotes: 1

Related Questions