Cannot save images to a post

This has been a nightmare for me. Many tutorials on how to upload multiple images to Django but I cannot find one that shows you how to add multiple images to a model with a foreign key to a post. Please tell me what I am doing wrong.

Post model:


from django.db import models
from django_extensions.db.fields import AutoSlugField
from django.contrib.auth import get_user_model

User = get_user_model()
class Post(models.Model):
    name = models.CharField(max_length=150, verbose_name="event name")
    category = models.ForeignKey(Category, on_delete=models.SET_DEFAULT, default=1)
    slug = AutoSlugField(populate_from=["category", "created_at", "user"])
    body = models.TextField("content", blank=True, null=True, max_length=5000)
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, verbose_name="author", related_name="posts"
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = "post"
        verbose_name_plural = "posts"
        db_table = "posts"
        ordering = ["created_at"]

    def __str__(self):
        return self.body[0:30]

    def get_absolute_url(self):
        return self.slug

Post image model:

class PostImage(models.Model):
    image = models.FileField(upload_to=user_directory_path)
    post = models.ForeignKey(Post, on_delete=models.DO_NOTHING)

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

Post serializer:

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = [
            "category", 
            "body",
            "video",
            "can_view",
            "can_comment",         
            "user",
            "published",
            "pinned",
            "created_at",
            "updated_at",
        ]

Image serializer:

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

the view to create the post:

class PostCreate(APIView):

    def post(self, request, format=None):
        image_serializer = PostImageSerializer(data=request.FILES)
        if(image_serializer.is_valid()):
            for image in image_serializer:
                PostImage.objects.create(image=image, post=self)
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=HTTP_201_CREATED)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

I use Vue as the front end and the data sent to the backend is fine and it shows the images with the encoding and the fields.

How do I save the image and attach it to the post using the post ID

Please help

Looking at this code, this does not seem valid. Are you getting any errors?

Ok why do you say it is not valid? I am not getting errors no

My problem is this

post=self

How do I get the instance ID so I can create a PostImage?

Because you are passing a serializer (self) and a serializer field (image) to the PostImage.objects.create method.

The instance id of the post, will only be available once you saved that post.
This is only happening here:

After you do serializer.save() the serializer will have a instance of your Post on an attribute called instance. The save method also returns that instance, so you can grab the instance using post = serializer.save()

So like this:

    def post(self, request, format=None):
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            for file in request.FILES:
                PostImage.objects.create(image=file, post=self.instance)
            return Response(serializer.data, status=HTTP_201_CREATED)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

You’re almost there!

Look at this block of code:

On this context self refers to the View class, not the Serializer.

This is a off-topic opinion.

Normally when i write my DRF views, i don’t do if serializer.is_valid() like you’re doing here.

the serializer.is_valid method has a optional argument raise_exception that when set to True does exactly this:

So if you prefer using this, you can simplify your view to this:

    def post(self, request, format=None):
        serializer = PostSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        for file in request.FILES:
            PostImage.objects.create(image=file, post=self.instance)
        return Response(serializer.data, status=HTTP_201_CREATED)

I haven’t changed the code from the last comment

My problem is this:

    def post(self, request, format=None):
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            for file in request.FILES:
                image_serializer = PostImageSerializer(file)
                PostImage.objects.create(image=image_serializer, post=serializer.id)
            return Response(serializer.data, status=HTTP_201_CREATED)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

After the Post serializer is saved I have a post right or wrong?

If I do then how do I get the id of that post because that is all I need to create the images I want to create, but for the life of me for the last 5 hours I cannot get someone to tell me that. Is it serializer.id or is is serializer.post.id or post.id or serializer.instance.id what?

The first thing is: keep calm. Coding is always a constant learning, and learning from your mistakes. You’re getting any far if you keep frustrating yourself.
Also, this is a public forum, almost nobody here is getting paid to answer you, so be kind.

I’m trying to help you understand the concepts that you’re using, this way on the future you’ll be able to solve these issues more easily. If i just gave you the answer, you didn’t learned anything.

So again, let’s recap…

serializer.save() will populate a instance of Post. That instance contains all the fields of your Post model, including the id or pk. So you have to access these attributes from the instance, not the serializer.

Right:
serializer.instance.some_field

Wrong:
serializer.some_field

Ok sorry it really wasn’t my intention to come across as anything but desperate and believe me I really appreciate any help I am getting, so please don’t think I don’t.

So I changed the code to this:

    def post(self, request, format=None):
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            for file in request.FILES:
                image_serializer = PostImageSerializer(file)
                PostImage.objects.create(image=image_serializer, post=serializer.instance)
            return Response(serializer.data, status=HTTP_201_CREATED)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

After trying to save I get an error:

AttributeError: 'PostImageSerializer' object has no attribute '_committed'

Alright!
Seeing errors is always good, because they lead us to a possible solution.

This error:

Is coming from this line:

The image field on your PostImage expects a file field. But you’re passing the serializer instead.

It’s been a while since i upload a file using DRF. But you can try to pass the file directly, so there’s no need to pass the file to the PostImageSerializer. Like this:

            for file in request.FILES:
                # image_serializer = PostImageSerializer(file) This is not needed
                PostImage.objects.create(image=file, post=serializer.instance)

I have this function to save the images to a specific directory:

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

I changed the code of my post save view:

    def post(self, request, format=None):
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            for file in request.FILES:
                PostImage.objects.create(image=file, post=serializer.instance)
            return Response(serializer.data, status=HTTP_201_CREATED)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

and now the post images gets saved but not the files and it gives me a path that is not right,

Why does the request payload look like this:

A©¢ùlUJ@(Kó«æ‰¯=:Rt"œ2ëno5›¥Ûg:±GÛzòÖ0H@ӄϢ’²™P†ƒf¥´E$XƒSŒA`

This seems to be related to the file upload part. In fact, you’re going to need the serializer.
Please review this topic from the documentation.

Also, this stackoverflow thread has a lot of possible solutions, read that carefully, since there’s a lot of answer from the “ancient era” of django.

As a said, it’s been a while since i played with file uploads. Maybe someone else can help you more on this specific part.