You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django REST Framework中PUT请求为何表现得如同PATCH请求?

问题根源分析

你的PUT请求表现得像PATCH,核心原因有两个:

  1. 序列化器字段配置与验证逻辑
    你的ExpenseWriteSerializer中,provider和client设置了required=False,这意味着当请求体不包含这两个字段时,序列化器不会将它们纳入validated_data。同时,当前的validate_provider和validate_client方法没有处理None值的情况——如果我们主动传入null或者试图将字段设为null,会直接抛出查询异常,导致无法正确清空字段。

  2. 视图层的更新逻辑不符合PUT语义
    在CustomViewSet的update方法中,你使用了instance.update(**serializer.validated_data)来更新资源。Django的QuerySet.update()方法只会更新传入的键值对,未传入的字段会保持原有值,这完全是PATCH请求的行为,而非PUT要求的“全量替换”。


分步解决方案

1. 修复序列化器的验证逻辑

首先,修改字段的验证方法,允许将字段设为null(符合模型的null=True配置):

class ExpenseWriteSerializer(CustomWriteSerializer):
    provider = serializers.IntegerField(required=False, allow_null=True)
    client = serializers.IntegerField(required=False, allow_null=True)

    def validate_provider(self, value):
        # 当值为None时直接返回,不执行查询
        if value is None:
            return None
        return contact_models.Contact.objects.using(self.context['db']).get(id=value)

    def validate_client(self, value):
        if value is None:
            return None
        return contact_models.Contact.objects.using(self.context['db']).get(id=value)

    class Meta:
        model = models.Expense
        fields = '__all__'

2. 让PUT请求真正执行全量替换

在CustomViewSet的update方法中,判断当前请求是否为PUT(即partial=False),如果是,则主动将未传入的provider和client字段设为None,确保它们被纳入更新逻辑:

def update(self, request, pk, **kwargs):
    """ Update Instance """
    try:
        partial = kwargs.pop('partial', False)
        instance = self.get_custom_queryset(request).filter(id=pk)
        core_user = User.objects.using(request.user.db).filter(email=request.user.email).first()
        serializer = self.write_serializer_class(
            data=request.data, partial=partial, context={"db": request.user.db, "core_user": core_user},
        )
        if serializer.is_valid():
            # --- 新增:处理PUT请求的全量替换逻辑 ---
            if not partial:
                # 对于PUT请求,未传入的provider/client字段设为None
                for field in ['provider', 'client']:
                    if field not in serializer.validated_data:
                        serializer.validated_data[field] = None
            # --- 结束新增 ---

            # extract many to many data
            tags = serializer.validated_data.pop('tags', None)
            attachments = serializer.validated_data.pop('attachments', None)
            # update values
            instance.update(**serializer.validated_data)
            instance.first().save()
            # ... 后续代码保持不变 ...

3. 可选:规范PUT请求的序列化器行为(进阶)

如果你希望严格区分PUT和PATCH的序列化规则,可以在序列化器的__init__方法中根据partial参数动态调整字段的required属性:

class ExpenseWriteSerializer(CustomWriteSerializer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 如果是PUT请求(partial=False),允许provider/client为null,但必须显式传入或由视图补充
        if not self.partial:
            for field_name in ['provider', 'client']:
                self.fields[field_name].required = False
                self.fields[field_name].allow_null = True

    # ... 其余字段和验证方法不变 ...

验证效果

现在发送PUT请求时:

  • 如果请求体包含provider或client,会正常更新为对应值;
  • 如果请求体不包含这两个字段,它们会被自动设为null,符合PUT请求“替换整个资源”的语义。

内容的提问来源于stack exchange,提问作者Ira Ramírez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 10:42:36