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.

MaxRah
  • 243
  • 1
  • 6
  • 21

1 Answers1

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')
    # ...
SonnyStar
  • 51
  • 7
  • Thank you for this answer. Option 1 looks the easiest but it didn't work. However, option 2 worked. Thanks again – MaxRah Jul 31 '20 at 08:16
  • @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! – SonnyStar Jul 31 '20 at 17:52