Python Django Rest框架-使用ModelSerializer和ModelViewSet更新相关模型 背景
我有两个序列化程序:PostSerializer和postmageserializer,它们都继承了DRF ModelSerializer。PostImage模型通过相关的图片链接到Post 由于我希望序列化程序执行更新,PostSerializer将覆盖ModelSerializer中的update()方法,如官方DRF文档中所述Python Django Rest框架-使用ModelSerializer和ModelViewSet更新相关模型 背景,python,django,django-rest-framework,Python,Django,Django Rest Framework,我有两个序列化程序:PostSerializer和postmageserializer,它们都继承了DRF ModelSerializer。PostImage模型通过相关的图片链接到Post 由于我希望序列化程序执行更新,PostSerializer将覆盖ModelSerializer中的update()方法,如官方DRF文档中所述 class PostSerializer(serializers.ModelSerializer): photos = PostImageSerialize
class PostSerializer(serializers.ModelSerializer):
photos = PostImageSerializer(many=True)
class Meta:
model = Post
fields = ('title', 'content')
def update(self, instance, validated_data):
photos_data = validated_data.pop('photos')
for photo in photos_data:
PostImage.objects.create(post=instance, image=photo)
return super(PostSerializer, self).update(instance, validated_data)
class PostImageSerializer(serializer.ModelSerializer):
class Meta:
model = PostImage
fields = ('image', 'post')
我还定义了一个继承ModelViewSet的视图集
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
最后,PostViewSet被注册到DefaultRouter。(省略代码)
目标
目标很简单
- 通过邮递员发送PUT请求,url类似于“PUT”
- 因为应该包括图像文件,所以请求将通过表单数据完成,如
“照片”:[ “此字段为必填字段。” ],
“标题”:[ “此字段为必填字段。” ],
“内容”:[ “此字段为必填字段。” ]
} (请注意,错误消息可能与DRF错误消息不完全匹配,因为它们是经过翻译的。) 很明显,我的PUT字段都没有被应用。 所以我一直在挖掘Django rest框架源代码本身,并在ViewSet中发现了序列化程序验证 我对此表示怀疑,因为我不是通过JSON而是通过使用键值对的表单数据来提交请求,所以request.data没有得到正确验证 但是,我应该在请求中包含多个图像,这意味着纯JSON将无法工作 对于这种情况,最明确的解决方案是什么 多谢各位 更新 正如Neil指出的,我在PostSerializer的update()方法的第一行添加了print(self)。但是,我的控制台上没有打印任何内容 我认为这是由于我上面的错误,因为调用序列化程序更新()方法的perform_update()方法在之后被称为 因此,我的问题的主要概念可以缩小到以下几点
再次感谢。首先,您需要设置标题:
Content-Type: multipart/form-data;
但如果您在postman中设置表单数据,则此标题应为
默认
您不能将图像作为json数据发送(除非您将其编码为字符串,并在服务器端解码为图像,例如base64)
在DRF中,默认情况下,PUT需要所有字段。如果只想设置部分字段,则需要使用修补程序
要解决此问题并使用PUT更新部分字段,您有两个选项:
- 编辑viewset中的update方法以部分更新序列化程序
- 编辑路由器以始终在更高级的序列化程序中调用部分更新方法
REST_FRAMEWORK = {
...
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.MultiPartParser',
)
}
看看你的序列化程序,奇怪的是你没有从后序列化程序中得到错误,因为你没有向Meta.fields元组添加“photos”字段
在这种情况下,我提供了更多建议:
- 将required=False添加到您的photos字段中(除非您希望这是必需的)
- 如上所述,向Meta.fields元组添加照片字段=('title','content','photos',)
- 为您的已验证的\u数据添加默认值None。弹出('photos'),然后在循环之前检查是否提供了照片数据
self.get_序列化程序(实例,data=request.data,partial=partialModelViewSet
中的方法)(尤其是UpdateModelMixin)无法理解request.data
部分
当前请求。来自已提交表单数据的数据如下所示
<QueryDict: { "photos": [PhotoObject1, PhotoObject2, ... ],
"request": ["{'\n 'title': 'title test', \n 'content': 'content test'}",]
}>
{ "photos": [{"image": ImageObject1, "post":1}, {"image": ImageObject2, "post":2}, ... ],
"title": "test title",
"content": "test content"
}
因此,让我们做一些实验,并根据上面的JSON格式放置一些数据。
i、 e
您将得到如下错误消息
“照片”:[{“图像”:[“提交的数据不是文件。”]}]
这意味着每个数据都已正确提交,但图像url不是文件,而是字符串
现在,整个问题的原因已经清楚了。需要修复的是解析器,以便序列化程序能够理解提交的表单数据
解决方案
1.编写新的解析器
新的解析器应该将“类似JSON”的字符串解析为正确的JSON数据
这个解析器所做的很简单。如果我们提交带有键“data”的“类似于JSON的”字符串,请从rest_框架调用JSON
,并对其进行解析。然后,返回解析后的JSON和请求的文件
class MultipartJsonParser(parsers.MultiPartParser):
# https://stackoverflow.com/a/50514022/8897256
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
data = json.loads(result.data["data"])
qdict = QueryDict('', mutable=True)
qdict.update(data)
return parsers.DataAndFiles(qdict, result.files)
2.重新设计序列化程序
官方DRF文档建议嵌套序列化程序更新或创建相关对象。但是,我们有一个明显的缺点,InMemoryFileObject无法转换为序列化程序期望的正确形式。为此,w
{ "photos": [{"image": "http://tny.im/gMU", "post": 1}],
"title" : "test title",
"content": "test content"
}
class MultipartJsonParser(parsers.MultiPartParser):
# https://stackoverflow.com/a/50514022/8897256
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
data = json.loads(result.data["data"])
qdict = QueryDict('', mutable=True)
qdict.update(data)
return parsers.DataAndFiles(qdict, result.files)
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
class PostImageSerializer(serializer.ModelSerializer):
class Meta:
model = PostImage
fields = '__all__'
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
# New Parser
parser_classes = (MultipartJsonParser,)
def update(self, request, *args, **kwargs):
# Unify PATCH and PUT
partial = True
instance = self.get_object()
# Create each PostImage
for photo in request.data.pop("photos"):
PostImage.objects.create(post=instance, image=photo)
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
# Do ViewSet work.
self.perform_update(serializer)
return Response(serializer.data)