Django学习之django和vue配合完成EXCEL文件上传并保存
django+drf+vue上传excel文件流程梳理
后端实现
1. 后台url.py
urlpatterns = [ ... re_path('^host_excel/', views.HostExcelView.as_view()), ]
2. 后台views.py
from host.utils.read_host_excel import read_host_excel_data class HostExcelView(APIView): # 上传host数据excel文件 def post(self,request): # request.data中能够拿到通过formdata上传的文件和其他键值对数据 # request.data>>>> <QueryDict: {'default_password': ['123'], 'host_excel': [<InMemoryUploadedFile: 教学日志模版-吴超-2019年11月份.xlsx (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)>]}> # from django.core.files.uploadedfile import InMemoryUploadedFile print('request.data>>>>', request.data) host_excel = request.data.get('host_excel') # 后台处理方式1:创建一个excel的临时存储目录(根目录下创建tem_file),先保存excel文件到本地,然后读取数据,保存,最后删除excel文件。 # file_path = f'{settings.BASE_DIR}/tem_file/{host_excel.name}' # with open(file_path, 'wb') as f: # for i in host_excel: # f.write(i) # 针对方式1:如果我们只是为了将数据存储到数据库中,为不需要保存excel文件到本地,那么我们需要删除上传上来的excel文件 # import os # if os.path.exists(file_path): # 如果文件存在 # # 删除文件,可使用以下两种方法。 # os.remove(file_path) # # os.unlink(path) # else: # print('没有该文件:%s' % file_path) # 则返回文件不存在 # 后台处理方式2: 如果我们只是为了将上传的excel数据保存到数据库中,那么我们不需要将excel文件保存到服务器本地,所以我们将接收到的数据,保存到内存bytes数据流中 from io import BytesIO,StringIO sio = BytesIO() for i in host_excel: sio.write(i) #将数据写入io对象 # 我在这里做了一个excel文件数据读取的函数,在下面的文件中 # res_data = read_host_excel_data(file_path, default_password) # 方式1:如果我们采用的是后台处理方式1,那么我们传入excel文件路径作为参数 res_data = read_host_excel_data(sio, default_password) # 方式2:如果我们采用的是后台处理方式2,那么我们传入io对象 # 拿到上传之后的数据之后,我们删掉上传上来的临时excel文件 return Response(res_data)
创建一个py文件存放我们上面处理excel的数据的函数
比如我的在host/utils/read_host_excel.py
def read_host_excel_data(recv_data, default_password=''): # data = xlrd.open_workbook(recv_data) #如果传入的是文件路径,那么这样传参 data = xlrd.open_workbook(file_contents=recv_data.getvalue()) #如果传入的是io数据对象,那么别忘了传参的时候要用关键字file_contents=指定一下,下面的处理两种方式都是一样的了 # 如下excel的处理,参考我的博客 # 根据索引获取第一个sheet工作簿 sheet = data.sheet_by_index(0) rows_count = sheet.nrows # print(sheet.name, sheet.nrows, sheet.ncols) # sheet名称,行数,列数 default_password = default_password # 查询出所有分类数据 category_list = HostCategory.objects.values_list('id', 'name') # print(category_list) host_info_list = [] for row_number in range(1, rows_count): one_row_dict = {} # print(sheet.cell(row_number, 0)) # 类型:值, 参数:(行号,列号) # print(sheet.cell_type(row_number, 0)) # 单元格数据类型 # print(sheet.cell_value(row_number, 0)) # 单元格的值 category = sheet.cell_value(row_number, 0) # 由于拿到的是分类名称,所以我们要找到对应名称的分类id,才能去数据库里面存储 for category_data in category_list: # print(category_data[1],type(category_data[1]),category,type(category)) if category_data[1].strip() == category.strip(): one_row_dict['category'] = category_data[0] break # 注意:数据列要对应 one_row_dict['hostname'] = sheet.cell_value(row_number, 1) one_row_dict['ip_addr'] = sheet.cell_value(row_number, 2) one_row_dict['port'] = sheet.cell_value(row_number, 3) one_row_dict['username'] = sheet.cell_value(row_number, 4) # 如果该条记录中没有密码数据,那么使用用户填写的默认密码,如果默认密码也没有,那么报错 # pwd = sheet.cell_value(row_number, 5) print(sheet.cell_value(row_number, 5),type(sheet.cell_value(row_number, 5))) excel_pwd = sheet.cell_value(row_number, 5) try: pwd = str(int(excel_pwd)) # 这样强转容易报错,最好捕获一下异常,并记录单元格位置,给用户保存信息时,可以提示用户哪个单元格的数据有问题 except: pwd = default_password # 注意:应该提醒用户,密码列应该转换为字符串类型,也就是excel的文本 if not pwd.strip(): pwd = default_password one_row_dict['password'] = pwd one_row_dict['desc'] = sheet.cell_value(row_number, 6) host_info_list.append(one_row_dict) # 校验主机数据 # 将做好的主机信息字典数据通过我们添加主机时的序列化器进行校验 res_data = {} # 存放上传成功之后需要返回的主机数据和某些错误信息数据 serializers_host_res_data = [] res_error_data = [] for k, host_data in enumerate(host_info_list): s_obj = HostModelSerializers(data=host_data) # print(s_obj.is_valid()) if s_obj.is_valid(): new_host_obj = s_obj.save() serializers_host_res_data.append(new_host_obj) else: # 报错,并且错误信息中应该体验错误的数据位置 res_error_data.append({'error': f'该{k+1}行数据有误,其他没有问题的数据,已经添加成功了,请求失败数据改完之后,重新上传这个错误数据,成功的数据不需要上传了'}) # 再次调用序列化器进行数据的序列化,返回给客户端 s2_obj = HostModelSerializers(instance=serializers_host_res_data, many=True) # print(s2_obj.data) res_data['data'] = s2_obj.data res_data['error'] = res_error_data return res_data
前端vue实现
标签:<input type="file" id="file"> <button @click="upload">上传</button> { ... data(){ return {} }, methods:{ upload(){ // 通过axios或者ajax上传文件,我这里是axios的示例,其实ajax和axios的是差不多的 const formData = new FormData(); // file是通过js获取的上传文件对象,不管用什么方式获取的,只要拿到文件对象就行,我下面举个例子,针对上面的input标签 let file_info = document.getElementById('file'); let file = file_info.files[0]; formData.append(`host_excel`, file); // 通过formdata上传文件数据,那么axios需要加上如下请求头键值对 this.$axios.post(`${this.$settings.host}/host/host_excel/`, formData, { headers: {'Content-Type': 'multipart/form-data'}, }).then().catch() } } }