保存任何缩略图的转换引擎问题

convert-engine issue with saving any thumbnail

所以我有这样的模型

from django.db import models
from sorl.thumbnail import get_thumbnail


class Upload(BaseModel):

    @staticmethod
    def upload_path_handler(instance, filename):
        return f'boxes/{instance.box.id}/uploads/{filename}'

    @staticmethod
    def thumbnail_path_handler(instance, filename):
        return f'boxes/{instance.box.id}/thumbnails/{filename}'

    def save(self, *args, **kwargs):
        if self._state.adding:
            # we cache the file size and store
            # it into the database to improve performance
            # we cannot edit the object's file so we don't
            # bother to modify the file size on updates
            self.size = self.file.size
            super(Upload, self).save(*args, **kwargs)
            thumbnail = get_thumbnail(self.file, '1280x720', crop='center')
            # sorl is not saving the thumbnails for non-image files
            return self.thumbnail.save(thumbnail.name, ContentFile(thumbnail.read()), True)
        super(Upload, self).save(*args, **kwargs)

    objects = api_managers.UploadManager()
    size = models.PositiveBigIntegerField()
    name = models.CharField(max_length=100, default='untitled', validators=[MinLengthValidator(2)])
    channel = models.ForeignKey('api_backend.Channel', on_delete=models.CASCADE, editable=False)
    box = models.ForeignKey('api_backend.Box', on_delete=models.CASCADE, editable=False)
    owner = models.ForeignKey('api_backend.User', on_delete=models.CASCADE, editable=False)
    thumbnail = models.ImageField(max_length=512, upload_to=thumbnail_path_handler.__func__, null=True, blank=True)
    file = models.FileField(max_length=512, upload_to=upload_path_handler.__func__)

    REQUIRED_FIELDS = [file, owner]

文件字段实际上可以是任何文件,我希望 sorl-thumbnail 为其制作缩略图并将其保存到缩略图字段中。我在 windows 并且正在使用 ImageMagick。 [python 版本 - 32 位]

这是我安装的二进制发行版。 https://imagemagick.org/script/download.php

ImageMagick-7.0.10-61-Q16-x86-dll.exe Win32 动态,每像素 16 位组件

settings.py

THUMBNAIL_ENGINE = 'sorl.thumbnail.engines.convert_engine.Engine'

但是,每当保存上传模型时,我都会收到以下错误。

FileNotFoundError: [Errno 2] No such file or directory: 'C:\Users\iyapp\PycharmProjects\rebox\media\cache\db\5a\db5a88e1d6a08cdfa1afbc92e9b8cb47.jpg'

完整追溯:

Exception ignored in: <function TemporaryFile.__del__ at 0x04184610>
Traceback (most recent call last):
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\django\core\files\temp.py", line 61, in __del__
    self.close()
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\django\core\files\temp.py", line 49, in close
    if not self.close_called:
AttributeError: 'TemporaryFile' object has no attribute 'close_called'
__init__() got an unexpected keyword argument 'delete'
Traceback (most recent call last):
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\sorl\thumbnail\base.py", line 104, in get_thumbnail
    source_image = default.engine.get_image(source)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\sorl\thumbnail\engines\convert_engine.py", line 76, in get_image
    with NamedTemporaryFile(mode='wb', delete=False) as fp:
TypeError: __init__() got an unexpected keyword argument 'delete'
Remote file [boxes/2/uploads/a243bfbd00fdcb54982faf63cfc290b1dfcd47f1c0484facbd67c8b8ff606aff.jpg] at [1280x720] does not exist
exc:  [Errno 2] No such file or directory: 'C:\Users\iyapp\PycharmProjects\rebox\media\cache\db\5a\db5a88e1d6a08cdfa1afbc92e9b8cb47.jpg'
Internal Server Error: /api/channels/1/uploads/
Traceback (most recent call last):
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\asgiref\sync.py", line 339, in thread_handler
    raise exc_info[1]
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\django\core\handlers\exception.py", line 38, in inner
    response = await get_response(request)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\django\core\handlers\base.py", line 233, in _get_response_async
    response = await wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\asgiref\sync.py", line 304, in __call__
    ret = await asyncio.wait_for(future, timeout=None)
  File "c:\python38\lib\asyncio\tasks.py", line 455, in wait_for
    return await fut
  File "c:\python38\lib\concurrent\futures\thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\asgiref\sync.py", line 343, in thread_handler
    return func(*args, **kwargs)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\django\views\generic\base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\rest_framework\views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
    raise exc
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\rest_framework\views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\rest_framework\generics.py", line 242, in post
    return self.create(request, *args, **kwargs)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\rest_framework\mixins.py", line 19, in create
    self.perform_create(serializer)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\rest_framework\mixins.py", line 24, in perform_create
    serializer.save()
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\rest_framework\serializers.py", line 205, in save
    self.instance = self.create(validated_data)
  File "C:\Users\iyapp\PycharmProjects\rebox\api_backend\serializers\partial.py", line 35, in create
    return super(PartialUploadSerializer, self).create(validated_data)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\rest_framework\serializers.py", line 939, in create
    instance = ModelClass._default_manager.create(**validated_data)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\django\db\models\query.py", line 447, in create
    obj.save(force_insert=True, using=self.db)
  File "C:\Users\iyapp\PycharmProjects\rebox\api_backend\models\uploads.py", line 43, in save
    return self.thumbnail.save(thumbnail.name, ContentFile(thumbnail.read()), True)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\sorl\thumbnail\images.py", line 162, in read
    f = self.storage.open(self.name)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\django\core\files\storage.py", line 36, in open
    return self._open(name, mode)
  File "C:\Users\iyapp\Envs\rebox_django\lib\site-packages\django\core\files\storage.py", line 231, in _open
    return File(open(self.path(name), mode))
FileNotFoundError: [Errno 2] No such file or directory: 'C:\Users\iyapp\PycharmProjects\rebox\media\cache\db\5a\db5a88e1d6a08cdfa1afbc92e9b8cb47.jpg'

有人可以帮我解决这个问题吗? 非常感谢!

据我所知,这就是第一个异常出现的原因。

from django.core.files.temp import NamedTemporaryFile

https://github.com/jazzband/sorl-thumbnail/blob/master/sorl/thumbnail/engines/convert_engine.py#L8 此导入 returns 一个临时文件 (参见 source code

"""
The temp module provides a NamedTemporaryFile that can be reopened in the same
process on any platform. Most platforms use the standard Python
tempfile.NamedTemporaryFile class, but Windows users are given a custom class.

This is needed because the Python implementation of NamedTemporaryFile uses the
O_TEMPORARY flag under Windows, which prevents the file from being reopened
if the same flag is not provided [1][2]. Note that this does not address the
more general issue of opening a file for writing and reading in multiple
processes in a manner that works across platforms.

The custom version of NamedTemporaryFile doesn't support the same keyword
arguments available in tempfile.NamedTemporaryFile.

1: https://mail.python.org/pipermail/python-list/2005-December/336957.html
2: https://bugs.python.org/issue14243
"""

import os
import tempfile

from django.core.files.utils import FileProxyMixin

__all__ = ('NamedTemporaryFile', 'gettempdir',)


if os.name == 'nt':
    class TemporaryFile(FileProxyMixin):
        """
        Temporary file object constructor that supports reopening of the
        temporary file in Windows.

        Unlike tempfile.NamedTemporaryFile from the standard library,
        __init__() doesn't support the 'delete', 'buffering', 'encoding', or
        'newline' keyword arguments.
        """
        def __init__(self, mode='w+b', bufsize=-1, suffix='', prefix='', dir=None):
            fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir)
            self.name = name
            self.file = os.fdopen(fd, mode, bufsize)
            self.close_called = False

        # Because close can be called during shutdown
        # we need to cache os.unlink and access it
        # as self.unlink only
        unlink = os.unlink

        def close(self):
            if not self.close_called:
                self.close_called = True
                try:
                    self.file.close()
                except OSError:
                    pass
                try:
                    self.unlink(self.name)
                except OSError:
                    pass

        def __del__(self):
            self.close()

        def __enter__(self):
            self.file.__enter__()
            return self

        def __exit__(self, exc, value, tb):
            self.file.__exit__(exc, value, tb)

    NamedTemporaryFile = TemporaryFile
else:
    NamedTemporaryFile = tempfile.NamedTemporaryFile

gettempdir = tempfile.gettempdir

并且 TemporayFile class' init 方法不采用任何名为 delete 的参数。相反,只有 tempfile.NamedTemporaryFile 可以。因此,这段代码失败了。

    def get_image(self, source):
        """
        Returns the backend image objects from a ImageFile instance
        """
        with NamedTemporaryFile(mode='wb', delete=False) as fp:
            fp.write(source.read())
        return {'source': fp.name, 'options': OrderedDict(), 'size': None}

https://github.com/jazzband/sorl-thumbnail/blob/master/sorl/thumbnail/engines/convert_engine.py#L72

我认为因此根本没有保存文件。 最后,在模型的保存方法中,

我们看到后端提示文件不存在。