文章来源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) # sql拼接
df = pd.read_sql_query(sql, ENGINE) # 将sql语句结果读取至Pandas Dataframe

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', # 数据透视汇总值为AMOUNT字段,一般保持不变
index='DATE', # 数据透视行为DATE字段,一般保持不变
columns=column, # 数据透视列为前端选择的分析维度
aggfunc=np.sum) # 数据透视汇总方式为求和,一般保持不变
if pivoted.empty is False:
pivoted.sort_values(by=pivoted.index[-1], axis=1, ascending=False, inplace=True) # 结果按照最后一个DATE表现排序

return pivoted
else:
return df


def query(request):
form_dict = dict(six.iterlists(request.GET))
pivoted = get_df(form_dict)

# KPI
kpi = get_kpi(pivoted)

table = ptable(pivoted)
table = table.to_html(formatters=build_formatters_by_col(table), # 逐列调整表格内数字格式
classes='ui selectable celled table', # 指定表格css class为Semantic UI主题
table_id='ptable' # 指定表格id
)

# Pyecharts交互图表
bar_total_trend = json.loads(prepare_chart(pivoted, 'bar_total_trend', form_dict))

# Matplotlib静态图表
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") # 返回结果必须是json格式

后端实现导出功能新建的export方法也可以和query方法一样在开头调用get_df:

try:
from io import BytesIO as IO # for modern python
except ImportError:
from io import StringIO as IO # for legacy python
import datetime


def 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)

# 设置浏览器mime类型
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 = {&#123; mselect_dict|safe &#125;};
for (key in dict) {
var form_name = dict[key]['select'] + "_select";
jquery_selector_id = "[id='" + form_name + "']";//因为我们的部分多选框id有空格,要用这种写法
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 = '{&#37; url 'chpa:export' 'pivoted' &#37;}'+ '?' + $.param(form_data, true);
window.location.href = downloadUrl;
});

$("#export_raw").click(function(){
var form_data = getForm();

var downloadUrl = '{&#37; url 'chpa:export' 'raw' &#37;}'+ '?' + $.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] # 如果键以[]结尾,删除_select[]取原字段名
else:
field_name = k[:-7] # 如果键不以[]结尾,删除_select取原字段名
selected = v # 选择项
sql = sql_extent(sql, field_name, selected) #未来可以通过进一步拼接字符串动态扩展sql语句
return sql

完成。

导出按钮可以视为一种可视化输出与其他可视化布局是并列关系

第一种导出形式,透视为时间序列后的数据

第二种导出形式,纯原始数