Python Django Rest框架-使用ModelSerializer和ModelViewSet更新相关模型 背景

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

我有两个序列化程序:PostSerializerpostmageserializer,它们都继承了DRF ModelSerializer。PostImage模型通过相关的图片链接到Post

由于我希望序列化程序执行更新,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')
我还定义了一个继承ModelViewSet的视图集

 class PostViewSet(viewsets.ModelViewSet):
        queryset = Post.objects.all()
        serializer_class = PostSerializer
最后,PostViewSet被注册到DefaultRouter。(省略代码)

目标 目标很简单

  • 通过邮递员发送PUT请求,url类似于“PUT”
  • 因为应该包括图像文件,所以请求将通过表单数据完成,如
问题 我得到400个响应,错误消息如下

{
“照片”:[ “此字段为必填字段。” ],
“标题”:[ “此字段为必填字段。” ],
“内容”:[ “此字段为必填字段。” ]
}

(请注意,错误消息可能与DRF错误消息不完全匹配,因为它们是经过翻译的。)

很明显,我的PUT字段都没有被应用。 所以我一直在挖掘Django rest框架源代码本身,并在ViewSet中发现了序列化程序验证

我对此表示怀疑,因为我不是通过JSON而是通过使用键值对的表单数据来提交请求,所以request.data没有得到正确验证

但是,我应该在请求中包含多个图像,这意味着纯JSON将无法工作

对于这种情况,最明确的解决方案是什么

多谢各位

更新 正如Neil指出的,我在PostSerializer的update()方法的第一行添加了print(self)。但是,我的控制台上没有打印任何内容

我认为这是由于我上面的错误,因为调用序列化程序更新()方法的perform_update()方法在之后被称为

因此,我的问题的主要概念可以缩小到以下几点

  • 我应该如何修复请求的数据字段,以便ModelViewSet的update()方法内部的验证能够通过
  • 是否必须重写ModelViewSet的update()方法(不是ModelSerializer中的方法)

  • 再次感谢。

    首先,您需要设置标题:

    Content-Type: multipart/form-data;
    
    但如果您在postman中设置表单数据,则此标题应为 默认

    您不能将图像作为json数据发送(除非您将其编码为字符串,并在服务器端解码为图像,例如base64)

    在DRF中,默认情况下,PUT需要所有字段。如果只想设置部分字段,则需要使用修补程序

    要解决此问题并使用PUT更新部分字段,您有两个选项:

    • 编辑viewset中的update方法以部分更新序列化程序
    • 编辑路由器以始终在更高级的序列化程序中调用部分更新方法
    您可以覆盖viewsetupdate方法以始终更新序列化程序部分(仅更改提供的字段):

    rest\u framework.parsers.MultiPartParser

    到REST\u框架dict的主设置文件:

    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'),然后在循环之前检查是否提供了照片数据

    解决方案是某种混合物或@Neil和@mon的答案。不过,我会再弄清楚一点

    分析 现在邮递员提交的表单数据包含2个键值对(请参考我在原始问题中上传的照片)。一个是链接到多个照片文件的“照片”键字段,另一个是链接到一大块类似JSON的字符串的“数据”键字段。尽管这是一种公平的方法,可以将数据与文件一起发布或放置,但DRF MultiPartParser或JSONParser不会正确解析这些数据

    我收到错误消息的原因很简单
    self.get_序列化程序(实例,data=request.data,partial=partial
    ModelViewSet
    中的方法)(尤其是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)