[Vuejs]-Vue3 frontend, Django back end. Key error for validated data in serializer

0๐Ÿ‘

So, after a few hours of research I was able to find my own solution. The method used to read multiple files, was taken from this answer. By breaking the [object FileList] into separate files and appending them to the FormData. The models are based on this answer

On the backend, overriding the create method of the serializer and loop through resquest.POST.data excluding unwanted keys to access the just the files. And saving them into the Images model (should be named PostImage).

Note that I do no access the validated_data for the files, instead they are retrieved directly from the request.

I used bootstrap5 in the frontend.

EDIT: Tested only two types of request GET(list) and POST(create) (as you see in vue component)

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>

0๐Ÿ‘

Hope this helps someone.

Finally after three days battling with this I found the solution to my issue. In the models I have this function that generates a string I can use as the upload_to string for the PostImage:

def post_directory_path(instance, filename):
    return 'user_{0}/posts/post_{1}/{2}'.format(instance.user.id, instance.post.id, filename)

There is no user instance on the PostImage only on the Post and Django does not not throw an exception or show any errors for this mistake, which is why I did not look for the problem there.

Leave a comment