about
本篇博客主要演示了,使用Django完成對本地Excel表格的上傳下載操作,當然,其他類型的文件也一樣。
環境:win10 + django1.11 + xlrd
上傳
一般的, 上傳可以分為通過form表單提交和通過ajax提交兩種。
form表單上傳
來看示例:
前端重要代碼。
<div>
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="f1">
<input type="text" name="user">
<input type="submit" value="提交">
</form>
</div>
一般的,在普通的form表單提交時,請求頭中的CONTENT_TYPE: application/x-www-form-urlencoded,然后數據是以鍵值對的形式傳輸的,服務端從request.POST取值,這沒問題,並且CONTENT_TYPE: application/x-www-form-urlencoded這種編碼類型滿足大多數情況,一切都挺好的。
而要說使用form表單上傳文件,就不得不多說兩句了。
起初,http 協議中沒有上傳文件方面的功能,直到 rfc1867 為 http 協議添加了這個功能。當然在 rfc1867 中限定 form標簽的 method 必須為 POST,enctype = "multipart/form-data" 以及<input type = "file">。
所以,當使用form表單上傳文件的時候,請求頭的content_type是multipart/form-data這種形式的,所以,我們需要在form標簽添加enctype="multipart/form-data屬性來進行標識。
如果你能打印上傳文件的請求頭,你會發現CONTENT_TYPE是這樣的content_type:multipart/form-data; boundary=----WebKitFormBoundarylZZyJUkrgm6h34DU,那boundary=----WebKitFormBoundarylZZyJUkrgm6h34DU又是什么呢?
在multipart/form-data 后面有boundary以及一串字符,這是分界符,后面的一堆字符串是隨機生成的,目的是防止上傳文件中出現分界符導致服務器無法正確識別文件起始位置。那分界符又有啥用呢?
對於上傳文件的post請求,我們沒有使用原有的 http 協議,所以 multipart/form-data 請求是基於 http 原有的請求方式 post 而來的,那么來說說這個全新的請求方式與 post 的區別:
- 請求頭的不同,對於上傳文件的請求,
contentType = multipart/form-data是必須的,而 post 則不是,畢竟 post 又不是只上傳文件~。 - 請求體不同,這里的不同也就是指前者(上傳文件請求)在發送的每個字段內容之間必須要使用分界符來隔開,比如文件的內容和文本的內容就需要分隔開,不然服務器就沒有辦法正常的解析文件,而后者 post 當然就沒有分界符直接以
key:value的形式發送就可以了。
當然,其中的彎彎繞繞不是三言兩語能解釋的清楚的,我們先知道怎么用就行。
再來看views視圖處理:
from django.shortcuts import render, redirect, HttpResponse
from django.db import transaction
def import_case(request, pk):
""" 導入Excel數據,pk是所屬項目的pk """
if request.method == 'POST':
try:
with transaction.atomic(): # 事物
# project_pk = request.POST.get("project_pk") # 數據庫使用字段
excel = request.FILES.get('file_obj')
book = xlrd.open_workbook(filename=None, file_contents=excel.read())
sheet = book.sheet_by_index(0)
title = sheet.row_values(0)
for row in range(1, sheet.nrows):
print(sheet.row_values(row)) # 這里取出來每行的數據,就可以寫入到數據庫了
return HttpResponse('OK')
except Exception as e:
print(e)
return render(request, 'import_case.html', {"project_pk": pk, "error": "上傳文件類型有誤,只支持 xls 和 xlsx 格式的 Excel文檔"})
return render(request, 'import_case.html', {"project_pk": pk, "error": ""})
ajax上傳文件
前端文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- ajax上傳文件開始 -->
<div>
{% csrf_token %}
<input type="file" id="ajaxFile">
<button id="ajaxBtn">上傳</button>
</div>
<!-- ajax上傳文件結束 -->
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
console.log($("[name='csrfmiddlewaretoken']").val());
$("#ajaxBtn").click(function () {
// 首先,實例化一個formdata對象
var formData = new FormData();
// 然后使用formdata的append來添加數據,即獲取文件對象
// var file_obj = $("#ajaxFile")[0].files[0]; // 使用jQuery獲取文件對象
var file_obj = document.getElementById('ajaxFile').files[0]; // 使用dom也行
formData.append('f1', file_obj );
// 處理csrftoken
formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
// 也可以將其他的數據,以鍵值對的形式,添加到formData中
formData.append('user','張開');
$.ajax({
url: "/upload/",
type: "POST",
data: formData,
processData:false, //
contentType:false,
success:function (dataMsg) {
console.log(dataMsg);
}
})
})
</script>
</html>
在 ajax 中 contentType 設置為 false 是為了避免 JQuery 對請求頭content_type進行操作,從而失去分界符,而使服務器不能正常解析文件。
在使用jQuery的$.ajax()方法的時候參數processData默認為true(該方法為jQuery獨有的),默認情況下會將發送的數據序列化以適應默認的內容類型application/x-www-form-urlencoded
如果想發送不想轉換的信息的時候需要手動將其設置為false即可。
再來看后端views.py如何處理:
import xlrd
from django.shortcuts import render
from django.http import JsonResponse
def upload(request):
if request.is_ajax():
# print(request.META['CONTENT_TYPE']) # multipart/form-data; boundary=----WebKitFormBoundaryuXDgAwSKKIGnITam
# print(request.POST) # <QueryDict: {'csrfmiddlewaretoken': ['mx1EBTtsOb0k96TUUW8XKbCGvK0Co3S6ZMlLvOuZOKAlO9nfhf6zol0V8KxRxbwT'], 'user': ['張開']}>
# print(request.FILES) # <MultiValueDict: {'f1': [<InMemoryUploadedFile: 接口測試示例.xlsx (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)>]}>
f1 = request.FILES.get('f1')
print(f1) # 接口測試示例.xlsx
book = xlrd.open_workbook(filename=None, file_contents=f1.read())
sheet = book.sheet_by_index(0)
print(sheet.row_values(1)) # ['cnodejs項目', 'get /topics 主題首頁', 'https://cnodejs.org/api/v1/topics', 'get', '', '{"success":true}']
return JsonResponse({"message": "upload successful"})
else:
return render(request, 'upload.html')
OK了,多說無益,干就完了。
下載
使用StreamingHttpResponse
views中主要代碼:
from django.http import StreamingHttpResponse
def download(request):
file=open('crm/models.py','rb')
response =StreamingHttpResponse(file)
response['Content-Type']='application/octet-stream'
response['Content-Disposition']='attachment;filename="models.py"'
return response
使用FileResponse
views中主要代碼:
from django.http import FileResponse
def download(request):
file=open('crm/models.py','rb')
response =FileResponse(file)
response['Content-Type']='application/octet-stream'
response['Content-Disposition']='attachment;filename="models.py"'
return response
解決filename不能有中文的問題
如果你細心的嘗試,會發現,上面兩中下載方式中的filename不能包含中文,那么如何解決呢?來,往下看!
from django.http import FileResponse
from django.utils.encoding import escape_uri_path # 導入這個家伙
def download(request):
file=open('crm/models.py','rb')
response =FileResponse(file)
response['Content-Type']='application/octet-stream'
response['Content-Disposition']='attachment;filename="{}.py"'.format(escape_uri_path("我是中文啦"))
return response
是不是解決了!完美!!
歡迎斧正,that's all see also:[Django上傳並讀取Excel ](https://www.jianshu.com/p/88f1acea37f1) | [淺談contentType = false](https://segmentfault.com/a/1190000007207128) | [$.AJAX()方法中的PROCESSDATA參數](https://www.cnblogs.com/zpsylgdx/p/10568837.html) | [詳解django三種文件下載方式](https://www.jb51.net/article/137790.htm) | [Django實現下載文件](https://blog.csdn.net/sinat_38068807/article/details/96183220)
