博客原创,作者:BruceBee,转载请标明出处,谢谢!
最近在用django写一个项目,项目当中有一处功能,需要在前端进行数据的选择,然后生成对应的excel表格并进行下载到本地。
将此功能进行拆解:
一、前端进行内容选择,生成excel表格
二、后端生成的excle表格供前端进行下载
python中提供的xlwt模块即可以实现生成excel表格,前后端的信息交互采用ajax,文件下载采用web前端访问url形式实现。
一、后端生成excel
现在我的项目目录下新建一个download目录,用于存储download的py文件和生成的excel文件,其中FileHandle.py为处理excel的主函数。

[root@localhost download]# tree . ├── core │?? ├── FileHandle.py │?? ├── FileHandle.pyc │?? ├── __init__.py │?? └── __init__.pyc ├── file │?? ├── csv │?? └── excel ├── __init__.py └── __init__.pyc
FileHandle.py:

1 #-*- coding:utf-8 -*- 2 import os,django,sys 3 BASE_DIR =os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 4 sys.path.append(BASE_DIR) 5 6 from Log.models import ActionLog,SSHLog 7 from OM.models import ServerGroup,ServerList 8 from Matrix.models import BaseInfo,ConfigInfo,Platform,BusinessUnit,DomainInfo,DnsInfo,ZabbixAlertInfo,Asset 9 10 import datetime 11 import xlwt 12 13 14 def BulidNewExcel(download_url,dbname): 15 db_dict={ 16 'BaseInfo':BaseInfo, 17 'ConfigInfo':ConfigInfo, 18 'Platform':Platform, 19 'BusinessUnit':BusinessUnit, 20 'DomainInfo':DomainInfo, 21 'DnsInfo':DnsInfo, 22 'ZabbixAlertInfo':ZabbixAlertInfo, 23 'Asset':Asset, 24 'ActionLog':ActionLog, 25 'SSHLog':SSHLog, 26 'ServerGroup':ServerGroup, 27 'ServerList':ServerList, 28 } 29 style0 = xlwt.easyxf('font: name Times New Roman, color-index red, bold on') 30 style1 = xlwt.easyxf(num_format_str='D-MMM-YY') 31 #获取字段名(列表) 32 field_name_list = [] 33 field_verbose_name_list = [] 34 35 # for i in models.SSHLog._meta.get_fields(): 36 for i in db_dict[dbname]._meta.get_fields(): 37 field_name_list.append(i.name) 38 if db_dict[dbname] ==BaseInfo or db_dict[dbname] ==Platform or db_dict[dbname] ==BusinessUnit or db_dict[dbname] ==DomainInfo: 39 field_verbose_name_list.append(i.name) 40 else: 41 field_verbose_name_list.append(i._verbose_name) 42 43 #Dns表中字段替换 44 field_name_list = ['Domain_name_id' if x == 'Domain_name' else x for x in field_name_list] 45 #config表中字段替换 46 field_name_list = ['baseid_id' if x == 'baseid' else x for x in field_name_list] 47 48 #plat、buss表字段替换 49 if 'baseinfo' in field_name_list:field_name_list.remove('baseinfo') 50 if 'baseinfo' in field_verbose_name_list:field_verbose_name_list.remove('baseinfo') 51 #domain表字段替换 52 if 'dnsinfo' in field_name_list:field_name_list.remove('dnsinfo') 53 if 'dnsinfo' in field_verbose_name_list:field_verbose_name_list.remove('dnsinfo') 54 55 #base表字段替换 56 if 'configinfo' in field_name_list:field_name_list.remove('configinfo') 57 if 'business_unit' in field_name_list:field_name_list.remove('business_unit') 58 if 'configinfo' in field_verbose_name_list:field_verbose_name_list.remove('configinfo') 59 if 'business_unit' in field_verbose_name_list:field_verbose_name_list.remove('business_unit') 60 61 field_name_list = ['isp_id' if x == 'isp' else x for x in field_name_list] 62 63 64 style0 = xlwt.easyxf('font: name Times New Roman, color-index red, bold on') 65 style1 = xlwt.easyxf(num_format_str='D-MMM-YY') 66 67 wb = xlwt.Workbook() 68 ws = wb.add_sheet('Sheet',cell_overwrite_ok=True) 69 for i in range(len(field_verbose_name_list)): 70 ws.write(0,i,field_verbose_name_list[i],style0) 71 72 mylist=[] 73 74 log_obj = db_dict[dbname].objects.all() 75 76 77 num = 0 78 for i in log_obj.values(): 79 mylist.append([]) 80 for j in range(len(field_name_list)): 81 mylist[num].append(i[field_name_list[j]]) 82 num+=1 83 84 for i in range(0,log_obj.count()): 85 for j in range(len(field_verbose_name_list)): 86 ws.write(i+1,j,mylist[i][j]) 87 timestr=datetime.datetime.now().strftime("%Y%m%d%H%M%S") 88 wb.save(download_url+'New-'+timestr+'.xls') 89 return timestr
说明如下:
2-4行:BASE_DIR为获取问文件路径,并将其添加到django的包路径中,供django的app调用;
6-8行:项目本地的models里面的名称,,由于我有三个app,为避免混淆我分别导入。可根据自己的情况修改;
14行:传入2个参数,分别为我定义好的文件存储路径和需要对应的数据库名称;
15-28行:定义字符串与实际数据库名为k-v关系的字典;
32-33行:定义两个列表,目的是将从models中获取的models的字段名和字段中文名提取出来(name与verbose_name),这里需要注意的是:如果你的models关系里没有定义verbose_name,那么提取出来的verbose_name将为空,多对多关系的字段没有verbose_name属性,直接取的话会报错;
36-61行:本项目实际情况对两个列表进行的处置动作,目的是得到最终的表格头部的内容,可忽略;
64-70行:实例化一个表格对象,使表格支持重复覆盖(即写动作),将列表内容写入表格的第一行,即得到表头。
74行:提取对应modles的内容,得到一个QuerySet对象,遍历这个对象,每个key就是一个记录,以字典形式呈现;
78-82行:嵌套循环,目的是生成一个列表,由每一条字段值组成的小列表,这些小列表为元素,组成一个大列表;
84-86行:嵌套循环这个大列表,将列表中的值写入到表格对象中;
87-89行:生成时间,将excel表格命名为New-‘时间格式’.xls,保存,返回该时间格式字符串;
至此,excel文件生成完成,但是潜在一个问题:就是当需要生成的数据量足够大的时候,这个转换列表也就足够大,其占用内存必然会很大。
view.py

1 from download.core import FileHandle 2 def BulidData(request): 3 dbname = request.POST.get('dbname') 4 # return HttpResponse(dbname) 5 ret = FileHandle.BulidNewExcel('/var/www/html/dtop/download/file/excel/',dbname) 6 return HttpResponse(ret) 7 8 9 def download(request,offset): 10 # ret = FileHandle.BulidNewExcel('/var/www/html/dtop/download/file/excel/') 11 from django.http import StreamingHttpResponse 12 def file_iterator(file_name,chunk_size=512): 13 with open(file_name) as f: 14 while True: 15 c = f.read(chunk_size) 16 if c: 17 yield c 18 else: 19 break 20 21 the_file_name ='New-'+offset+'.xls' 22 response = StreamingHttpResponse(file_iterator('/var/www/html/dtop/download/file/excel/New-'+offset+'.xls')) 23 response['Content-Type'] = 'application/octet-stream' 24 response['Content-Disposition'] = 'attachment;filename="{0}"'.format(the_file_name) 25 26 return response
说明如下:
1行:从前面的download目录中导入表格生成函数
2-7行:BulidData函数调用生成表格,得到该表格名称的时间字符串
9-26行:download函数根据用户传进来的offset值,拼接成文件名称字符串,并到指定目录取回该文件,以http流方式返回给前端,即实际的下载功能。
url.py:

urlpatterns = [ url(r'^BulidData/', Matrix.views.BulidData), url(r'^download/(\w+)*/$', Matrix.views.download), ]
说明如下:
在url里添加以上两个函数的路由信息;注意download函数采用动态url的方式获取用户的参数,这里参数实际上是一串日期字符串,即前面的Filehadle函数返回的timestr
二:前端交互并下载
html界面:

<input id="downData" class="btn btn-info" type="button" value="导出" name="Asset" onclick="downData();">
说明如下:为了简化,我只写了一个button,注意这里的name属性值最终是要传递给后端,与FileHadel函数里的字典进行匹配,需要取不同数据库的excel,改这里就可以了。
js:

1 function downData(){ 2 var inputChecks=$("input:checkbox[name='dataFrom_check']:checked"); 3 if(inputChecks.length==0){ 4 layer.alert('请选中导出项!'); 5 return; 6 } 7 var dbname =$("#downData").attr("name") 8 9 $.ajax({ 10 type:'POST', 11 url:'/BulidData/', 12 dataType:'text', 13 data:{'dbname':dbname}, 14 success:function(text){ 15 var url ='/download/'+text; 16 window.location.href=url; 17 },error:function(){ 18 alert('导出失败'); 19 } 20 }); 21 22 }
说明如下:
定义了一个downData函数,获取指定DOM元素的的name属性,通过ajax传递给BulidData函数,生成excel,得到该excel文件时间字符串
16行:window.location.href=url,ajax访问该url,即实际的下载功能。
至此,前端点击“导出”按钮,即可实现后端生成excel并下载只本地,功能实现。但是这个功能还有一些地方不完善,除了前面提到的转换形成的列表是个潜在的因素以外(后来测试了一下,生成一个8000条的excel表格,内存2G的虚拟机mysql瞬时的占用内存才不到10%,CPU使用率不到5%,从前端点击到下载到本地,感觉耗时不到半秒钟)。自定义内容的excel表格没有实现,后续自己慢慢完善该功能。