Django REST Framework中PUT请求为何表现得如同PATCH请求?
问题根源分析
你的PUT请求表现得像PATCH,核心原因有两个:
序列化器字段配置与验证逻辑
你的ExpenseWriteSerializer中,provider和client设置了required=False,这意味着当请求体不包含这两个字段时,序列化器不会将它们纳入validated_data。同时,当前的validate_provider和validate_client方法没有处理None值的情况——如果我们主动传入null或者试图将字段设为null,会直接抛出查询异常,导致无法正确清空字段。视图层的更新逻辑不符合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
相关产品推荐
相关产品推荐

