[Django]-Django with PIL – '_io.BytesIO' object has no attribute 'name'

4👍

The issue is that new versions of Django default to using MemoryFileUploadHandler, which doesn’t create a temporary file, and therefore there is no file “name.” See related Django ticket.

You’ll probably have to modify your code a bit to make this work, but you can at least start getting the name property by setting:

FILE_UPLOAD_HANDLERS = [
    'django.core.files.uploadhandler.TemporaryFileUploadHandler',
]

In your settings.py file.

You may find the code I’ve used to solve almost the exact same issue as helpful.

def clean_logo_file(self):
    logo_file_field = self.cleaned_data.get('logo_file')
    if logo_file_field:
        try:
            logo_file = logo_file_field.file
            with Image.open(logo_file_field.file.name) as image:
                image.thumbnail((512, 512), Image.ANTIALIAS)
                image.save(logo_file, format=image.format)
                logo_file_field.file = logo_file
                return logo_file_field
        except IOError:
            logger.exception("Error during image resize.")

Additional information on upload handlers.

0👍

  • If file is bigger than 2.5mb (2621440 bytes) – Django will
    use TemporaryFileUploadHandler.

  • Otherwise Django will use MemoryFileUploadHandler.

You can change FILE_UPLOAD_MAX_MEMORY_SIZE (doc) in settings.py
Or change FILE_UPLOAD_HANDLERS (doc) as Nostalg.io mentioned above.
My example with Django Rest Framework serializers:

Broken code:

# models.py
class ImageModel(Model):
    image = models.ImageField(upload_to='images/', null=False, blank=False)

# serializers.py
class ImageSerializer(serializers.ModelSerializer):

    class Meta:
        model = ImageModel
        fields = ["id", "image"]
        read_only_fields = ["id"]

    def validate_image(self, user_img):
        img = Image.open(user_img)
        ...  # process image here
        img_io = io.BytesIO()
        img.save(img_io, format='JPEG', quality=100)
        filename = "%s.jpg" % user_img.name.split('.')[0]

        user_img.name = "%s.jpg" % user_img.name.split('.')[0]
        user_img.file = img_io  # BAD IDEA!!!
        # This overrides django's tempfile._TemporaryFileWrapper() with _io.BytesIO() !!!
        ...
        return user_img  # if picture bigger than 2.5mb -> gives an error!

Fixed code:

#settings.py
FILE_UPLOAD_HANDLERS = [
    'django.core.files.uploadhandler.TemporaryFileUploadHandler',
]

# serializers.py
class ImageSerializer(serializers.ModelSerializer):

    class Meta:
        model = ImageModel
        fields = ["id", "image"]
        read_only_fields = ["id"]

    def validate_image(self, user_img):
        img = Image.open(user_img)

        ...  # process image here

        # override old TemporaryFile image with edited image
        path_to_tmp = user_img.file.name
        new_filename = "%s.jpeg" % user_img.name.split('.')[0]

        # set new image name
        img.save(path_to_tmp, format='JPEG', quality=100)
        user_img.name = new_filename
        ...
        return user_img  # no errors more :)

It might be more rational to process image by rewriting save() method in models.py, but I convert images in serializers.py because of handly ValidationError() 🙂

Leave a comment