Upload image to Digital Ocean fails due to InMemoryUploadedFile not supported

Hi,

I’m attempting to upload an image from a Viewset to a Digital Ocean space, and getting the following error and would appreciate any help I can get to resolve this:

RuntimeError: Input marvel.jpg of type: <class 'django.core.files.uploadedfile.InMemoryUploadedFile'> is not supported.

Model:

from sorl.thumbnail import ImageField

class Publisher(CommonInfo):
    founded = models.PositiveSmallIntegerField("Year Founded", null=True, blank=True)
    image = ImageField("Logo", upload_to="publisher/%Y/%m/%d/", blank=True)
    attribution = GenericRelation(Attribution, related_query_name="publishers")
    edited_by = models.ForeignKey(CustomUser, default=1, on_delete=models.SET_DEFAULT)

Viewset:

class PublisherViewSet(
    mixins.CreateModelMixin,
    mixins.UpdateModelMixin,
    mixins.ListModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet,
):
    queryset = Publisher.objects.prefetch_related("series_set")
    filterset_class = NameFilter
    throttle_classes = (GetUserRateThrottle, PostUserRateThrottle)

    def get_serializer_class(self):
        match self.action:
            case "list":
                return PublisherListSerializer
            case "series_list":
                return SeriesListSerializer
            case _:
                return PublisherSerializer

    def get_permissions(self):
        permission_classes = []
        if self.action in ["create", "update", "partial_update"]:
            permission_classes = [IsAdminUser]
        elif self.action in ["retrieve", "list", "series_list"]:
            permission_classes = [IsAuthenticated]
        return [permission() for permission in permission_classes]

    def perform_create(self, serializer: PublisherSerializer) -> None:
        serializer.save(edited_by=self.request.user)
        return super().perform_create(serializer)

Serializer:

class PublisherSerializer(serializers.ModelSerializer):
    def create(self, validated_data):
        """
        Create and return a new `Publisher` instance, given the validated data.
        """
        return Publisher.objects.create(**validated_data)

    def update(self, instance: Publisher, validated_data):
        """
        Update and return an existing `Publisher` instance, given the validated data.
        """
        instance.name = validated_data.get("name", instance.name)
        instance.founded = validated_data.get("founded", instance.founded)
        instance.desc = validated_data.get("desc", instance.desc)
        instance.image = validated_data.get("image", instance.image)
        instance.save()
        return instance

    class Meta:
        model = Publisher
        fields = ("id", "name", "founded", "desc", "image", "modified")

Traceback:

Traceback (most recent call last):
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/rest_framework/mixins.py", line 19, in create
    self.perform_create(serializer)
  File "/home/bpepple/Git/metron/comicsdb/views/viewsets.py", line 345, in perform_create
    return super().perform_create(serializer)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/rest_framework/mixins.py", line 24, in perform_create
    serializer.save()
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/rest_framework/serializers.py", line 207, in save
    self.instance = self.update(self.instance, validated_data)
  File "/home/bpepple/Git/metron/comicsdb/serializers.py", line 443, in update
    instance.save()
  File "/home/bpepple/Git/metron/comicsdb/models/publisher.py", line 35, in save
    return super(Publisher, self).save(*args, **kwargs)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/django/db/models/base.py", line 812, in save
    self.save_base(
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/django/db/models/base.py", line 863, in save_base
    updated = self._save_table(
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/django/db/models/base.py", line 967, in _save_table
    values = [
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/django/db/models/base.py", line 971, in <listcomp>
    (getattr(self, f.attname) if raw else f.pre_save(self, False)),
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/django/db/models/fields/files.py", line 316, in pre_save
    file.save(file.name, file.file, save=False)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/django/db/models/fields/files.py", line 92, in save
    self.name = self.storage.save(name, content, max_length=self.field.max_length)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/django/core/files/storage.py", line 56, in save
    name = self._save(name, content)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/storages/backends/s3boto3.py", line 445, in _save
    obj.upload_fileobj(content, ExtraArgs=params, Config=self._transfer_config)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/boto3/s3/inject.py", line 725, in object_upload_fileobj
    return self.meta.client.upload_fileobj(
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/boto3/s3/inject.py", line 636, in upload_fileobj
    return future.result()
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/s3transfer/futures.py", line 103, in result
    return self._coordinator.result()
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/s3transfer/futures.py", line 266, in result
    raise self._exception
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/s3transfer/tasks.py", line 269, in _main
    self._submit(transfer_future=transfer_future, **kwargs)
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/s3transfer/upload.py", line 579, in _submit
    upload_input_manager = self._get_upload_input_manager_cls(
  File "/home/bpepple/.local/share/virtualenvs/metron-_kgEiLG6/lib64/python3.10/site-packages/s3transfer/upload.py", line 546, in _get_upload_input_manager_cls
    raise RuntimeError(
RuntimeError: Input marvel.jpg of type: <class 'django.core.files.uploadedfile.InMemoryUploadedFile'> is not supported.

If anyone has any thoughts or solutions, I’d appreciate it. Thanks!

I don’t have any specific knowledge about this, but some browsing around shows some avenues for looking for a solution.

What are your settings for those libraries that are being used? (django-storages? boto3? s3transfer?)

Is this link from the docs relevent to what you’re trying to do? How To Set Up Object Storage with Django | DigitalOcean

Was debating whether to include the storages setting or not. Uploading images to DO thru a CreateView has worked fine for a few years, but trying to implement the same functionality with DRF is what is failing, which leads me to believe the problem is with my Serializer or Viewset.

Anyway here’s my Boto/Storages settings which works fine, as I mentioned above, with the CreateView:

Settings

AWS_ACCESS_KEY_ID = config("DO_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = config("DO_SECRET_ACCESS_KEY")

AWS_STORAGE_BUCKET_NAME = config("DO_STORAGE_BUCKET_NAME")
AWS_S3_ENDPOINT_URL = config("DO_S3_ENDPOINT_URL")
AWS_S3_CUSTOM_DOMAIN = config("DO_S3_CUSTOM_DOMAIN")
# Set the cache to 7 days. 86400 seconds/day * 7
AWS_S3_OBJECT_PARAMETERS = {
    "CacheControl": "max-age=604800",
    "ACL": "public-read",
}
AWS_LOCATION = "static"

STATICFILES_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"

Storages

from storages.backends.s3boto3 import S3Boto3Storage


class MediaStorage(S3Boto3Storage):
    location = "media"
    file_overwrite = False

Ok, I needed to modify my serializer to this to get it working correctly:

class PublisherSerializer(serializers.ModelSerializer):
    def create(self, validated_data):
        """
        Create and return a new `Publisher` instance, given the validated data.
        """
        if "image" in validated_data:
            validated_data["image"] = validated_data["image"].seek(0)
        return Publisher.objects.create(**validated_data)