在 RESTful API 设计中,部分更新是一种非常常见的需求。Django REST Framework(DRF)对 HTTP 的 PATCH 方法提供了原生支持,但在实际开发中,很多开发者会遇到 validated_data 为空、数据库没有更新等问题。本文将从 HTTP 原理、DRF 支持、请求方式、Serializer 与 ViewSet 使用方法、以及实际开发中常见问题的排查方法进行全面解析,并给出可直接使用的示例。
一、HTTP PATCH 方法简介
HTTP 协议提供了多种请求方法,其中:
- GET:查询资源
- POST:新建资源
- PUT:整体替换资源
- PATCH:部分更新资源
- DELETE:删除资源
PATCH 方法与 PUT 最大的区别在于,它只修改资源中指定的字段,而不影响未指定的字段,非常适合在前端只需要更新部分数据的场景。
例如,对于某个材料清单资源:
http
PATCH /materials/1
Content-Type: application/json
{
"material_count": 20
}
表示仅修改 material_count 字段,其余字段保持不变。相比之下,PUT 请求需要提交完整资源数据,否则未提交的字段可能会被置空。
二、DRF 对 PATCH 的支持
在 DRF 中,只要使用 UpdateModelMixin 或者继承 ModelViewSet,就自动支持 PUT 与 PATCH 请求。例如:
python
class MaterialViewSet(mixins.UpdateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = IcEngineeringChecklistMaterials.objects.all()
serializer_class = ChecklistMaterialSerializer
默认路由情况下:
| URL | HTTP 方法 | 功能 |
|---|---|---|
| /materials/{id}/ | PUT | 全量更新 |
| /materials/{id}/ | PATCH | 部分更新 |
无需额外编写 action,就可以直接使用 PATCH 实现部分更新。
三、PATCH 请求的正确使用方式
DRF 默认只从请求体(body)读取 PATCH 数据,而不会从查询参数(params)中读取。这是许多开发者在实际开发中遇到 validated_data 为空的根本原因。
正确示例
前端使用 Axios:
js
axios.patch("/api/materials/1/", {
unit: "kg",
material_count: 5
}, {
headers: { "Content-Type": "application/json" }
});
或者使用 fetch:
js
fetch("/api/materials/1/", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
unit: "kg",
material_count: 5
})
});
错误示例(不可用):
http
PATCH /materials/1?unit=kg
因为 DRF 不会把 query 参数作为 request.data 处理,Serializer 无法接收到数据,validated_data 将为空。
四、Serializer 部分更新写法
对于部分更新,通常会定义一个专门的 Serializer,只包含可更新字段:
python
class ChecklistMaterialPartialUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = IcEngineeringChecklistMaterials
fields = ['material_count', 'deadline_arrive', 'brand', 'unit']
如果某些字段修改需要额外逻辑(例如单位转换),可以在 update() 方法中实现:
python
def update(self, instance, validated_data):
if 'unit' in validated_data:
new_unit = validated_data['unit']
old_unit = instance.unit
if old_unit != new_unit:
# 示例:单位转换逻辑
instance.material_count = convert_count(instance.material_count, old_unit, new_unit)
instance.unit = new_unit
for field in ['material_count', 'brand', 'deadline_arrive']:
if field in validated_data:
setattr(instance, field, validated_data[field])
instance.save()
return instance
注意:
validated_data不会自动写入 Model,必须通过setattr或super().update()显式赋值。return instance返回的是修改后的对象(前提是你已经修改并保存了字段)。
五、ViewSet 中 get_serializer_class 写法
如果你的 ViewSet 中不同操作使用不同的 Serializer,可以通过 get_serializer_class 动态返回:
python
def get_serializer_class(self):
if self.action == 'partial_update':
return ChecklistMaterialPartialUpdateSerializer
return ChecklistSerializer
六、PATCH 常见问题排查
当发现 PATCH 不生效时,可以按照以下步骤排查:
-
检查 request.data 是否有内容
打印
request.data,确保前端将数据放在请求体 JSON 中,而不是 params。 -
检查 serializer 校验是否通过
使用
serializer.is_valid(raise_exception=True),确保字段验证通过。 -
检查 validated_data 是否为空
打印
serializer.validated_data。 -
检查 update 是否真正写入数据库
可以通过 Django Admin 或直接查询数据库验证。
七、最终推荐模板
Serializer
python
class ChecklistMaterialPartialUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = IcEngineeringChecklistMaterials
fields = ['material_count', 'deadline_arrive', 'brand', 'unit']
def update(self, instance, validated_data):
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
ViewSet
python
class MaterialViewSet(mixins.UpdateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = IcEngineeringChecklistMaterials.objects.all()
def get_serializer_class(self):
if self.action == 'partial_update':
return ChecklistMaterialPartialUpdateSerializer
return ChecklistSerializer
八、总结
- PATCH 用于部分更新,只修改指定字段,不影响未指定字段。
- DRF 自动支持 PATCH,只需继承 UpdateModelMixin 或 ModelViewSet。
- 请求体必须使用 JSON,参数不要放在 query params 中,否则 validated_data 会为空。
- Serializer.update 必须显式赋值 ,返回的
instance是修改后的对象。 - 对于涉及逻辑转换的字段(如 unit),在 update 方法中处理即可。