在Django 1.0 版本后,文件上传的处理做了很大的改变,其中很重要的一点就是引入了 Upload Handlers 的概念。
Upload Handlers
这是个和Django中的Middleware差不多的东西,可以通过在 settings.py 文件中设置 FILE_UPLOAD_HANDLERS 定义一系列Upload Handlers,
和Middleware相似的地方主要表现为以下两点:
1、Django会按照 FILE_UPLOAD_HANDLERS 所定义的列表中各个Upload Handlers的先后顺序将上传的文件数据依次传递给Upload Handlers。
2、Upload Handlers是一个类,可以通过定义各种hook methods,在文件上传的各个阶段进行相应操作。同时可以通过返回操作后的数据或只返回None等形式,对是否需要后续的Upload Handlers进行处理进行控制。
通过以上两点,Django本身内置了2个Upload Handlers:
("django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler",)
第一个Handler是将上传的文件数据保存在内存中,如果上传的文件数据大小超过 FILE_UPLOAD_MAX_MEMORY_SIZE 所设置的值后,那么他只要将数据丢给后续的Handler(即TemporaryFileUploadHandler)即可。
通过此种处理方式,可以对文件上传进行详细的控制并进行各种有趣的操作,比如边上传边压缩,上传进度提示等。
而这次我所要讲的Ajax上传,进度显示就是通过自定义Handler实现的。通过定义以下的Handler,并且将该Handler放置在所有其他Handler之前,就可以在文件上传的过程中记录某个文件表单域所对应的上传进度。
from django.core.files.uploadhandler import FileUploadHandler
from django.core.cache import cache
import time
class LogFileUploadHandler(FileUploadHandler):
def __init__(self, request=None):
super(LogFileUploadHandler, self).__init__(request)
if 'formhash' in self.request.GET:
self.formhash = self.request.GET['formhash']
cache.add(self.formhash, {})
self.activated =True
else:
self.activated = False
def new_file(self, *args, **kwargs):
super(LogFileUploadHandler, self).new_file(*args, **kwargs)
if self.activated:
fields = cache.get(self.formhash)
fields[self.field_name] = 0
cache.set(self.formhash, fields)
def receive_data_chunk(self, raw_data, start):
# time.sleep(5) for local test, it slow down the upload speed
if self.activated:
fields = cache.get(self.formhash)
fields[self.field_name] = start
cache.set(self.formhash, fields)
return raw_data
def file_complete(self, file_size):
if self.activated:
fields = cache.get(self.formhash)
fields[self.field_name] = -1
cache.set(self.formhash, fields)
def upload_complete(self):
if self.activated:
fields = cache.get(self.formhash)
fields[self.formhash] = -1
cache.set(self.formhash, fields)
因为显示给每个用户每次浏览页面的时候,表单域的name都是相同的,为了区分每个页面,在表单提交的时候需要加上一个
formhash 的GET参数,以此来区分文件上传来源。
第一个问题,为什么formhash是一个GET参数,而不是POST参数,如果是POST的话不是更方便?因为只要在表单中加入一个hidden域即可。
那是因为如果在Handler尝试存取request.POST中的值,会导致又一次调用Upload Handler,从而形成无限递归,所以只能存取request.GET中的值。当然这并不是什么大问题,后面我会讲到用js将hidden域中的formhash提取出来作为GET参数。
第二个问题是如何保存文件上传进度的数据,用数据库来保存显然是开销太大了,因为一次文件上传的过程中需要反复读写记录的数据。最终我这里选择使用了django的cache组件来进行保存,因为保存的数据格式相对比较简单,并且对速度的要求比较高。(相关文章:http://www.djangosnippets.org/snippets/678/ http://www.djangosnippets.org/snippets/679/)
第三个问题,经实践测试,upload handler中的self.content_length 似乎一直都为None,所以在文件没有完整上传之前是没法获得总的数据长度的。因此此handler只记录了已上传的字节数,并且以 -1 表示上传结束。同样,为了表示一个表单中的所有文件上传域的文件上传完毕,通过将 formhash 作为key,值为 -1 来表示。
声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。更多精彩内容,访问http://jinhao.iteye.com/
如何提交表单