http://www.laurentluce.com/posts/upload-to-django-with-progress-bar-using-ajax-and-jquery/
本文中我打算描述一下,我如何实现用 Ajax 和 jQuery 实现上传文件到Django+进度条。我需要实现这个功能来让用户上传菜品图片到 Gourmious 并跟踪上传进度。
客户端
首先需要一个表单来让用户选择要上传的文件。
1 | < form id = "form_upload" action = "/upload" method = "POST" > |
2 | < input type = "file" name = "picture" id = "picture" /> |
3 | < input type = "hidden" id = "X-Progress-ID" name = "X-Progress-ID" value = "" /> |
4 | < input type = "hidden" id = "id" name = "id" value = "" /> |
5 | < input id = "form_submit_button" class = "tp-button" type = "submit" value = "Submit" /> |
这里增加了两个隐藏的输入框,第一个是 ‘X-Progress-ID’,代表上传 ID,这样我们才能够在服务器端支持并发的上传请求。稍后我们会看到,服务器是如何处理这个值的。
然后还有一个隐藏输入框 ‘id’,在我们的例子里代表菜品的编号。
我们将使用 Ajax 来发送 POST 请求,这样表单便可以很好地集成在现代的网络界面中,同时包含一个进度条。我们打算使用 jQuery Form plugin 来实现这一点。
函数 ajaxSubmit() 将会帮我们搞定一切。
为上传 ID 生成一个随机字串,并用它设置输入框的值。
需要指定一个用于上传请求的 URL 和两个回调函数:一个在请求前调用,另一个在请求完成后调用。
1 | $( '#X-Progress-ID' ).val( 'random string' ); |
4 | url: '/upload?X-Progress-ID=' +$( '#X-Progress-ID' ).val(), |
5 | beforeSubmit: showRequest, |
8 | $( '#form_upload' ).ajaxSubmit(options); |
showRequest 回调函数只需要像下面这么简单就行了:
1 | function showRequest(formData, jqForm, options) { |
在 showResponse 函数中,我们需要处理响应,并对它进行操作。在我的例子里,我处理了服务器返回的带有状态值的 xml。
1 | function showResponse(response) { |
用户按下提交的时候,我们希望显示一个进度条,因此可以使用下面的 JS 代码,向表单添加进度条。progressBar() 方法是 jQuery progress bar plugin 的一部分。
1 | $( '#form_upload' ).find( '#form_submit_input' ).append( '<span id="uploadprogressbar"></span<' ); |
2 | $( '#form_upload' ).find( '#uploadprogressbar' ).progressBar(); |
现在我们需要添加一个每隔几秒运行一次的函数,来从服务器获取上传进度,并相应地更新进度条。
为此,我们使用 setInterval() 向服务器发出一个 GET 请求,获取 JSON 格式的进度值。我们向服务器传送上传 ID。当返回 null 值的时候,就可以知道上传已经结束。
01 | function startProgressBarUpdate(upload_id) { |
02 | $( "#uploadprogressbar" ).fadeIn(); |
03 | if (g_progress_intv != 0) |
04 | clearInterval(g_progress_intv); |
05 | g_progress_intv = setInterval( function () { |
06 | $.getJSON( "/get_upload_progress?X-Progress-ID=" |
07 | + upload_id, function (data) { |
09 | $( "#uploadprogressbar" ).progressBar(100); |
10 | clearInterval(g_progress_intv); |
14 | var percentage = Math.floor(100 * parseInt(data.uploaded) / parseInt(data.length)); |
15 | $( "#uploadprogressbar" ).progressBar(percentage); |
服务器端
首先需要 views.py 中有一个函数来处理上传。这个函数处理请求 “/upload?X-Progress-ID=xxxx”。我们将会一块一块地读取文件,以免使用过多 RAM。在我的例子里,我会返回包含状态值的 xml:上传成功或失败。
02 | id = request.POST[ 'id' ] |
03 | path = '/var/www/pictures/%s' % id |
04 | f = request.FILES[ 'picture' ] |
05 | destination = open (path, 'wb+' ) |
06 | for chunk in f.chunks(): |
07 | destination.write(chunk) |
如何跟进上传进度?需要使用一个不同的文件上传句柄。我们打算使用 UploadProgressCachedHandler。We just need the class from this snippet, not the view function which we are going to write ourself. You can add the class to a file named uploadprogresscachedhandler in your project.
这一句柄将上传进度保存到 cache,这样当我们收到客户端请求时就可以轻松地获取。
为了启用这一句柄,须向 settings.py 添加如下代码:
1 | from django.conf import global_settings |
2 | FILE_UPLOAD_HANDLERS = ( 'uploadprogresscachedhandler.UploadProgressCachedHandler' , ) \ |
3 | + global_settings.FILE_UPLOAD_HANDLERS |
我们还需要启用 cache 系统。我们将使用 memcached。这也放在 settings.py 里。
必须确保服务器上安装了 memcached 和 the python bindings。
我们需要在 views.py 中添加一个函数,在上传过程中,返回客户端每几秒请求获取的上传进度。这一函数处理请求“/get_upload_progress?X-Progress-ID=xxxx”。进度值使用key “remoteaddress_uploadid”来存储。
1 | from django.utils import simplejson |
2 | from django.core.cache import cache |
3 | def get_upload_progress(request): |
4 | cache_key = "%s_%s" % (request.META[ 'REMOTE_ADDR' ], request.GET[ 'X-Progress-ID' ]) |
5 | data = cache.get(cache_key) |
6 | return HttpResponse(simplejson.dumps(data)) |