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
是不是解决了!完美!!