Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework - Updating a foreign key

I am a bit frustrated with this problem using the Django Rest Framework:

I am using a viewset, with a custom serializer. This serializer has its depth set to 1. When i query this viewset I get the correct representation of data for example:

data = {
  id: 1,
  issue_name: 'This is a problem',
  status: {
    id: 3,
    name: 'todo'
  }
}

The problem comes in when I need to update the status. For example if I want to select another status for this issue, for example:

status_new = {
   id: 4,
   name: 'done'
}

I send the following PATCH back to the server, this is the output:

data = {
  id: 1,
  issue_name: 'This is a problem',
  status: {
    id: 4,
    name: 'done'
  }

}

However, the status does not get updated. Infact, it is not even a part of the validated_data dictionary. I have read that nested relations are read-only. Could someone please tell me what I need to do this in a simple way?

Would really be obliged.

Thanks in advance

like image 355
Nav Avatar asked Oct 12 '15 09:10

Nav


3 Answers

As stated in the documentation, you will need to write your own create() and update() methods in your serializer to support writable nested data.

You will also need to explicitly add the status field instead of using the depth argument otherwise I believe it won't be automatically added to validated_data.

EDIT: Maybe I was a bit short on the details: what you want to do is override update in ModelIssueSerializer. This will basically intercept the PATCH/PUT requests on the serializer level. Then get the new status and assign it to the instance like this:

class StatusSerializer(serializers.ModelSerializer):
    class Meta:
        model = Status

class ModelIssueSerializer(serializers.ModelSerializer):
    status = StatusSerializer()
    # ...
    def update(self, instance, validated_data):
        status = validated_data.pop('status')
        instance.status_id = status.id
        # ... plus any other fields you may want to update
        return instance

The reason I mentioned in the comment that you might need to add a StatusSerializer field is for getting status into validated_data. If I remember correctly, if you only use depth then nested objects might not get serialized inside the update() / create() methods (although I might be mistaken on that). In any case, adding the StatusSerializer field is just the explicit form of using depth=1

like image 84
gbs Avatar answered Oct 02 '22 21:10

gbs


I usually use custom field for such cases.

class StatusField(serializers.Field):

    def to_representation(self, value):
        return StatusSerializer(value).data

    def to_internal_value(self, data):
        try:
            return Status.objects.filter(id=data['id']).first()
        except (AttributeError, KeyError):
            pass

And then in main serializer:

class IssueSerializer(serializers.ModelSerializer):
    status = StatusField()

    class Meta:
        model = MyIssueModel
        fields = (
            'issue_name',
            'status',
        )
like image 36
Igor Pomaranskiy Avatar answered Oct 02 '22 19:10

Igor Pomaranskiy


I would assume that your models mimic your serializer's data. Also, I would assume that you have a one to many relation with the status(es) but you don't need to create them via the issue serializer, you have a different endpoint for that. In such a case, you might get away with a SlugRelatedField.

from rest_framework import serializers


class StatusSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyStatusModel
        fields = (
            'id',
            'status',
        )


class IssueSerializer(serializers.ModelSerializer):
    status = serializers.SlugRelatedField(slug_field='status', queryset=MyStatusModel.objects.all())

    class Meta:
        model = MyIssueModel
        fields = (
            'issue_name',
            'status',
        )

Another valid solution would be to leave here the foreign key value and deal with the display name on the front-end, via a ui-select or select2 component - the RESTfull approach: you are handling Issue objects which have references to Status objects. In an Angular front-end app, you would query all the statuses from the back-end on a specific route and then you will display the proper descriptive name based on the foreign key value form Issue.

Let me know how is this working out for you.

like image 42
Roba Avatar answered Oct 02 '22 21:10

Roba