博客原創,作者: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表格沒有實現,后續自己慢慢完善該功能。