秀啊,用Python快速开发在线数据库更新修改工具

上一篇 / 下一篇  2021-04-12 09:57:11

  1 简介
  这是我的系列教程「Python+Dash快速web应用开发」的第十三期,在上一期中,我们一起认识了Dash自带的交互式表格组件dash_table,并学会了如何自定义表格中不同部分的样式。
  而今天的教程,我们将继续深入认识dash_table的更多交互方面的功能,学习如何为渲染出的表格分页,并添加动态内容修改等交互功能。
图1
  2 dash_table的基础交互能力
  dash_table的核心功能是赋予用户与图表进行快捷交互的能力,下面我们来学习其基础常用的一些交互功能:
  2.1 分页翻页
  当我们要展示的数据行数较多时,在网页中渲染可以选择分页,这在dash_table中实现起来比较方便,根据数据传递方式的不同,可以分为「前端分页」与「后端分页」:
  2.1.1 前端分页
  前端分页顾名思义,就是在我们访问Dash应用时,表格内所有页面的数据一次性加载完成,适合数据量不大的情况,将数据存储压力转移到浏览器端。
  通过参数page_size设置每页要显示的记录行数,Dash会自动帮我们分好页,并配上翻页部件:
  app1.py
  import dash 
  import dash_bootstrap_components as dbc 
  import dash_table 
   
  import seaborn as sns 
   
  df = sns.load_dataset('tips') 
  df.insert(0, '#', df.index) 
   
  app = dash.Dash(__name__) 
   
  app.layout = dbc.Container( 
      [ 
          dash_table.DataTable( 
              id='dash-table', 
              data=df.to_dict('records'), 
              columns=[ 
                  {'name': column, 'id': column} 
                  for column in df.columns 
              ], 
              page_size=15,  # 设置单页显示15行记录行数 
              style_header={ 
                  'font-family': 'Times New Romer', 
                  'font-weight': 'bold', 
                  'text-align': 'center' 
              }, 
              style_data={ 
                  'font-family': 'Times New Romer', 
                  'text-align': 'center' 
              } 
          ) 
      ], 
      style={ 
          'margin-top': '50px' 
      } 
  ) 
   
  if __name__ == '__main__': 
      app.run_server(debug=True) 
图2
  2.1.2 后端分页
  虽然前端分页简单易用,但当我们的数据很大时,强行使用前端分页会给「网络传输」和「浏览器端」带来不小的延迟和内存压力,严重影响用户体验,因此Dash贴心地为我们准备了「后端分页」方式。
  这时首先我们得为DataTable设置参数page_action='custom',这是使用后端分页的先决条件,接下来我们需要认识一些新的参数:
  page_current,int型,对应当前翻到的页码;
  page_count,int型,对应显示的总页数;
  我们在使用「后端分页」时,实际上就是通过用户当前翻到的页码,以及设定的page_size,来动态地在翻页后加载对应批次的数据,并控制显示的总页数,参考下面这个简单的例子:
  app2.py
  import dash 
  import dash_bootstrap_components as dbc 
  import dash_table 
  from dash.dependencies import Input, Output 
   
  import seaborn as sns 
  import pandas as pd 
  from tqdm import tqdm 
   
  # 压力测试 
  df = pd.concat([sns.load_dataset('tips') for _ in tqdm(range(1000))], ignore_index=True) 
  df.insert(0, '#', df.index) 
   
  app = dash.Dash(__name__) 
   
  app.layout = dbc.Container( 
      [ 
          dbc.Spinner( 
              dash_table.DataTable( 
                  id='dash-table', 
                  columns=[ 
                      {'name': column, 'id': column} 
                      for column in df.columns 
                  ], 
                  page_size=15,  # 设置单页显示15行记录行数 
                  page_action='custom', 
                  page_current=0, 
                  style_header={ 
                      'font-family': 'Times New Romer', 
                      'font-weight': 'bold', 
                      'text-align': 'center' 
                  }, 
                  style_data={ 
                      'font-family': 'Times New Romer', 
                      'text-align': 'center' 
                  } 
              ) 
          ) 
      ], 
      style={ 
          'margin-top': '50px' 
      } 
  ) 
   
   
  @app.callback( 
      [Output('dash-table', 'data'), 
       Output('dash-table', 'page_count')], 
      [Input('dash-table', 'page_current'), 
       Input('dash-table', 'page_size')] 
  ) 
  def refresh_page_data(page_current, page_size): 
      return df.iloc[page_current * page_size:(page_current + 1) * page_size].to_dict('records'), 1 + df.shape[ 
          0] // page_size 
   
   
  if __name__ == '__main__': 
      app.run_server(debug=True)
  可以看到,即使我们完整的数据集被我concat到24万行,加载应用以及网页内翻页时依然轻松自如毫无压力,在实际应用中你还可以将翻页部分改成受到LIMIT与OFFSET控制的数据库查询过程,使得应用运行的更加快速高效:
图3
  2.2 对单元格内容进行编辑
  讲完了分页翻页,接下来我们来学习dash_table中更加强大的功能——单元格内容编辑。
  一个现代化的web应用当然不能局限于仅仅查看数据这么简单,Dash同样赋予了我们双击数据表单元格进行数据编辑的能力,首先得设置参数editable=True,即开启表格编辑模式,接下来就可以对数据区域单元格进行任意的双击选中编辑。
  不过Dash默认的单元格被选中的样式忒丑了(是粉色的你敢信),因此我们可以利用下面的参数设置方式来自定义美化:
  style_data_conditional=[ 
                  { 
                      # 对选中状态下的单元格进行自定义样式 
                      "if": {"state": "selected"}, 
                      "background-color": "#b3e5fc", 
                      "border": "none" 
                  }, 
              ] 
  来看一个形象的例子,我们对「前端分页」方式渲染出的表格进行随意的修改,并在下方对利用pandas的compare比较出的数据框之间的差异结果进行打印:
  app3.py
  import dash 
  import dash_html_components as html 
  import dash_core_components as dcc 
  import dash_bootstrap_components as dbc 
  import dash_table 
  from dash.dependencies import Input, Output 
   
  import seaborn as sns 
  import pandas as pd 
   
  df = sns.load_dataset('tips') 
  df.insert(0, '#', df.index) 
   
  app = dash.Dash(__name__) 
   
  app.layout = dbc.Container( 
      [ 
          dash_table.DataTable( 
              id='dash-table', 
              data=df.to_dict('records'), 
              columns=[ 
                  {'name': column, 'id': column} 
                  for column in df.columns 
              ], 
              fixed_rows={'headers': True}, 
              page_size=15, 
              editable=True, 
              style_header={ 
                  'font-family': 'Times New Romer', 
                  'font-weight': 'bold', 
                  'text-align': 'center' 
              }, 
              style_data={ 
                  'font-family': 'Times New Romer', 
                  'text-align': 'center' 
              }, 
              style_data_conditional=[ 
                  { 
                      # 对选中状态下的单元格进行自定义样式 
                      "if": {"state": "selected"}, 
                      "background-color": "#b3e5fc", 
                      "border": "none" 
                  }, 
              ] 
          ), 
          html.H4('与原表格内容比较:', style={'margin-top': '50px'}), 
          dcc.Markdown( 
              '无差别', 
              id='markdown', 
              dangerously_allow_html=True 
          ) 
      ], 
      style={ 
          'margin-top': '50px' 
      } 
  ) 
   
   
  @app.callback( 
      Output('markdown', 'children'), 
      Input('dash-table', 'data'), 
      prevent_initial_call=True 
  ) 
  def compare_difference(dash_table_data): 
      print(pd.DataFrame(dash_table_data)) 
   
      return df.compare(pd.DataFrame(dash_table_data)).to_html() 
   
   
  if __name__ == '__main__': 
      app.run_server(debug=True) 
  可以看到,我们成功地对指定单元格元素进行了修改。
图4
  3 开发数据库内容在线更新工具
  在学习完今天的内容之后,我们就可以开发一个简单的,可在线自由修改并同步变动到数据库的小工具,这里我们以MySQL数据库为例,对示例表进行修改和更新:
  首先我们利用下列代码向示例数据库中新建表格tips:
  from sqlalchemy import create_engine 
  import seaborn as sns 
   
  df = sns.load_dataset('tips') 
  df.insert(0, '#', df.index) 
   
  engine = create_engine('mysql+pymysql://root:mysql@localhost/DASH') 
   
  df.to_sql('tips', con=engine, if_exists='replace', index=False) 
图5
  接下来我们就以创建好的tips表为例,开发一个Dash应用,进行数据的修改和更新到数据库:
图6
  效果非常的不错,你可以在我这个简单示例的基础上,拓展更多新功能,也可以采取后端分页+条件修改的方式来应对大型数据表的修改,全部代码如下:
  app4.py
  import dash 
  import dash_bootstrap_components as dbc 
  import dash_core_components as dcc 
  import dash_html_components as html 
  import dash_table 
  from dash.dependencies import Input, Output, State 
   
  from sqlalchemy import create_engine 
  import pandas as pd 
   
  engine = create_engine('mysql+pymysql://root:mysql@localhost/DASH') 
   
  app = dash.Dash(__name__) 
   
  app.layout = dbc.Container( 
      [ 
          dbc.Row( 
              [ 
                  dbc.Col(dbc.Button('更新数据表', id='refresh-tables', style={'width': '100%'}), width=2), 
                  dbc.Col(dcc.Dropdown(id='table-select', style={'width': '100%'}), width=2) 
              ] 
          ), 
          html.Hr(), 
          dash_table.DataTable( 
              id='dash-table', 
              editable=True, 
              page_size=15, 
              style_header={ 
                  'font-family': 'Times New Romer', 
                  'font-weight': 'bold', 
                  'text-align': 'center' 
              }, 
              style_data={ 
                  'font-family': 'Times New Romer', 
                  'text-align': 'center' 
              }, 
              style_data_conditional=[ 
                  { 
                      # 对选中状态下的单元格进行自定义样式 
                      "if": {"state": "selected"}, 
                      "background-color": "#b3e5fc", 
                      "border": "none" 
                  }, 
              ] 
          ), 
          dbc.Button('同步变动到数据库', id='update-tables', style={'display': 'none'}), 
          html.P(id='message') 
      ], 
      style={ 
          'margin-top': '50px' 
      } 
  ) 
   
   
  @app.callback( 
      Output('table-select', 'options'), 
      Input('refresh-tables', 'n_clicks') 
  ) 
  def refresh_tables(n_clicks): 
      if n_clicks: 
          return [ 
              { 
                  'label': table, 
                  'value': table 
              } 
              for table in pd.read_sql_query('SHOW TABLES', con=engine)['Tables_in_dash'] 
          ] 
   
      return dash.no_update 
   
   
  @app.callback( 
      [Output('dash-table', 'data'), 
       Output('dash-table', 'columns'), 
       Output('update-tables', 'style')], 
      Input('table-select', 'value') 
  ) 
  def render_dash_table(value): 
      if value: 
          df = pd.read_sql_table(value, con=engine) 
   
          return df.to_dict('records'), [ 
              {'name': column, 'id': column} 
              for column in df.columns 
          ], {'margin-top': '25px'} 
   
      else: 
          return [], [], {'display': 'none'} 
   
   
  @app.callback( 
      [Output('message', 'children'), 
       Output('message', 'style')], 
      Input('update-tables', 'n_clicks'), 
      [State('dash-table', 'data'), 
       State('table-select', 'value')] 
  ) 
  def update_to_database(n_clicks, data, value): 
   
      if n_clicks: 
   
          try: 
              pd.DataFrame(data).to_sql(value, con=engine, if_exists='replace', index=False) 
   
              return '更新成功!', {'color': 'green'} 
          except Exception as e: 
              return f'更新失败!{e}', {'color': 'red'} 
   
      return dash.no_update 
   
  if __name__ == '__main__': 
      app.run_server(debug=True) 

TAG: 软件开发 Python

 

评分:0

我来说两句

Open Toolbar