Django Rest Framework – nested serializer is not working like expected or how the docs explains

Question:

I have a post model and i have a post image model. I would like to show the images related to a post in the API, but I cannot seem to get it right.I only see the post data and not the related images.

Here are my post and post image models:

class Post(models.Model):

    EV = "Everybody"
    FO = "Followers"
    FR = "Friends"
    AUDIENCE = [
        (EV, "Everybody"),
        (FO, "Followers"),
        (FR, "Friends"),
    ]
    category = models.ForeignKey(Category, on_delete=models.SET_DEFAULT, default=1)
    body = models.TextField("content", blank=True, null=True, max_length=5000)
    slug = AutoSlugField(populate_from=["category", "created_at"])
    video = models.FileField(upload_to=user_directory_path, null=True, blank=True)
    can_view = models.CharField(max_length=10, choices=AUDIENCE, default=EV)
    can_comment = models.CharField(max_length=10, choices=AUDIENCE, default=EV)
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, verbose_name="user", related_name="user"
    )
    published = models.BooleanField(default=False)

    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


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

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

    def image_tag(self):
        return mark_safe(
            '<img src="/storage/%s" width="50" height="50" />' % (self.image)
        )

    image_tag.short_description = "Image"

This is the post and post images serializers:

class PostImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = PostImage
        fields = ["id", "image", "post"]
        extra_kwargs = {
            "post": {"required": True},
        }


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

    class Meta:
        model = Post
        fields = [
            "id",
            "can_view",
            "can_comment",
            "category",
            "body",
            "images",
            "video",
            "user",
            "published",
            "created_at",
            "updated_at",
        ]
        read_only_fields = ['category', 'user']

    def create(self, validated_data):
        user = User.objects.get(id=self.context['request'].data.get('user'))
        category = Category.objects.get(id=self.context['request'].data.get('category'))
        new_post = Post.objects.create(**validated_data, category=category, user=user)
        images = dict((self.context['request'].FILES).lists()).get('images', None)
        if images:
            for image in images:
                PostImage.objects.create(
                    image=image, post=new_post
                )
        return new_post

The post gets created fine and the images related to the post as well but if I go to the API end point to look at the post’s data, I see all the fields but not the nested field with the images. What am I doing wrong please? How should you nest related fields otherwise?

Asked By: EagerLearner

||

Answers:

A minor mistake, it’s just a wrong reference. Change the field related name in models.py:

class PostImage(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="images")
    ...

To add a the user profile, you can use the user field to retrieve the profile and then exclude it from the serializer while replacing it.

There is no need to add profile to read-only because SerializerMethodField already has that property by default:

class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = '__all__'

class PostSerializer(serializers.ModelSerializer):
    images = PostImageSerializer(many=True, read_only=True, required=False)
    profile = serializers.SerializerMethodField()

    class Meta:
        model = Post
        exclude = ['user']
        read_only_fields = ['category']
    
    def get_profile(self, obj):
        return UserProfileSerializer(obj.user.profile).data

    def create(self, validated_data):
        ...
        return new_post
Answered By: Niko