如何正确序列化其模型具有外键且也需要ImageField的对象?

时间:2018-10-19 01:42:16

标签: django django-rest-framework

# models.py

class Post(models.Model):
    content = models.TextField(blank=True, default='')
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

class PostImage(models.Model):
    image = models.ImageField(upload_to=unique_upload)
    post = models.ForeignKey(
        Post, related_name='images', on_delete=models.CASCADE)

这是我为基本场景设置的模型,用户可以输入内容或上传图片作为帖子。

我想捆绑我的逻辑以处理使用contentimages或两者都创建帖子。

我首先开始玩GenericViewSetCreateViewSet,但是图像从未传递给我的序列化器。

# views.py

class CreatePostViewSet(generics.CreateAPIView /* viewsets.GenericViewSet */):
    permission_classes = (IsAuthenticated,)
    queryset = Post.objects.order_by('id')
    serializer_class = CreatePostSerializer

    def create(self, request, *args, **kwargs):
        data = {}
        print(request.data)
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        serializer.save(created_by=request.user)

        # post = serializer.instance
        # print(post)
        # for im in post.images.all():
        #     im.save(post=post)
        # print(post.images.all())

        return Response(data,
                        status=status.HTTP_201_CREATED,
                        headers=self.get_success_headers(serializer.data))

# serializers.py

class PostImageSerializer(serializers.ModelSerializer):

    class Meta:
        model = PostImage
        fields = ('id', 'url', 'image', 'post',)
        read_only_fields = ('post',)
        depth = 1

class CreatePostSerializer(serializers.ModelSerializer):
    images = PostImageSerializer(many=True, required=False)

    class Meta:
        model = Post
        fields = ('id', 'url', 'content', 'images',)
        read_only_fields = ('created_by',)
        depth = 1

    def create(self, validated_data):
        # validated_data['images'] is always []
        print(validated_data)
        raise

images在传递给序列化程序时始终为[],但在request.data['images']中确实以[<TemporaryUploadedFile: 1 - 5H5hHgY.png (image/png)>, ...的形式存在

我希望使用ModelSerializer来帮助自动解析ImageField。

# CreatePostSerializer serializers breaks down to

CreatePostSerializer():
    id = UUIDField(read_only=True)
    url = HyperlinkedIdentityField(view_name='post-detail')
    content = CharField(allow_blank=True, required=False, style={'base_template': 'textarea.html'})
    images = PostImageSerializer(many=True, required=False):
        id = UUIDField(read_only=True)
        url = HyperlinkedIdentityField(view_name='postimage-detail')
        image = ImageField(max_length=100)
        post = NestedSerializer(read_only=True):

            id = UUIDField(read_only=True)
            content = CharField(allow_blank=True, required=False, style={'base_template': 'textarea.html'})
            created_by = PrimaryKeyRelatedField(queryset=User.objects.all())

2 个答案:

答案 0 :(得分:2)

它认为request.data['images']将需要稍作更改,因为您的PostImageSerializer期望有一个包含“图像”键的对象,而您正在传递TemporaryUploadedFile的列表。

鉴于request.data['images'],您可以在将数据传递给序列化程序之前在视图中执行以下操作:

images_list: List[TemporaryUploadedFile] = request.data.pop("images") 
images = []
for image in images_list:
    images.append({
        "image": image,
    })
request.data["images"] = images

因此,我们正在使用图像键将您的TemporaryUploadedFiles列表转换为对象列表。

:edit:您是否不想在视图上转换数据以使其与序列化程序兼容?然后,您可以更改序列化程序以使其与数据兼容,这涉及到自定义createupdate方法,我现在仅向您展示如何覆盖create方法

class CreatePostSerializer(serializers.ModelSerializer):
    images = serializers.ImageField(many=True)

    class Meta:
        model = Post
        fields = ('id', 'url', 'content', 'images',)
        read_only_fields = ('created_by',)
        depth = 1

    def create(self, validated_data):
        images = validated_data.pop("images")
        post = super().create(validated_data)
        for image in images:
            serializer = PostImageSerializer(data={"image": image, "post": post.pk}, context=self.context)
            serializer.is_valid()
            serializer.save()
        return post

因此您不想覆盖请求中的数据,也不想自定义序列化器的create方法?更改序列化器使用validate方法将初始数据转换为已验证数据的方式(我认为这适用于嵌套序列化器,但未经测试):

class CreatePostSerializer(serializers.ModelSerializer):
    images = PostImageSerializer(many=True, required=False)

    class Meta:
        model = Post
        fields = ('id', 'url', 'content', 'images',)
        read_only_fields = ('created_by',)
        depth = 1

    def validate(self, attrs):
        images_list = attrs.pop("images") 
        images = []
        for image in images_list:
            images.append({
                "image": image,
            })
        attrs["images"] = images
        return attrs

答案 1 :(得分:0)

因此,我能够按照@ARJMP的建议使用它。

# views.py

class CreatePostViewSet(generics.CreateAPIView):
    # authentication_classes = (TokenAuthentication,)
    permission_classes = (IsAuthenticated,)
    queryset = Post.objects.order_by('id')
    serializer_class = CreatePostSerializer

    def create(self, request, *args, **kwargs):
        data = {}
        print(request.data)

        images = [{'image': i} for i in request.data.pop('images', [])]
        serializer = self.get_serializer(
            data={'content': request.data['content'], 'images': images})
        serializer.is_valid(raise_exception=True)

        post = serializer.save(created_by=request.user)
        # self.perform_create(serializer)
        data['post'] = serializer.data

        return Response(data,
                        status=status.HTTP_201_CREATED,
                        headers=self.get_success_headers(serializer.data))

# serializers.py

class CreatePostSerializer(serializers.ModelSerializer):
    images = PostImageSerializer(many=True, required=False)

    class Meta:
        model = Post
        fields = ('id', 'content', 'images',
                  'is_private', 'created_by',)
        read_only_fields = ('view_count', 'created',)
        depth = 1

    def create(self, validated_data):
        images = validated_data.pop('images', [])
        p = Post.objects.create(**validated_data)
        for im in images:
            pi = PostImage.objects.create(image=im['image'], post=p)
        return p

我的想法是,要使其正常工作,这似乎有些令人费解。我自己做了很多操作。我真的希望能够利用ModelSerializerCreateAPIView完成的更多“神奇”工作。

有更好的方法吗?