axios Vue3前端,Django后端,序列化器中验证数据的键错误

ds97pgxw  于 2022-11-29  发布在  iOS
关注(0)|答案(1)|浏览(139)

我有一个Vue前端,它从用户那里收集数据(和文件),并使用Axios将其发布到Django Rest框架端点。
下面是该函数的代码:

import { ref } from "vue";
import axios from "axios";

const fields = ref({
    audience: "",
    cancomment: "",
    category: "",
    body: "",
    errors: [],
    previews: [],
    images: [],
    video: [],
    user: user,
});

function submitPost() {
    
    const formData = {
        'category': fields.value.category.index,
        'body': fields.value.body,
        'can_view': fields.value.audience,
        'can_comment': fields.value.cancomment,
        'video': fields.value.video,
        'uploaded_images': fields.value.images,
        'user': store.userId
    };
    console.log(formData['uploaded_images'])
    axios
    .post('api/v1/posts/create/', formData, {
        headers: {
            "Content-Type": "multipart/form-data",
            "X-CSRFToken": "{{csrf-token}}"
        }
    })
    .then((response) => {
        if(response.status == 201){
            store.messages.push("Post created successfully")
        }
    })
    .catch((error) => {
        messages.value.items.push(error.message)
    })
}

当我发布数据时,我在服务器端看到的响应是:

uploaded_data = validated_data.pop('uploaded_images')
KeyError: 'uploaded_images'

来自这个序列化程式:

class PostImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = PostImage
        fields = ['image', 'post']

class PostSerializer(serializers.ModelSerializer):
    images = PostImageSerializer(many=True, read_only=True, required=False)
    uploaded_images = serializers.ListField(required=False, child=serializers.FileField(max_length=1000000, allow_empty_file=False, use_url=False),write_only=True)

    class Meta:
        model = Post
        fields = [
            "category", 
            "body",
            "images",
            "uploaded_images",
            "video",
            "can_view",
            "can_comment",         
            "user",
            "published",
            "pinned",
            "created_at",
            "updated_at",
        ]
    
    def create(self, validated_data):

        uploaded_data = validated_data.pop('uploaded_images')
        new_post = Post.objects.create(**validated_data)
        try:
            for uploaded_item in uploaded_data:
                PostImage.objects.create(post = new_post, images = uploaded_item)
        except:
            PostImage.objects.create(post=new_post)
        return new_post

尝试理解这一点,那么我的想法是正确的吗?DRF在数据发送到端点时保存序列化程序?我假设变量validated_data是request.data对象?那么为什么我会得到KeyError?我如何才能看到正在验证的数据是什么?或者在服务器端的post请求中发送。在浏览器中post请求中发送的数据如下所示:

-----------------------------2091287168172869498837072731
Content-Disposition: form-data; name="body"

Post
-----------------------------2091287168172869498837072731
Content-Disposition: form-data; name="can_view"

Everybody
-----------------------------2091287168172869498837072731
Content-Disposition: form-data; name="can_comment"

Everybody
-----------------------------2091287168172869498837072731
Content-Disposition: form-data; name="uploaded_images.0"; filename="tumblr_42e2ad7e187aaa1b4c6f4f7e698d03f2_c9a2b230_640.jpg"
Content-Type: image/jpeg

-----------------------------2091287168172869498837072731
Content-Disposition: form-data; name="body"

Post
-----------------------------2091287168172869498837072731
Content-Disposition: form-data; name="can_view"

Everybody
-----------------------------2091287168172869498837072731
Content-Disposition: form-data; name="can_comment"

Everybody
-----------------------------2091287168172869498837072731
Content-Disposition: form-data; name="uploaded_images.0"; filename="tumblr_42e2ad7e187aaa1b4c6f4f7e698d03f2_c9a2b230_640.jpg"
Content-Type: image/jpeg

(¼T¼Þ7ó[®«ý;>7гô
eIqegy[XbkéÉc¤ÎSFÌÔÂåÄAR§*P!I<R,4AP9ÖgÅÖYÔ×éu«ÅÉ<IJª+`,.uòÜtK7xéu.Ô¬]{ù£æÍ÷·n²±×:îã¡`UÐKxªyjxñDUAP¢+ÄÅB1yõçùuS5å
D÷ zö4®n¦Öod&<z¼P
W9©xeúD5ÈMpÖö¬ðÓKÊľO«oµÊMçÇy|z=^<AKêôz¼x##:ù;«OdÞ¢¶WRùººRêÜêú8ø¡ãÄ"¼AãÅj¿3ÆõÙRÆ]_MTÆ^;;
`ttR}mì¤*bêwy¾=d<xòøòxÄ(

以下是位于端点的ViewSet:

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    filter_backends = [django_filters.rest_framework.DjangoFilterBackend, filters.SearchFilter, django_filters.rest_framework.OrderingFilter]
    # filterset_class = PostFilter
    ordering_fields = ['created_at',]
    search_fields = ['category', 'body']
    permission_classes = [permissions.IsAuthenticated]
    def get_serializer_context(self):
        return {'request': self.request}
    
    parser_classes = [MultiPartParser, FormParser]
    lookup_field = 'slug'
djmepvbi

djmepvbi1#

因此,经过几个小时的研究,我找到了自己的解决方案。用于读取多个文件的方法就是从这个answer中获得的。通过将[对象FileList]分解为单独的文件并将它们附加到FormData中。模型就是基于这个answer
在后端,重写序列化程序的create方法,并循环访问resquest.POST.data,排除不需要的键,以访问文件。并将它们保存到Images模型中**(应命名为PostImage)**。
注意,我不访问文件的validated_data,而是直接从请求中检索它们。
我在前端使用了bootstrap5。
编辑:仅测试了两种类型的请求GET(列表)和POST(创建)(如您在vue组件中所见)
models.py:

class Post(models.Model):
    title = models.CharField(max_length=128)
    body = models.CharField(max_length=400)
  
def get_image_filename(instance, filename):
    title = instance.post.title
    slug = slugify(title)
    return "post_images/%s-%s" % (slug, filename)  

class Images(models.Model):
    post = models.ForeignKey(Post, default=None, on_delete=models.CASCADE)
    image = models.ImageField(upload_to=get_image_filename,
                              verbose_name='Image')

serializers.py:

from core.models import Images, Post
from rest_framework import serializers

class PostSerializer(serializers.ModelSerializer):
    images = serializers.SerializerMethodField()

    class Meta:
        model = Post
        fields = '__all__'

    def create(self, validated_data):
        new_post = Post.objects.create(**validated_data)
        data = self.context['request'].data
        for key, image in data.items():
            if key != 'title' and key != 'body':
                image = Images.objects.create(post=new_post, image=image)

        return new_post
    
    def get_images(self, obj):
        images = []
        qs = Images.objects.filter(post=obj)
        for item in qs:
            images.append(item.image.name)
        return images

views.py:

from rest_framework import viewsets
        
from core.models import Post
from core.serializers import PostSerializer

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

TestComponent.vue:

<template>
    <div class="container" style="display: flex; justify-content: center; align-items: center;">
        <form @submit.prevent="submit" >
            <div class="mb-3">
                <label for="exampleInputTitle" class="form-label">Title</label>
                <input type="text" class="form-control" id="exampleInputTitle" v-model="title">                 
            </div>

            <div class="mb-3">
                <label for="exampleInputBody" class="form-label">Body</label>
                <input type="text" class="form-control" id="exampleInputBody" v-model="body">                 
            </div>

            <div class="mb-3">
                <label for="formFileMultiple" class="form-label">Multiple files input example</label>
                <input class="form-control" type="file" id="formFileMultiple" ref="file" multiple>
            </div>

            <div>
                <button type="submit" class="btn btn-primary">Submit</button>
            </div>
        </form>
    </div>
</template>

<script>

import axios from 'axios'

export default {
    data () {
        this.title = '',
        this.body = ''
    },
    methods: {
        submit() {
            const formData = new FormData();
            for( var i = 0; i < this.$refs.file.files.length; i++ ){
                let file = this.$refs.file.files[i];
                formData.append('files[' + i + ']', file);
            }
            formData.append("title", this.title);
            formData.append("body", this.body);

            axios.post('http://localhost:8000/posts/', formData, {
                    headers: {
                    'Content-Type': 'multipart/form-data'
                    }
                })
                .then((response) => {
                    console.log(response.data);
                })
                .catch((error) => {
                    console.log(error.response);
                });
        }
    },
    mounted() {
        axios.get('http://localhost:8000/posts/')
            .then((response) => {
                console.log(response.data);
            })
            .catch((error) => {
                console.log(error.response);
            });
    }
}
</script>

相关问题