我有一个自定义的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开发服务器上运行时,不会发生这种情况。
答案 0 :(得分:0)
好吧,因为这似乎是与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删除它。当然,这不是我一直在寻找的最佳解决方案,但至少可以让我继续工作。