Django图片系统
906 浏览 5 years, 10 months
9 源码解析
版权声明: 转载请注明出处 http://www.codingsoho.com/源码解析
ImageField (FileField)
ImageField从FileField继承而来,它的保存功能是在FileField里实现的,我们来看一下调用结构
- FileField::pre_save
- Field::pre_save
- FieldFile::save
- Storage::save
- FileSystemStorage::save
- Storage::save
class FileField(Field):
def pre_save(self, model_instance, add):
"Returns field's value just before saving."
file = super(FileField, self).pre_save(model_instance, add)
if file and not file._committed:
# Commit the file to storage prior to saving the model
file.save(file.name, file, save=False)
return file
class Field(RegisterLookupMixin):
def pre_save(self, model_instance, add):
"""
Returns field's value just before saving.
"""
return getattr(model_instance, self.attname)
FileField首先通过pre_save函数进行处理,如果有需要在save之前处理的需求的话,可以在这儿实现,如果文件没有commit, 接下来它会调用save函数。
class FieldFile(File):
def save(self, name, content, save=True):
name = self.field.generate_filename(self.instance, name)
#
args, varargs, varkw, defaults = getargspec(self.storage.save)
if 'max_length' in args:
self.name = self.storage.save(name, content, max_length=self.field.max_length) # (1)
else:
warnings.warn(
'Backwards compatibility for storage backends without '
'support for the `max_length` argument in '
'Storage.save() will be removed in Django 1.10.',
RemovedInDjango110Warning, stacklevel=2
)
self.name = self.storage.save(name, content)
#
setattr(self.instance, self.field.name, self.name)
#
# Update the filesize cache
self._size = content.size
self._committed = True
#
# Save the object because it has changed, unless save is False
if save:
self.instance.save()
save.alters_data = True
- (1)
self.storage.save
该语句调用storage进行存储,如果不做处理,它会调用文件的默认文件系统存储。但是有的时候你如果想修改行为,或者用外部存储比如SAE之类的话,需要替换这个storage.
class Storage(object):
def save(self, name, content, max_length=None):
"""
Saves new content to the file specified by name. The content should be
a proper File object or any python file-like object, ready to be read
from the beginning.
"""
# Get the proper name for the file, as it will actually be saved.
if name is None:
name = content.name
if not hasattr(content, 'chunks'):
content = File(content)
args, varargs, varkw, defaults = getargspec(self.get_available_name)
if 'max_length' in args:
name = self.get_available_name(name, max_length=max_length)
else:
warnings.warn(
'Backwards compatibility for storage backends without '
'support for the `max_length` argument in '
'Storage.get_available_name() will be removed in Django 1.10.',
RemovedInDjango110Warning, stacklevel=2
)
name = self.get_available_name(name)
name = self._save(name, content) # (1)
# Store filenames with forward slashes, even on Windows
return force_text(name.replace('\\', '/'))
- (1)
name = self._save(name, content)
这个真正完成文件系统的存储操作,默认FileSystemStorage
的实现如下。如前面所说,如果你想修改为其他的存储方式的话,可以修改DEFAULT_FILE_STORAGE
, 比如下面这个是SAE的存储文件系统
DEFAULT_FILE_STORAGE = 'sae.ext.django.storage.backend.Storage'
class FileSystemStorage(Storage):
def _save(self, name, content):
full_path = self.path(name)
# Create any intermediate directories that do not exist.
# Note that there is a race between os.path.exists and os.makedirs:
# if os.makedirs fails with EEXIST, the directory was created
# concurrently, and we can continue normally. Refs #16082.
directory = os.path.dirname(full_path)
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:
os.makedirs(directory)
except OSError as e:
if e.errno != errno.EEXIST:
raise
if not os.path.isdir(directory):
raise IOError("%s exists and is not a directory." % directory)
# 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'):
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)
_file.write(chunk)
finally:
locks.unlock(fd)
if _file is not None:
_file.close()
else:
os.close(fd)
except OSError as e:
if e.errno == errno.EEXIST:
# Ooops, the file exists. We need a new file name.
name = self.get_available_name(name)
full_path = self.path(name)
else:
raise
else:
# OK, the file save worked. Break out of the loop.
break
if self.file_permissions_mode is not None:
os.chmod(full_path, self.file_permissions_mode)
return name
UploadFile
接下来看看文件系统的上传时怎么完成的
django\core\files\uploadedfile.py
字典request.FILES中的每一个条目都是一个UploadedFile对象。
UploadedFile对象有如下方法:
- UploadFile.read(): 从文件中读取全部上传数据。当上传文件过大时,可能会耗尽内存,慎用。
- UploadFile.multiple_chunks(): 如上传文件足够大,要分成多个部分读入时,返回True.默认情况,当上传文件大于2.5M时,返回True。但这一个值可以配置。
- UploadFile.chunks(): 返回一个上传文件的分块生成器。如multiple_chunks()返回True,必须在循环中使用chrunks()来代替read()。一般情况下直接使用chunks()就行。
- UploadFile.name():上传文件的文件名
- UplaodFile.size():上传文件的文件大小(字节)
由上面的说明可以写出handle_uploaded_file函数
def handle_uploaded_file(f):
destination = open('some/file/name.txt', 'wb+')
for chunk in f.chunks():
destination.write(chunk)
destination.close()
上传文件保存的位置
保存上传文件前,数据需要存放在某个位置。默认时,当上传文件小于2.5M时,django会将上传文件的全部内容读进内存。意味着保存文件只有一次从内存读取,一次写磁盘。 但当上传文件很大时,django会把上传文件写到临时文件中,然后存放到系统临时文件夹中。
改变upload handler的行为
三个设置控制django文件上传的行为:
FILE_UPLOAD_MAX_MEMORY_SIZE: 直接读入内存的最大上传文件大小(字节数)。当大于此值时,文件存放到磁盘。默认2.5M字节
FILE_UPLOAD_TEMP_DIR
FILE_UPLOAD_PERMISSIONS: 权限
FILE_UPLOAD_HANDLERS
上传文件真正的处理句柄。修改此项设置可以完成自定义django上传文件的过程。
默认是两面两个:
# List of upload handler classes to be applied in order.
FILE_UPLOAD_HANDLERS = (
'django.core.files.uploadhandler.MemoryFileUploadHandler',
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
)