如何处理嵌套序列化程序的 PATCH 请求?

How to handle a PATCH request for a nested Serializer?

我正在尝试使用 Django Rest Framework 为 http://127.0.0.1:8000/api/v1/controller/device/<pk>/

上的设备详细信息端点构建端点

型号:-

设备型号

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()

配置模型

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()

对于上述模型,我将序列化程序创建为:-

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

由于我想合并一个嵌套的序列化器并使其可写,所以需要手动添加.update方法。

和观点:

观看次数

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

以上代码对于 PUT 请求工作正常,但是当我尝试发送补丁请求时,它需要所有字段,即,直到我输入所有字段,我无法发送请求,但是当我必须提供所有字段以更改单个字段时,这不是补丁请求。

ps:我已经对这道题的模型表示进行了抽象,并尝试给出模型的概念。

您可以使用 partial=True

覆盖补丁方法
class DeviceDetailView(RetrieveUpdateDestroyAPIView):
    serializer_class = DeviceDetailSerializer
    queryset = Device.objects.all()
    
    def patch(self, request, *args, **kwargs):
        kwargs['partial'] = True 
        return self.update(request, *args, **kwargs)

或者你可以试试这个方法,为此你必须手动编写补丁 https://www.django-rest-framework.org/api-guide/serializers/#partial-updates

# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)

你可以通过partial=True

参考:https://www.django-rest-framework.org/api-guide/serializers/#partial-updates

partial 将在这里工作。所以就像

s = DeviceDetailSerializer(instance, data={'name': 'Manish'}, partial=True)
s.is_valid()
s.save()

如果您使用 patch 请求点击 URL,它将自动传递 partial 参数。

我在您的代码中发现的唯一问题是

  1. 你为什么要做 self.Meta.model(**validated_data)?因为在更新方法中 self.instance 总是存在
  2. 当您执行 instance.organization = validated_data['organization'] 时,您并没有检查 key 是否实际存在于 validated_data 中。所以你需要先检查密钥是否存在,如果密钥存在才更新数据。为此,您可以执行 if 'organization' in validated_data 然后执行 instance.organization = validated_data['organization']

其他一切看起来都不错,如果您仍然遇到任何问题,请更新该问题的描述以便更好地理解。

  1. 对于您的第一个疑问,补丁请求需要所有字段:

当您写 instance.organization = validated_data['organization'] 时,您并没有检查 key 是否确实存在。 所以,为此你可以像 validated_data.get('organization') 那样做。这表示如果您的组织出现在经过验证的数据中,那么您的变量将被分配。

您正在将所有经过验证的数据提取到变量中,然后尝试更新它,但您可以使用此代码轻松完成。

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

这只会更新仅存在于 validated_data 中的您的字段。它不会更新其他字段。

  1. 对于第二个疑问,您还想更新嵌套的序列化程序字段

您可以直接从 validated_data 获取数据并更新它。

在这种情况下,您所做的是正确的,我认为它会完美无缺。

如果您仍然遇到任何问题,请在本帖中回复我,我会尽力为您解答。