背景
某運(yùn)維平臺(tái)有一個(gè)業(yè)務(wù)需求,需要周期性對(duì)某個(gè)導(dǎo)出的文件進(jìn)行透視分析。通過在現(xiàn)有平臺(tái)上添加上傳文件功能,服務(wù)器后臺(tái)收到文件后完成透視分析流程,跳轉(zhuǎn)到圖表等形式的報(bào)告頁。
本文介紹如何利用Django完成文件上傳功能,并且因?yàn)榻y(tǒng)一風(fēng)格和美化的關(guān)系,前臺(tái)隱藏input標(biāo)簽。
Django部分
views.py
views中兩個(gè)處理邏輯:file_upload函數(shù)負(fù)責(zé)提供上傳頁面(此函數(shù)正式平臺(tái)不需要,后面講到);upload負(fù)責(zé)接收文件并完成實(shí)際的文件處理,返回生成的報(bào)告。
def file_upload(request):
return render(request, 'file_upload_input_submit.html')
def upload(request):
f = request.FILES['upload_file']
filename = f.name
with open('upload\\{}'.format(filename), 'wb+') as destination:
for chunk in f.chunks():
destination.write(chunk)
# do something with the file uploaded
report = '<h1>UPLOADED FILE:{}</h1><h2>REPORT FROM FILE CONTENTS</h2>'.format(filename)
# render report
return render_to_response('report.html', {'report': report})
urls.py
添加兩個(gè)地址路由:file_upload地址路由到views.file_upload,upload地址路由到views.upload。
from django.contrib import admin
from django.urls import path
import app.views as views
urlpatterns = [
path('admin/', admin.site.urls),
path('file_upload/', views.file_upload),
path('upload/', views.upload),
]
TEMPLATES模板
最普通的上傳頁(顯示input)
僅完成上傳功能的file_upload_input_submit.html模板,通過input按鈕與用戶交互,其body部分代碼如下:
<body>
<form enctype="multipart/form-data" name='form' method="post" action="/upload/">{% csrf_token %}
<input type="file" name="upload_file" onChange="document.forms['form'].submit();"/>
</body>
留著input就是很丑,效果如下:

隱藏input
file_upload_hide_input_with_function.html模板中隱藏了input,通過超鏈接調(diào)用click_upload函數(shù)→模擬點(diǎn)擊input→彈出文件選擇對(duì)話框,選取文件→觸發(fā)onChange事件→提交表單→訪問upload地址,代碼如下:
<head>
<title>文件上傳</title>
<script>
function click_upload() {
document.getElementById('input').click();
}
</script>
</head>
<body>
<form enctype="multipart/form-data" name='form' method="post" action="/upload/">{% csrf_token %}
<input type="file" id='input' name="upload_file" onChange="document.forms['form'].submit();" style='display:none'/>
<a href='javascript:click_upload();' >UPLOAD</>
</body>
效果如下:

簡化html
其實(shí)前面一個(gè)模板的function中就一行代碼,如果覺得一個(gè)函數(shù)就一行代碼顯得比較啰嗦,可以將這行代碼直接寫到a標(biāo)簽的href中。寫成函數(shù)在整個(gè)前端中可以保持代碼風(fēng)格統(tǒng)一,而嵌套在html中則比較簡潔,兩者沒有好壞之分,視具體應(yīng)用場(chǎng)景。
<body>
<form enctype="multipart/form-data" name='form' method="post" action="/upload/">{% csrf_token %}
<input type="file" id='input' name="upload_file" onChange="document.forms['form'].submit();" style='display:none'/>
<a href='javascript:document.getElementById("input").click();' >UPLOAD</a>
</body>
嵌入正式平臺(tái)
正式平臺(tái)左側(cè)是一堆功能鏈接,因此不需要單獨(dú)的上傳頁。將表單和超鏈接部分的代碼插入平臺(tái)的主頁即可。

上傳文件完成后跳轉(zhuǎn)到報(bào)告演示頁:

關(guān)于文件名
一開始為上傳到服務(wù)器的文件定了一個(gè)臨時(shí)的文件名,后來想使用文件原來的名字在服務(wù)器端命名。使用搜索引擎查找了一下,得到的方法是在js函數(shù)中截取上傳路徑中分隔符的最后一部分作為文件名參數(shù)傳遞給后臺(tái),比如在StackOverflow上某回答中的代碼如下:
function uploadOnChange() {
var filename = this.value;
var lastIndex = filename.lastIndexOf("\\");
if (lastIndex >= 0) {
filename = filename.substring(lastIndex + 1);
}
document.getElementById('filename').value = filename;}
這也是為什么我在嘗試隱藏input時(shí)首先單獨(dú)寫了個(gè)函數(shù)的原因(原本的函數(shù)是不止1行的)。后來翻Django文檔時(shí)看到,UploadedFile 類其實(shí)是有name屬性的:
name = property(_get_name, _set_name)
所以在views.upload中直接對(duì)文件調(diào)用該屬性即可,沒有必要用js那么麻煩。
參考資料
https://docs.djangoproject.com/en/2.1/topics/http/file-uploads/
https://docs.djangoproject.com/en/2.1/_modules/django/core/files/uploadedfile/
http://www.cnblogs.com/linxiyue/p/4038436.html
https://stackoverflow.com/questions/5480934/pass-filename-from-file-upload-to-text-field