文章来源 :Python Django+SQL+Pandas+Pyecharts自建在线数据分析平台(一) 作者 :ccpic 感谢 :感谢作者 ccpic 分享的优质内容,本网页主要用于学习知识的存档备份,欢迎点击原网页支持作者。
(一)需求分析&技术实现
(二)初步搭建Django环境
(三)页面布局&Django模板
(四)SQL+Pandas初步处理数据
(五)前端表单交互
(六)Ajax异步传参与加载
(七)前端数据格式的处理
(八)DataTables接管前端表格
(九)Pyecharts实现交互图表
(十)静态图表的展示
(十一)“导出数据至Excel”功能 (十二)添加和配置缓存
(十三)用户登录系统
(十四)部署Django至生产环境
本章实现最后一个必须的功能“导出数据至Excel”,因为目标很明确,实现是比较简单直接的,但也有一些需要注意的地方。
首先,我们需要明确导出数据的response这次不再适合用AJAX回调函数调用了,我们的整个导出功能需要和查询功能query方法并行,也就是需要完全新建一个方法以及相应URL。
为了复用,我们先把之前views.py文件里query方法的前一部分改写成get_df方法:
def get_df (form_dict, is_pivoted=True ): sql = sqlparse(form_dict) df = pd.read_sql_query(sql, ENGINE) if is_pivoted is True : dimension_selected = form_dict['DIMENSION_select' ][0 ] if dimension_selected[0 ] == '[' : column = dimension_selected[1 :][:-1 ] else : column = dimension_selected pivoted = pd.pivot_table(df, values='AMOUNT' , index='DATE' , columns=column, aggfunc=np.sum ) if pivoted.empty is False : pivoted.sort_values(by=pivoted.index[-1 ], axis=1 , ascending=False , inplace=True ) return pivoted else : return df def query (request ): form_dict = dict (six.iterlists(request.GET)) pivoted = get_df(form_dict) kpi = get_kpi(pivoted) table = ptable(pivoted) table = table.to_html(formatters=build_formatters_by_col(table), classes='ui selectable celled table' , table_id='ptable' ) bar_total_trend = json.loads(prepare_chart(pivoted, 'bar_total_trend' , form_dict)) bubble_performance = prepare_chart(pivoted, 'bubble_performance' , form_dict) context = { "market_size" : kpi["market_size" ], "market_gr" : kpi["market_gr" ], "market_cagr" : kpi["market_cagr" ], 'ptable' : table, 'bar_total_trend' : bar_total_trend, 'bubble_performance' : bubble_performance } return HttpResponse(json.dumps(context, ensure_ascii=False ), content_type="application/json charset=utf-8" )
后端实现导出功能新建的export方法也可以和query方法一样在开头调用get_df:
try : from io import BytesIO as IO except ImportError: from io import StringIO as IO import datetimedef export (request, type ): form_dict = dict (six.iterlists(request.GET)) if type == 'pivoted' : df = get_df(form_dict) elif type == 'raw' : df = get_df(form_dict, is_pivoted=False ) excel_file = IO() xlwriter = pd.ExcelWriter(excel_file, engine='xlsxwriter' ) df.to_excel(xlwriter, 'data' , index=True ) xlwriter.save() xlwriter.close() excel_file.seek(0 ) response = HttpResponse(excel_file.read(), content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) now = datetime.datetime.now().strftime("%Y%m%d%H%M%S" ) response['Content-Disposition' ] = 'attachment; filename=' + now + '.xlsx' return response
实现导出功能的Python写法五花八门,条条大路通罗马。我们这次代码的特点是使用了pandas的df.to_excel方法,需要加载xlsxwriter这个库。有些场景的导出方法则可能需要其他一些处理Excel的库。这段代码最需要注意的是最后设置浏览器mime类型和文件名部分的写法。
相应地,在url.py里再增加一个url pattern:
urlpatterns = [ ... path(r'export/<str:type>', views.export, name='export'), ]
前端因为也不是一个AJAX URL就返回所有数据了,也产生了代码复用的需求,我们把filter.html下面这段获取表单选择结果的JS代码独立出来:
<script> function getForm ( ){ var form_data = { "DIMENSION_select" : $("#DIMENSION_select" ).val (), "PERIOD_select" : $("#PERIOD_select" ).val (), "UNIT_select" : $("#UNIT_select" ).val (), }; var dict = {{ ; mselect_dict|safe } ;}; for (key in dict) { var form_name = dict[key]['select' ] + "_select" ; jquery_selector_id = "[id='" + form_name + "']" ; form_data[form_name] = $(jquery_selector_id).val (); } return form_data } </script>
在display.html里新建两个导出按钮,并写上相应的鼠标点击函数,注意这里的传参用了+ ‘?’ + $.param()的写法:
<div class ="ui pointing secondary menu" > ... <a class ="item" data-tab="export" ><i class ="download icon" > </i > 导出数据</a> </div> ... <div class ="ui tab segment" data-tab="export" > <div class ="ui buttons" > <input class ="ui blue button" type ='button' id ='export_pivot' value ="导出整理后时间序列数据" /> </div > <div class ="ui buttons" > <input class ="ui blue button" type ='button' id ='export_raw' value ="导出原始数据" /> </div > </div> <script > $("#export_pivot" ).click (function ( ){ var form_data = getForm (); var downloadUrl = '{% url ' chpa :export ' ' pivoted' %}' + '?' + $.param (form_data, true ); window .location .href = downloadUrl; }); $("#export_raw" ).click (function ( ){ var form_data = getForm (); var downloadUrl = '{% url ' chpa :export ' ' raw' %}' + '?' + $.param (form_data, true ); window .location .href = downloadUrl; }) </script >
最后需要注意这里的超级大坑,还记得第五章传参时表单多选框[]结尾的问题吗,这里因为不是AJAX传参,这时的多选框又不以[]结尾传参了,这种前后的不一致可能会导致后端混乱。我们需要在后端涉及到的地方顾忌这个问题,比如SQL语句拼接的函数中:
def sqlparse (context ): print (context) sql = "Select * from %s Where PERIOD = '%s' And UNIT = '%s'" % \ (DB_TABLE, context['PERIOD_select' ][0 ], context['UNIT_select' ][0 ]) for k, v in context.items(): if k not in ['csrfmiddlewaretoken' , 'DIMENSION_select' , 'PERIOD_select' , 'UNIT_select' ]: if k[-2 :] == '[]' : field_name = k[:-9 ] else : field_name = k[:-7 ] selected = v sql = sql_extent(sql, field_name, selected) return sql
完成。
导出按钮可以视为一种可视化输出与其他可视化布局是并列关系
第一种导出形式,透视为时间序列后的数据
第二种导出形式,纯原始数