Getting a KeyError for a serializer and don't know why

I have a Vue front end that sends data to a Django Rest Framework endpoint. Here is the console log of the data object being sent:

Object { category: undefined, body: "", can_view: "Everybody", can_comment: "Everybody", video: Proxy, uploaded_images: Proxy, user: 1 }

Here is the ViewSet that sits at the endpoint:

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'

Here are the serializers:

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

Why am I getting this error? Is uploaded_images not part of the validated_data object? Isn’t that what comes from the front end?

How can I see what the validated_data variable contains pls?

Please include all errors (error message and stacktrace) for this flow.

Also confirm what is sent over the wire for this request via your browser’s Developer Tools’ Network panel. What values are being sent?

Thank you for responding.

This is what is sent to the DRF endpoint from the frontend:

-----------------------------404193895316728380273212347628
Content-Disposition: form-data; name="category"

1
-----------------------------404193895316728380273212347628
Content-Disposition: form-data; name="body"

post 1
-----------------------------404193895316728380273212347628
Content-Disposition: form-data; name="can_view"

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

Everybody
-----------------------------404193895316728380273212347628
Content-Disposition: form-data; name="user"

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

ÿØÿà

This is the Traceback:

Internal Server Error: /api/v1/posts/create/
Traceback (most recent call last):
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/mixins.py", line 19, in create
    self.perform_create(serializer)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/mixins.py", line 24, in perform_create
    serializer.save()
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/serializers.py", line 212, in save
    self.instance = self.create(validated_data)
  File "/mnt/500GB/calvin/business/projects/Current/addapost/website/backend/posts/serializers.py", line 50, in create
    uploaded_data = validated_data.pop('uploaded_images')
KeyError: 'uploaded_images'
Internal Server Error: /api/v1/posts/create/
Traceback (most recent call last):
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/mixins.py", line 19, in create
    self.perform_create(serializer)
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/mixins.py", line 24, in perform_create
    serializer.save()
  File "/home/calvin/.local/share/virtualenvs/website-OCaWupbT/lib/python3.8/site-packages/rest_framework/serializers.py", line 212, in save
    self.instance = self.create(validated_data)
  File "/mnt/500GB/calvin/business/projects/Current/addapost/website/backend/posts/serializers.py", line 50, in create
    uploaded_data = validated_data.pop('uploaded_images')
KeyError: 'uploaded_images'

I’m trying to understand the logic so please bear with me.
This

validated_data.pop('uploaded_images')

removes the last item from a list which in this case would be uploaded_images and I know it must be done so a new Post model can be created without the uploaded_images data. But then if it is removed from the list then this part below cannot function because uploaded images is no longer in the list?

if I remove this part from the serializer’s create method:

        try:
            for uploaded_item in uploaded_data:
                PostImage.objects.create(post = new_post, image = uploaded_item)
        except:
            PostImage.objects.create(post=new_post)
        return new_post

Then the post gets created so i assume the data is valid and the serializer works correctly?
If you look at the data being sent then you will see uploaded_images is the last item BUT I see every uploaded_image has a .0 for the first and .1 for the second and so on appended to the end. Can that be the issue? Why is the 0 and 1 appended and why is it not showing it as [0] since it’s an array?

Your help is appreciated very much

I changed the create part of the serializer to this

    def create(self, validated_data):
        uploaded_data = validated_data.pop('uploaded_images')
        new_post = Post.objects.create(**uploaded_data)
        images = validated_data('uploaded_images')
        for image in images:
            PostImage.objects.create(image=image, post=new_post)
        return new_post

because it makes more sense to me. I still get the KeyError

if I do it like this:

    def create(self, validated_data):
        if 'uploaded_images' in validated_data:
            uploaded_data = validated_data.pop('uploaded_images')
            new_post = Post.objects.create(**uploaded_data)
            images = validated_data('uploaded_images')
            for image in images:
                PostImage.objects.create(image=image, post=new_post)
            return new_post
        else:
            new_post = Post.objects.create(**validated_data)
            return new_post

then the post gets created. Why would the uploaded_images key not be in the dictionary? Does images and uploaded_images have to be fields on the model as well?

I’m now one step further and I changed the serializer code to this:

    def create(self, validated_data):
        request = self.context.get("request")
        new_post = Post.objects.create(**validated_data)
        images = request.FILES
        if images:
            try:
                for image in images:
                    PostImage.objects.create(image=image, post=new_post)
            except:
                PostImage.objects.create(post=new_post)
            return new_post

Now the posts gets saved and the images associated with the post gets created as well but somehow the files is not save in the right place. Because if I click on it then it says:

storage/uploaded_images.2” does not exist

and there are no files saved in the backend in the path I specify on the model:

class PostImage(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='images')
    image = models.FileField(upload_to="posts/images", default='posts/default.png', null=True)

    class Meta:
        db_table = "post_images"
        ordering = ["post"]

This has really been a frustration because I have followed many people and how they did it and it just does not work. Why are the files not being saved in the correct path?

Use request.FILES.getlist('uploaded_images') to get the list of files.

Does not work and the error I now get is AssertionError: create() did not return an object instance.

My question again.

Why when the PostImages are created the name of the image is /storage/uploaded_images.3”

so when I want to look at the image I get not found. It returns the post like that as well and no files or images are saved in the backend. Really don’t understand why the name has a 0 appended to it because that I believe is what the issue is. What in Django is saving the name of the file like that.

Selection_004

The reason I suggested you change your approach on grabbing the files from request.FILES is because request.FILES is a dictionary-like object. When you iterate over a dictionary like you are with for file in request.FILES, file will be the key, not the value.

Does not work and the error I now get is AssertionError: create() did not return an object instance.

You should always include the full error message and stacktrace when looking for help with an error.

The best way to understand what your code is doing something you don’t understand is to use a debugger. You can drop a breakpoint() in your code, then walk through the code. Or you can use an IDE’s debugger.

Ok thank you I will go through the code again and make changes and see if I can make it work

Hi

I changed the a lot of things and I am now at the point where it looks like I might win. This is the create function of my post serializer:

    def create(self, validated_data):
        request = self.context.get("request")
        new_post = Post.objects.create(**validated_data)
        images = dict((self.context['request'].FILES).lists()).get('files', None)
        breakpoint()
        if images:
            try:
                for image in images:
                    PostImage.objects.create(image, post=new_post)
            except:
                PostImage.objects.create(post=new_post)
        return new_post

and this is the output of the breakpoint for the files:

(Pdb) images
[<InMemoryUploadedFile: tumblr_b1726621a367fe017a73edb996ca0222_9b52593b_540.jpg (image/jpeg)>, <InMemoryUploadedFile: tumblr_ed996cac6a5338bc6393b454ae22388e_9f94c3e7_540.jpg (image/jpeg)>, <InMemoryUploadedFile: tumblr_42e2ad7e187aaa1b4c6f4f7e698d03f2_c9a2b230_640.jpg (image/jpeg)>, <InMemoryUploadedFile: tumblr_3950cb16d9df608490ca518d19a3d15a_5fa519bd_540.jpg (image/jpeg)>]

But I cannot iterate over the list for some reason. It does not create the PostImages for me, instead it just creates a post with the default image. Would appreciate a nudge in the right direction pls.

At least part of the problem is the blanket try / except block. That’s an anti-pattern and hides useful information. If you’re only seeing posts with the default image, it’s likely because an exception is being raised, caught the the blanket except and then creating the PostImage with PostImage.objects.create(post=new_post)

PostImage.objects.create(image, post=new_post) is also likely invalid. You should be specifying the field name for the image argument, not trying to rely on a positional argument. You can confirm that this is the cause of your problems by removing the try/except and seeing what the error is first.

Thank you for the advice it helps me a lot. I actually found the problem and it is the function that generates a path string for me to use in the upload_to attribute of the PostImage model. I was trying to get the user id from the instance but that does not exist. So because I have a try except block as you just stated it does not give me any errors and so I never bothered to look there. Progress so thanks again