Home > front end >  How to pass Django InMemoryUploadFile to serializer in case with images?
How to pass Django InMemoryUploadFile to serializer in case with images?

Time:01-03

I'm creating an API using Django Rest Framework to save images from file directly or from url. I've got no problem with first case but with second. So, my models looks like this:

class Image(models.Model):
    picture = models.ImageField(upload_to="site_media", blank=True)
    url = models.URLField(null=True, blank=True, max_length=255)
    parent_picture = models.IntegerField(null=True, blank=True)


    @property
    def name(self):
        return os.path.split(self.picture.name)[-1]

    @property
    def width(self):
        return self.picture.width

    @property
    def height(self):
        return self.picture.height

Next, my serializer class:

class ImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Image
        fields = [
            'id',
            'name',
            'url',
            'picture',
            'width',
            'height',
            'parent_picture'
        ]

The main idea is if you pass the URL while post request, API needs to download image from this URL and store it in database.

To do this I overwrote create method in Django Rest Framework ModelViewSet class:

from io import BytesIO
import urllib
import os

from django.core.files.uploadedfile import InMemoryUploadedFile
from PIL import Image as PILImage
from rest_framework import status
from rest_framework import viewsets
from rest_framework.response import Response

from .models import Image
from .serializers import ImageSerializer

    
class ImageViewSet(viewsets.ModelViewSet):
    serializer_class = ImageSerializer
    queryset = Image.objects.all()
    
    def create(self, request, *args, **kwargs):
        if request.data["url"]:
            url = request.data["url"]

            image_extensions = {
                ".jpg": "JPEG",
                ".jpeg": "JPEG",
                ".png": "PNG",
                ".gif": "GIF"
            }

            image_path = urllib.parse.urlparse(url).path
            image_filename = image_path.split("/")[-1]
            extension = os.path.splitext(image_filename)[-1]

            image_bytes = BytesIO(urllib.request.urlopen(url).read())
            pillow_image = PILImage.open(image_bytes)
            
            buffer = BytesIO()
            pillow_image.save(buffer, format=image_extensions[extension])

            django_file = InMemoryUploadedFile(
                buffer, 
                "ImageField", 
                image_filename, 
                "image/" extension[1:],
                len(buffer.getvalue()),
                None
            )

            serializer = self.get_serializer(data={"url": url, "picture": django_file})
            serializer.is_valid(raise_exception=True)
            self.perform_create(serializer)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

But when I used Django shell I found that serializer doesn't serialize InMemoryUploadFile, only URL:

>>>django_file = InMemoryUploadFile(...)
>>>serializer = ImageSerializer(data={"url": url, "picture": django_file})
>>>serializer.data
{'url': 'some url', 'picture': None, 'parent_picture': None}  

But if I will save picture through the model directly, all will be okay. Here is the code how I do this in the end of views.py file:

Image.objects.create(url=url, picture=django_file)
return Response("success!")

So, I want to know what is the problem and how can I fix it?

CodePudding user response:

I found this link:

Pillow corrupt image for serializer

So, when I added

...
django_file.seek(0)

serializer = self.get_serializer(data={"url": url, "picture": django_file})
...

your code worked on my machine.

  • Related