Search code examples
pythondjangoapidjango-rest-frameworkdjango-serializer

How to handle a PATCH request for a nested Serializer?


I am trying to build an endpoint using Django Rest Framework for the device detail endpoint on http://127.0.0.1:8000/api/v1/controller/device/<pk>/

Models:-

Device Model

class AbstractDevice(OrgMixin, BaseModel):
    name = models.CharField()
    mac_address = models.CharField()
    key = KeyField()
    model = models.CharField()
    os = models.CharField()
    system = models.CharField()
    notes = models.TextField(blank=True, help_text=_('internal notes'))
    last_ip = models.GenericIPAddressField()
    management_ip = models.GenericIPAddressField()
    hardware_id = models.CharField()

Config Model

class AbstractConfig(BaseConfig):
    device = models.OneToOneField(Device, on_delete=models.CASCADE)
    templates = SortedManyToManyField()
    vpn = models.ManyToManyField()
    STATUS = Choices('modified', 'applied', 'error')
    status = StatusField()
    context = JSONField()

For the above models, I have created serializers as:-

DeviceConfigSerializer

class DeviceConfigSerializer(serializers.ModelSerializer):
    config = serializers.JSONField()
    context = serializers.JSONField()

    class Meta:
        model = Config
        fields = ['backend', 'status', 'templates', 'context', 'config']

DevicedetailSerializer

class DeviceDetailSerializer(serializers.ModelSerializer):
    config = DeviceConfigSerializer()

    class Meta(BaseMeta):
        model = Device
        fields = [
            'id',
            'name',
            'organization',
            'mac_address',
            'key',
            'last_ip',
            'management_ip',
            'model',
            'os',
            'system',
            'notes',
            'config',
        ]
    def update(self, instance, validated_data):
        instance = self.instance or self.Meta.model(**validated_data)
        instance.name = validated_data['name']
        instance.organization = validated_data['organization']
        instance.mac_address = validated_data['mac_address']
        instance.key = validated_data['key']
        instance.last_ip = validated_data['last_ip']
        instance.management_ip = validated_data['management_ip']
        instance.model = validated_data['model']
        instance.os = validated_data['os']
        instance.system = validated_data['system']
        instance.notes = validated_data['notes']
        instance.config.backend = validated_data['config']['backend']
        instance.config.status = validated_data['config']['status']

        config_templates = validated_data['config']['templates']
        instance.config.templates.clear()
        for template in config_templates:
            instance.config.templates.add(template.pk)

        instance.config.context = json.loads(
            json.dumps(validated_data['config']['context']),
            object_pairs_hook=collections.OrderedDict,
        )
        instance.config.config = json.loads(
            json.dumps(validated_data['config']['config']),
            object_pairs_hook=collections.OrderedDict,
        )
        instance.save()
        instance.config.save()
        return instance

Since I want to incorporate a nested serializer and to make it writable so, it is required to manually add the .update method.

and the views:

Views

class DeviceDetailView(RetrieveUpdateDestroyAPIView):
    serializer_class = DeviceDetailSerializer
    queryset = Device.objects.all()

The above codes work fine for PUT request but when I try to send a patch request, it expects all the fields, i., until I feed all the fields, I am not able to send the request, but then this is not a patch request when I have to feed all the fields for changing a single field.

ps: I have abstracted the representation of the models for this questions, and tried to give an idea of the model.


Solution

    1. for your first doubt patch request expects all the fields:

    when you are writing instance.organization = validated_data['organization'] you are not checking if the key is actually present or not. so, for that you can do it like validated_data.get('organization'). This represents if you have organization present in validated data then you your variable will be assigned.

    you are fetching all the validated data into variables and then you are trying to update it but you can do it easily with this code.

    instance = super().update(instance, validated_data)
    return instance
    

    This will only update yourfields which is only present in validated_data. It will not update the other fields.

    1. for the second doubt you also want to update the nested serializer fields

    you can just get the data from the validated_data and update it.

    In this case what you have done is correct and i think it will work perfect.

    If you still face any issue, please reply me in this thread i will try to give you answer.