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

问题描述

所以我有一个这样的模型

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,**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',editable=False)
    owner = models.ForeignKey('api_backend.User',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 dynamic at 16 bits-per-pixel component

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,**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,**kwargs)
  File "C:\Users\iyapp\Envs\reBox_django\lib\site-packages\rest_framework\generics.py",line 242,in post
    return self.create(request,**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,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 此导入返回一个 TemporaryFile (见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,tb)

    NamedTemporaryFile = TemporaryFile
else:
    NamedTemporaryFile = tempfile.NamedTemporaryFile

gettempdir = tempfile.gettempdir

TemporayFile 类的 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

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

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