0

I wanted to upload multiple images in a Post model by following this answer.

As Admin, I can successfully upload multiple images to the certificates fields but when I view the post detail, or post list pages in browser, the certificates do not show.

Here is the json:

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": 1,
            "title": "Post One",
            "description": "Lorem ipsum",
            "url": "http://www.postone.com",
            "country": "United Kingdom",
            "coverImage": "http://localhost:8000/media/cover_images/Post%20One/earth-large_yPJZXAH.jpg",
            "tags": [
                "UK"
            ],
            "creator": "Admin",
            "slug": "post-one"
        }
    ]
}

For some reason, the certificate images that were uploaded in Admin are not showing on the post details or list pages.

Here are the models:

class Post(models.Model):
    creator = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='posts')
    title = models.CharField(_('Title'), max_length=255, blank=False, null=False)
    description = models.TextField(_('Description'), max_length=500, blank=False, null=False)
    url = models.URLField(_('URL'), unique=True, max_length=255, blank=False, null=False)
    country = models.CharField(_('Country'), max_length=255, blank=False, null=False)
    cover_image = ProcessedImageField(
        verbose_name=_('Cover'),
        blank=False,
        null=False,
        format='JPEG',
        options={'quality': 90},
        processors=[ResizeToFill(600, 200)],
        upload_to=cover_image_directory)

    STATUS_DRAFT = 'D'
    STATUS_PUBLISHED = 'P'
    STATUSES = (
        (STATUS_DRAFT, 'Draft'),
        (STATUS_PUBLISHED, 'Published'),
    )
    status = models.CharField(blank=False, null=False, choices=STATUSES, default=STATUS_DRAFT, max_length=2)
    tags = TaggableManager(blank=True)
    slug = models.SlugField(max_length=255, unique=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)


    def save(self, *args, **kwargs):
        if self.slug:  # edit
            if slugify(self.title) != self.slug:
                self.slug = generate_unique_slug(Post, self.title)
        else:  # create
            self.slug = generate_unique_slug(Post, self.title)
        super(Post, self).save(*args, **kwargs)

    def get_tags(self):
        """ names() is a django-taggit method, returning a ValuesListQuerySet
        (basically just an iterable) containing the name of each tag as a string
        """
        return self.tags.names()

    def __str__(self):
        """Return post title"""
        return self.title

    def get_absolute_url(self):
        return reverse('posts:detail', kwargs={'slug': self.slug})

    def is_draft(self):
        return self.status == STATUS_DRAFT
    class Meta:
        ordering = ['-created_at',]


class PostCertificateImage(models.Model):
    post = models.ForeignKey(Post, default=None, on_delete=models.CASCADE)
    image = ProcessedImageField(
        verbose_name=_('Certificate'),
        blank=True,
        null=True,
        format='JPEG',
        options={'quality': 90},
        processors=[ResizeToFill(100, 100)],
        upload_to=certificate_directory)

    def __str__(self):
        return self.post.title

The serializer:

class PostCertificateImageSerializer(serializers.ModelSerializer):

    class Meta:
        model = PostCertificateImage
        fields = ('image',)


class PostSerializer(serializers.ModelSerializer):
    tags = TagSerializer(source='get_tags')
    creator = CreatorField(queryset=User.objects.all())
    certificate_images = PostCertificateImageSerializer(many=True, read_only=True)

    class Meta:
        model = Post
        fields = (
            'id',
            'title',
            'description',
            'url',
            'country',
            'certificate_images',
            'cover_image',
            'tags',
            'creator',
            'slug',
        )

And the views:

class PostList(generics.ListAPIView):
    queryset = Post.objects.filter(status='P').order_by('-created_at')
    serializer_class = PostSerializer
    permission_classes = (permissions.AllowAny,)

    def get_queryset(self, *args, **kwargs):
        queryset = Post.objects.filter(status='P').order_by('-created_at')
        return queryset


class PostDetail(generics.RetrieveAPIView):
    queryset = Post.objects.filter(status='P')
    serializer_class = PostSerializer
    permission_classes = (permissions.AllowAny,)

I understand that the answer to this question isn't that old. The code appears to run fine but I just can't see the certificate images.

1 Answer 1

1

If you do not specify the source argument for a serializer field, it tries to use the data from the attribute with the same name (reference). The issue here is that the certificate_images serializer field tries to use the attribute named certificate_images, but doesn't find it (the Post model does not have an attribute called certificate_images).

To overcome this problem, you have two options:

  1. Set the correct source for the certificate_images serializer field, or
  2. Set a custom related_name for the post field of the PostCertificateImage model.

Let's start with the first option. The default related_name for the post field is postcertificateimage_set (reference). To use that as the source, you'll need to add source='postcertificateimage_set' to the serializer field arguments, so the serializer field will use the PostCertificateImage objects that are related to the deserialized object:

class PostSerializer(serializers.ModelSerializer):
    # ...
    certificate_images = PostCertificateImageSerializer(many=True, read_only=True, source='postcertificateimage_set')

The second option is to set a custom related_name on the ForeignKey field of the PostCertificateImage model. This way, the serializer field will be able to find the certificate_images attribute. However, doing this has a downside: you will end up with a new database migration (reference). Here is the code, if you go with this option:

class PostCertificateImage(models.Model):
    post = models.ForeignKey(Post, default=None, on_delete=models.CASCADE, related_name='certificate_images')
    # ...
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for this answer. Option 1 looks the easiest but it didn't work. However, option 2 worked. Thanks again
@MaxRah I am sorry, I forgot to add the source='postcertificateimage_set' argument in the code for option #1. Fixed now. I tested locally and it works fine. Cheers!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.