用Python轻松开发数据库取数下载工具

 1 简介

这是我的系列教程「Python+Dash快速web应用开发」的第十四期,在前两期中,我们针对dash_table的自定义样式、前后端分页、单元格内容编辑等特点展开了介绍。

成都创新互联拥有网站维护技术和项目管理团队,建立的售前、实施和售后服务体系,为客户提供定制化的成都网站设计、网站制作、网站维护、多线BGP机房解决方案。为客户网站安全和日常运维提供整体管家式外包优质服务。我们的网站维护服务覆盖集团企业、上市公司、外企网站、商城网站定制开发、政府网站等各类型客户群体,为全球上1000+企业提供全方位网站维护、服务器维护解决方案。

而在dash_table中还有很多高级特性,可以极大程度上丰富DataTable()所渲染网页表格的交互能力,今天的文章作为「交互表格篇」的下篇,我们就来一起学习其中比较实用的一些特性。

图1

2 dash_table的更多实用功能

2.1 更多表格交互特性

上一期文章最后我们学习了通过设置参数editable=True,使得渲染出的表格可以通过鼠标双击进行编辑,而dash_table除此之外,还有更多实用的交互能力:

2.1.1 按列排序

  • 普通单列排序

在DataTable()中,我们只需要设置参数sort_action='native',即可开启列排序功能,此时每一列列名单元格内都会出现部件供我们点击切换排序方式:

app1.py

 
 
 
 
  1. import dash 
  2. import dash_table 
  3. import dash_bootstrap_components as dbc 
  4.  
  5. import seaborn as sns 
  6.  
  7. df = sns.load_dataset('iris') 
  8.  
  9. app = dash.Dash(__name__) 
  10.  
  11. app.layout = dbc.Container( 
  12.     [ 
  13.         dash_table.DataTable( 
  14.             data=df.to_dict('records'), 
  15.             columns=[ 
  16.                 {'name': column, 'id': column} 
  17.                 for column in df.columns 
  18.             ], 
  19.             style_table={ 
  20.                 'height': '500px', 
  21.                 'overflow-y': 'auto' 
  22.             }, 
  23.             sort_action='native' 
  24.         ) 
  25.     ], 
  26.     style={ 
  27.         'margin-top': '50px' 
  28.     } 
  29.  
  30. if __name__ == '__main__': 
  31.     app.run_server(debug=True) 

 

图2

  • 基于后端排序的多列排序

在DataTable()中设置sort_action='native'时,对应的是「按列排序」的前端模式,也即是数据一次性灌注到浏览器的前提下进行排序,这种方式不仅不适合大型数据集,而且只支持「单列排序」。

而当数据渲染方式为后端模式时,我们通过设置参数sort_action='custom'以及sort_mode='multi',配合在回调中获取属性sort_by中记录的参与排序的列名及升序降序方式,就可以实现多列排序。

我们在上一期的app2.py的基础上修改得到下面的例子:

app2.py

 
 
 
 
  1. import dash 
  2. import dash_bootstrap_components as dbc 
  3. import dash_table 
  4. from dash.dependencies import Input, Output 
  5.  
  6. import seaborn as sns 
  7.  
  8. df = sns.load_dataset('iris') 
  9. df.insert(0, '#', df.index) 
  10.  
  11. app = dash.Dash(__name__) 
  12.  
  13. app.layout = dbc.Container( 
  14.     [ 
  15.         dbc.Spinner( 
  16.             dash_table.DataTable( 
  17.                 id='dash-table', 
  18.                 columns=[ 
  19.                     {'name': column, 'id': column} 
  20.                     for column in df.columns 
  21.                 ], 
  22.                 page_size=15,  # 设置单页显示15行记录行数 
  23.                 page_action='custom', 
  24.                 page_current=0, 
  25.                 style_header={ 
  26.                     'font-family': 'Times New Romer', 
  27.                     'font-weight': 'bold', 
  28.                     'text-align': 'center' 
  29.                 }, 
  30.                 style_data={ 
  31.                     'font-family': 'Times New Romer', 
  32.                     'text-align': 'center' 
  33.                 }, 
  34.                 sort_action='custom', 
  35.                 sort_mode='multi' 
  36.             ) 
  37.         ) 
  38.     ], 
  39.     style={ 
  40.         'margin-top': '50px' 
  41.     } 
  42.  
  43.  
  44. @app.callback( 
  45.     [Output('dash-table', 'data'), 
  46.      Output('dash-table', 'page_count')], 
  47.     [Input('dash-table', 'page_current'), 
  48.      Input('dash-table', 'page_size'), 
  49.      Input('dash-table', 'sort_by')] 
  50. def refresh_page_data(page_current, page_size, sort_by): 
  51.  
  52.     if sort_by: 
  53.         return ( 
  54.             df 
  55.             .sort_values( 
  56.                 [col['column_id'] for col in sort_by], 
  57.                 ascending=[ 
  58.                     col['direction'] == 'asc' 
  59.                     for col in sort_by 
  60.                 ] 
  61.             ) 
  62.             .iloc[page_current * page_size:(page_current + 1) * page_size] 
  63.             .to_dict('records'), 
  64.             1 + df.shape[0] // page_size 
  65.         ) 
  66.  
  67.     return ( 
  68.         df.iloc[page_current * page_size:(page_current + 1) * page_size].to_dict('records'), 
  69.         1 + df.shape[0] // page_size 
  70.     ) 
  71.  
  72.  
  73. if __name__ == '__main__': 
  74.     app.run_server(debug=True) 

 

图3

2.1.2 按列条件筛选

除了基于指定字段进行排序之外,dash_table还支持列的条件筛选,设置filter_action="native",就可以开启基础的按列条件筛选功能,此时每一列表头下都会多出供用户输入筛选条件的单元格:

app3.py

 
 
 
 
  1. import dash 
  2. import dash_table 
  3. import dash_bootstrap_components as dbc 
  4.  
  5. import seaborn as sns 
  6.  
  7. df = sns.load_dataset('iris') 
  8.  
  9. app = dash.Dash(__name__) 
  10.  
  11. app.layout = dbc.Container( 
  12.     [ 
  13.         dash_table.DataTable( 
  14.             data=df.to_dict('records'), 
  15.             columns=[ 
  16.                 {'name': column, 'id': column} 
  17.                 for column in df.columns 
  18.             ], 
  19.             # 自定义条件筛选单元格样式 
  20.             style_filter={ 
  21.                 'font-family': 'Times New Romer', 
  22.                 'background-color': '#e3f2fd' 
  23.             }, 
  24.             style_table={ 
  25.                 'height': '500px', 
  26.                 'overflow-y': 'auto' 
  27.             }, 
  28.             style_header={ 
  29.                 'font-family': 'Times New Romer', 
  30.                 'font-weight': 'bold', 
  31.                 'text-align': 'center' 
  32.             }, 
  33.             style_data={ 
  34.                 'font-family': 'Times New Romer', 
  35.                 'text-align': 'center' 
  36.             }, 
  37.             filter_action="native" 
  38.         ) 
  39.     ], 
  40.     style={ 
  41.         'margin-top': '50px' 
  42.     } 
  43.  
  44. if __name__ == '__main__': 
  45.     app.run_server(debug=True) 

 

图4

而dash_table中自带的条件筛选语法很丰富,有条件的朋友可以前往https://dash.plotly.com/datatable/filtering了解更多。

而dash_table同样可以实现后端筛选,和前面的后端排序类似,主要利用filter_query属性的回调变化在后台基于pandas等框架进行数据筛选,比较简单,这里就不再赘述。

2.2 自带的数据表格下载功能

dash_table还自带了将当前所渲染的表格内容直接下载为csv或xlsx格式文件的简易功能,通过参数export_format设置导出的文件格式,但自带的下载按钮样式比较丑,如果你对此有比较高的要求,还是建议结合之前的「上传下载篇」自己设计相关功能:

 

图5

2.3 冻结首行

通过设置参数fixed_rows={'headers': True},我们可以实现下滑查看表格的过程中,始终保持表头被冻结:

图6

3 开发一个在线取数工具

在学习完今天的内容之后,我们来结合之前「上传下载篇」中提到的下载功能,来制作一个简单的对指定数据库中的数据表进行快速条件筛选并下载的工具,其中DataTable的derived_virtual_data属性记录了经过排序、条件筛选等操作后当前显示的表格数据:

图7

app4.py

 
 
 
 
  1. import dash 
  2. import dash_bootstrap_components as dbc 
  3. import dash_core_components as dcc 
  4. import dash_html_components as html 
  5. import dash_table 
  6. from dash.dependencies import Input, Output 
  7.  
  8. from flask import send_from_directory 
  9.  
  10. import os 
  11. import uuid 
  12. from sqlalchemy import create_engine 
  13. import pandas as pd 
  14.  
  15. try: 
  16.     os.mkdir("downloads") 
  17. except FileExistsError: 
  18.     pass 
  19.  
  20. engine = create_engine('mysql+pymysql://root:mysql@localhost/DASH') 
  21.  
  22. app = dash.Dash(__name__) 
  23.  
  24.  
  25. @app.server.route('/download/') 
  26. def download(file): 
  27.     return send_from_directory('downloads', file) 
  28.  
  29.  
  30. app.layout = dbc.Container( 
  31.     [ 
  32.         dbc.Row( 
  33.             [ 
  34.                 dbc.Col(dbc.Button('更新数据表', id='refresh-tables', style={'width': '100%'}), width=2), 
  35.                 dbc.Col(dcc.Dropdown(id='table-select', style={'width': '100%'}), width=2) 
  36.             ] 
  37.         ), 
  38.         html.Hr(), 
  39.         dash_table.DataTable( 
  40.             id='dash-table', 
  41.             editable=True, 
  42.             page_size=15, 
  43.             style_header={ 
  44.                 'font-family': 'Times New Romer', 
  45.                 'font-weight': 'bold', 
  46.                 'text-align': 'center' 
  47.             }, 
  48.             style_data={ 
  49.                 'font-family': 'Times New Romer', 
  50.                 'text-align': 'center' 
  51.             }, 
  52.             style_data_conditional=[ 
  53.                 { 
  54.                     # 对选中状态下的单元格进行自定义样式 
  55.                     "if": {"state": "selected"}, 
  56.                     "background-color": "#b3e5fc", 
  57.                     "border": "none" 
  58.                 }, 
  59.             ], 
  60.             filter_action="native" 
  61.         ), 
  62.         html.Br(), 
  63.         html.A(id='download-url', target="_blank") 
  64.     ], 
  65.     style={ 
  66.         'margin-top': '50px' 
  67.     } 
  68.  
  69.  
  70. @app.callback( 
  71.     Output('table-select', 'options'), 
  72.     Input('refresh-tables', 'n_clicks') 
  73. def refresh_tables(n_clicks): 
  74.     if n_clicks: 
  75.         return [ 
  76.             { 
  77.                 'label': table, 
  78.                 'value': table 
  79.             } 
  80.             for table in pd.read_sql_query('SHOW TABLES', con=engine)['Tables_in_dash'] 
  81.         ] 
  82.  
  83.     return dash.no_update 
  84.  
  85.  
  86. @app.callback( 
  87.     [Output('dash-table', 'data'), 
  88.      Output('dash-table', 'columns')], 
  89.     Input('table-select', 'value') 
  90. def render_dash_table(value): 
  91.     if value: 
  92.         df = pd.read_sql_table(value, con=engine) 
  93.  
  94.         return df.to_dict('records'), [ 
  95.             {'name': column, 'id': column} 
  96.             for column in df.columns 
  97.         ] 
  98.  
  99.     else: 
  100.         return [], [] 
  101.  
  102.  
  103. @app.callback( 
  104.     [Output("download-url", "href"), 
  105.      Output("download-url", "children")], 
  106.     [Input("dash-table", "derived_virtual_data"), 
  107.      Input("dash-table", "filter_query")], 
  108.     prevent_initial_call=True 
  109. def download_table(derived_virtual_data, filter_query): 
  110.     if derived_virtual_data: 
  111.         print(derived_virtual_data) 
  112.  
  113.         filename = f"output_{uuid.uuid1()}.xlsx" 
  114.  
  115.         pd.DataFrame(derived_virtual_data).to_excel("downloads/" + filename, index=False) 
  116.  
  117.         return "/download/" + filename, "下载当前状态表格" 
  118.  
  119.     return "", "" 
  120.  
  121.  
  122. if __name__ == '__main__': 
  123.     app.run_server(debug=True) 

分享标题:用Python轻松开发数据库取数下载工具
网站地址:http://www.shufengxianlan.com/qtweb/news17/133917.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联