【问题标题】:Django custom FileSystemStorage works in development server but not in Apache serverDjango 自定义 FileSystemStorage 适用于开发服务器,但不适用于 Apache 服务器
【发布时间】:2019-04-02 08:09:08
【问题描述】:

我有一个自定义的FileSystemStorage 类,允许在通过管理更改界面上传文件时覆盖MEDIA_ROOT 子文件夹中现有的上传文件。但是,它在 Django 开发服务器中运行良好,但在 Apache 中,当用户上传文件并且没有报告错误时不会创建文件(在 Apache error.log 或 Django 日志系统中都没有)。相应的服务器文件夹和子文件夹对 www-data 用户和组都具有适当的 R/W 权限。代码如下:

class MyFileStorage(FileSystemStorage):
    def get_available_name(self, name, max_length=None):
        #pudb.set_trace()
        return name

    def _save(self, name, content):
        full_path = self.path(name)

        # Create any intermediate directories that do not exist.
        directory = os.path.dirname(full_path)
        logger = logging.getLogger('fileupload')

        if not os.path.exists(directory):
            try:
                if self.directory_permissions_mode is not None:
                    # os.makedirs applies the global umask, so we reset it,
                    # for consistency with file_permissions_mode behavior.
                    old_umask = os.umask(0)
                    try:
                        os.makedirs(directory, self.directory_permissions_mode)
                    finally:
                        os.umask(old_umask)
                else:
                    logger.debug("MyFileStorage::_save: Trying to create directory: %s" % (directory,))
                    os.makedirs(directory)
            except FileNotFoundError:
                # There's a race between os.path.exists() and os.makedirs().
                # If os.makedirs() fails with FileNotFoundError, the directory
                # was created concurrently.
                pass

        # If the file already exists we delete it, so we can re-upload a file
        # with the same filename and avoid the annoying random characters.
        if not os.path.isdir(directory):
            logger.debug("MyFileStorage::_save: IOError: %s exists and is not a directory." % directory)
            raise IOError("%s exists and is not a directory." % directory)

        if os.path.exists(full_path):
            logger.debug("MyFileStorage::_save: A file %s already exists. We remove it to avoid Django to crate a copy with random characters added to the filename." % (full_path,))
            os.remove(full_path)
        # There's a potential race condition between get_available_name and
        # saving the file; it's possible that two threads might return the
        # same name, at which point all sorts of fun happens. So we need to
        # try to create the file, but if it already exists we have to go back
        # to get_available_name() and try again.

        while True:
            try:
                # This file has a file path that we can move.
                if hasattr(content, 'temporary_file_path'):
                    logger.debug("MyFileStorage::_save: Moving the file %s to %s" % (content.temporary_file_path(), full_path))
                    file_move_safe(content.temporary_file_path(), full_path)

                # This is a normal uploadedfile that we can stream.
                else:
                    # This fun binary flag incantation makes os.open throw an
                    # OSError if the file already exists before we open it.
                    flags = (os.O_WRONLY | os.O_CREAT | os.O_EXCL |
                             getattr(os, 'O_BINARY', 0))
                    # The current umask value is masked out by os.open!
                    fd = os.open(full_path, flags, 0o666)
                    _file = None
                    try:
                        locks.lock(fd, locks.LOCK_EX)
                        for chunk in content.chunks():
                            if _file is None:
                                mode = 'wb' if isinstance(chunk, bytes) else 'wt'
                                _file = os.fdopen(fd, mode)
                                logger.debug("MyFileStorage::_save: The file %s was opened in mode '%s'." % (str(fd), mode))

                            _file.write(chunk)
                            logger.debug("MyFileStorage::_save: A chunk of information was written to the file %s." % (full_path, ))
                    finally:
                        logger.debug("MyFileStorage::_save: Closing file.")
                        locks.unlock(fd)
                        if _file is not None:
                            _file.close()
                        else:
                            os.close(fd)
            except OSError as e:
                if e.errno == errno.EEXIST:
                    # A new name is needed if the file exists.
                    logger.debug("MyFileStorage::_save: The file already exists in the server. It needs a new name.")
                    name = self.get_available_name(name)
                    full_path = self.path(name)
                else:
                    logger.debug("MyFileStorage::_save: Some error occurred.")
                    raise e
            else:
                # OK, the file save worked. Break out of the loop.
                break

        if self.file_permissions_mode is not None:
            logger.debug("MyFileStorage::_save: Changing file permissions.")
            os.chmod(full_path, self.file_permissions_mode)

        # Store filenames with forward slashes, even on Windows.
        if not os.path.exists(full_path):
            logger.debug("MyFileStorage::_save: Something went wrong. The file was not created.")

        logger.debug("MyFileStorage::_save: Apparently returning normally.")
        return name.replace('\\', '/')

相应的 Django 模型使用此自定义存储定义了一个 FileField:

filename = models.FileField(upload_to=get_remote_folder, blank=True, null=True, storage=MyFileStorage()) 

地点:

def get_remote_folder(instance, filename):
    if not instance.subjectid is None:
        path = settings.MEDIA_ROOT + 'attachments/subjects/%04d/' % (instance.subjectid.id,)        
    else:
        path = settings.MEDIA_ROOT + 'attachments/'

    return path + filename   

日志文件显示:

MyFileStorage::_save: The file 22 was opened in mode 'wb'.
MyFileStorage::_save: A chunk of information was written to the file /var/www/<app dir goes here>/uploads/attachments/subjects/0015/test_document.pdf.
MyFileStorage::_save: Closing file.
MyFileStorage::_save: Apparently returning normally.

有人知道我错过了什么吗?

更新:如果我手动将文件复制到 Apache 下的文件夹中,并尝试上传相同的文件,则该文件将从文件夹中删除(正如预期的那样,因为我想覆盖如果文件名相同),但不会创建新文件。

更新 2:我在 Apache 文件夹上运行了命令 watch -n 0.1 ls,我可以看到,当文件上传时,它出现在屏幕上,但显然只要 _save() 函数离开它被删除。当应用程序在 Django 开发服务器上运行时不会发生这种情况。

【问题讨论】:

    标签: django apache django-models django-admin file-system-storage


    【解决方案1】:

    好的,因为这似乎是一些与 Lepricon 相关的问题,所以我找到了一种解决方法(不是解决方案),但至少在此期间它有效。

    解决方法是在关闭文件后在 finally 块上调用 move.file_move_safe()

    # We need to change the name of the file just to avoid Apache lepricons from deleting 
    new_full_path = os.path.dirname(full_path) + "/_" + os.path.basename(full_path)
    move.file_move_safe(full_path, new_full_path)
    

    并像这样在models.save() 方法中修改文件名:

    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        self.filename.name = "_" + os.path.basename(self.filename.name)
        super(Files,self).save(force_insert, force_update, using, update_fields)
    

    显然,在创建文件后更改文件名会阻止 Lepricons 删除它。当然,这不是我一直在寻找的最佳解决方案,但至少可以让我继续工作。

    【讨论】:

      猜你喜欢
      • 2021-10-27
      • 2014-08-26
      • 2016-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-05
      • 2020-02-19
      相关资源
      最近更新 更多