Ihn*_*Kim 5 python django django-rest-framework
我有两个序列化器:PostSerializer和PostImageSerializer,它们都继承了 DRF ModelSerializer。PostImage 模型通过 related_name='photos' 与 Post 相关联。
由于我希望序列化程序执行update,PostSerializer 覆盖 ModelSerializer 中的 update() 方法,如官方 DRF 文档中所述。
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')
Run Code Online (Sandbox Code Playgroud)
我还定义了一个继承ModelViewSet 的 ViewSet。
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
Run Code Online (Sandbox Code Playgroud)
最后 PostViewSet 被注册到 DefaultRouter。(省略代码)
目标很简单。
我收到 400 响应,错误消息如下。
{
"照片": [ "此字段是必需的。" ],
"title": [ "此字段为必填项。" ],
"content": [ "此字段为必填项。" ]
}
(您是否应该注意错误消息可能与 DRF 错误消息不完全吻合,因为它们已被翻译。)
很明显,我的 PUT 字段都没有被应用。所以我一直在挖掘 Django rest 框架源代码本身,并发现 ViewSet update() 方法中的序列化程序验证仍然失败。
我对此表示怀疑,因为我不是通过 JSON 而是通过使用键值对的表单数据 PUT 请求,所以 request.data 没有得到正确验证。
但是,我应该在请求中包含多个图像,这意味着纯 JSON 不起作用。
对于这种情况,最明确的解决方案是什么?
谢谢你。
正如尼尔指出的那样,我在 PostSerializer 的 update() 方法的第一行添加了 print(self)。但是我的控制台上没有打印出来。
我想这是因为我的doupt上述这就要求串行update()方法被调用,因为perform_update()方法后 串行进行了验证。
因此,我的问题的主要概念可以缩小为以下内容。
再次感谢。
小智 6
首先你需要设置标题:
Content-Type: multipart/form-data;
Run Code Online (Sandbox Code Playgroud)
但也许如果你在邮递员中设置表单数据,这个标题应该是默认的。
您无法将图像作为 json 数据发送(除非您将其编码为字符串并在服务器端解码为图像,例如 base64)。
在 DRF PUT中,默认需要所有字段。如果您只想设置部分字段,则需要使用PATCH。
要解决此问题并使用PUT更新部分字段,您有两种选择:
您可以重写视图集更新方法以始终更新序列化器部分(仅更改提供的字段):
def update(self, request, *args, **kwargs):
partial = True # Here I change partial to True
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
Run Code Online (Sandbox Code Playgroud)
添加
rest_framework.parsers.MultiPartParser
到 REST_FRAMEWORK 字典的主设置文件:
REST_FRAMEWORK = {
...
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.MultiPartParser',
)
}
Run Code Online (Sandbox Code Playgroud)
查看您的序列化程序,很奇怪您没有从PostSerializer收到错误,因为您没有将“照片”字段添加到 Meta.fields 元组中。
在这种情况下我的更多建议:
解决方案有点混合或@Neil 和@mon 的答案。不过我会再澄清一点。
现在,Postman 提交的表单数据包含 2 个键值对(请参阅我在原始问题中上传的照片)。一个是与多个照片文件链接的“照片”键字段,另一个是与一大块“类似 JSON 的字符串”链接的“数据”键字段。尽管这是一种将数据与文件一起 POST 或 PUT 的公平方法,但 DRF MultiPartParser 或 JSONParser 无法正确解析这些数据。
我收到错误消息的原因很简单。self.get_serializer(instance, data=request.data, partial=partial里面的方法ModelViewSet(特别是UpdateModelMixin)无法理解request.data部分。
目前request.data提交的表单数据如下所示。
<QueryDict: { "photos": [PhotoObject1, PhotoObject2, ... ],
"request": ["{'\n 'title': 'title test', \n 'content': 'content test'}",]
}>
Run Code Online (Sandbox Code Playgroud)
仔细观察“请求”部分。该值是一个普通string对象。
然而我的 PostSerializer 希望request.data看起来像下面这样。
{ "photos": [{"image": ImageObject1, "post":1}, {"image": ImageObject2, "post":2}, ... ],
"title": "test title",
"content": "test content"
}
Run Code Online (Sandbox Code Playgroud)
因此,我们来做一些实验,按照上面的 JSON 形式 PUT 一些数据。IE
{ "photos": [{"image": "http://tny.im/gMU", "post": 1}],
"title" : "test title",
"content": "test content"
}
Run Code Online (Sandbox Code Playgroud)
您将收到如下错误消息。
"photos": [{"image": ["提交的数据不是文件。"]}]
这意味着每个数据都已正确提交,但图像 url http://tny.im/gMU不是文件而是字符串。
现在整个问题的原因已经清楚了。需要修复解析器,以便序列化器可以理解提交的表单数据。
1. 编写新的解析器
新的解析器应该将“类 JSON”字符串解析为正确的 JSON 数据。我从这里借用了 MultipartJSONParser 。
这个解析器的作用很简单。如果我们提交带有键“data”的“类 JSON”字符串,则从jsonrest_framework 调用并解析它。之后,返回解析后的 JSON 和请求的文件。
class MultipartJsonParser(parsers.MultiPartParser):
# /sf/answers/3535981571/
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)
Run Code Online (Sandbox Code Playgroud)
2.重新设计序列化器
官方 DRF 文档建议使用嵌套序列化器来更新或创建相关对象。然而,我们有一个显着的缺点,即 InMemoryFileObject 无法转换为序列化器期望的正确形式。为此,我们应该
update方法request.datarequest.data键名“photos”。这是因为我们的 PostSerializer 期望键名是“photos”。但基本上request.data是一个默认情况下不可变的 QuerySet。我非常怀疑我们是否必须强制改变 QuerySet。因此,我宁愿将 PostImage 创建过程委托update()给ModelViewSet. 在这种情况下,我们就不需要nested serializer再定义了。
只需这样做:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
class PostImageSerializer(serializer.ModelSerializer):
class Meta:
model = PostImage
fields = '__all__'
Run Code Online (Sandbox Code Playgroud)
3. 重写update()方法ModelViewSet
为了利用我们的 Parser 类,我们需要显式指定它。我们将整合 PATCH 和 PUT 行为,因此设置partial=True。正如我们之前看到的,图像文件带有“照片”键,因此弹出值并创建每个照片实例。
最后,得益于我们新设计的解析器,普通的“类 JSON”字符串将被转换为常规 JSON 数据。所以只需将所有内容放入serializer_class和即可perform_update。
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)
Run Code Online (Sandbox Code Playgroud)
该解决方案有效,但我不确定这是保存外键相关模型的最干净的方法。我强烈的感觉是序列化器应该保存相关模型。正如文档所述,文件以外的数据都是以这种方式保存的。如果有人能告诉我更微妙的方法来做到这一点,我将不胜感激。
| 归档时间: |
|
| 查看次数: |
8077 次 |
| 最近记录: |