Как передать Django InMemoryUploadFile в сериализатор в случае с изображениями?

Я создаю API с использованием Django Rest Framework для сохранения изображений из файла напрямую или из url. С первым случаем у меня проблем нет, а вот со вторым есть. Итак, мои модели выглядят следующим образом:

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

Далее, мой класс сериализатора:

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

Основная идея заключается в том, что если вы передаете URL во время пост запроса, API должен загрузить изображение с этого URL и сохранить его в базе данных.

Для этого я переписал create метод в классе Django Rest Framework ModelViewSet:

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)

Но когда я использовал Django shell, я обнаружил, что сериализатор не сериализует InMemoryUploadFile, только URL:

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

Но если я буду сохранять картинку через модель напрямую, то все будет нормально. Вот код, как я это делаю в конце файла views.py:

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

Итак, я хочу знать, в чем проблема и как я могу ее решить?

Я нашел эту ссылку:

Поврежденное изображение для сериализатора

Итак, когда я добавил

...
django_file.seek(0)

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

ваш код сработал на моей машине.

Вернуться на верх