Dash应用点击下载按钮导出CSV时打开新标签页问题解决
Dash按钮导出DataTable为CSV时新标签页异常打开问题修复
问题背景
- 实现目标:在Dash应用中通过按钮点击触发DataTable导出CSV,不使用DataTable自带的
export_format配置项 - 异常现象:点击按钮可正常触发文件下载,但会同步在新标签页打开应用页面,所有回调函数无报错信息
- 运行环境:Python 3.9.5、Dash 2.4.1
问题复现代码
import os import pandas as pd import io import dash from dash import Dash, html, dcc, dash_table, callback_context from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate root, dirs, files = next(os.walk("./data/current/")) for f in range(len(files)): prior = pd.read_csv(root+files[f],header=0,delimiter=',',nrows=5,keep_default_na=False) tab1 = html.Div([ html.Div([ html.A(id='priortable',href='',target='_blank',children=[html.Button("Download Prioritization Table",id="btn_pt",n_clicks=0)]) ]), html.Br(), dash_table.DataTable(id='mesa', data=prior.to_dict('records'),columns=[{'name':i,'id':i} for i in prior.columns]), dcc.Download(id="download_pt") ]) app = dash.Dash(__name__,suppress_callback_exceptions=True) app.layout = html.Div([ dcc.Tabs(id='tabs', children=[ dcc.Tab(label='Prioritization',value='tab-1')]), html.Div(id='TABS') ]) @app.callback(Output('TABS', 'children'), Input('tabs', 'value')) def render_content(tab): if tab == 'tab-1': return tab1 @app.callback( Output('download_pt','data'), Input('btn_pt','n_clicks'), State('mesa','data'), prevent_initial_callback=True ) def download_pt(n_clicks, table_data): df = pd.DataFrame.from_dict(table_data) filename = '' fmt = "csv" if not n_clicks > 0: raise PreventUpdate download_buffer = io.StringIO() df.to_csv(download_buffer, index=False) download_buffer.seek(0) filename = 'prioritization_table_all_fires.%s' % fmt return dict(content=download_buffer.getvalue(), filename=filename) @app.server.route('/downloads/<path:path>') def serve_static(path): root_dir = os.getcwd() return flask.send_from_directory(os.path.join(root_dir,'download'), path) if __name__ == '__main__': app.run_server(debug=True)
根因定位
新标签页打开的核心原因是按钮外层错误嵌套了html.A标签:
- 该a标签配置了
target='_blank',同时href属性为空,点击时浏览器默认行为就是打开新标签页指向当前页面地址 - 外层嵌套a标签是Dash早期无官方下载组件时的旧实现方案,在使用Dash 2.x自带的
dcc.Download组件时,这种写法完全多余,反而会触发默认跳转行为 - 代码同时存在两处额外小问题:回调参数
prevent_initial_callback拼写错误(正确参数名是prevent_initial_callbacks,末尾带s)、静态下载路由用到flask但未导入,且该路由在当前内存生成文件的下载逻辑中完全无用。
修复方案
- 移除按钮外层多余的
html.A标签,直接将按钮放在普通容器Div中,从根源上阻断a标签的默认跳转行为 - 修正回调装饰器的参数拼写错误,避免初始加载时回调意外触发
- 可直接删除无用的静态文件路由,当前下载逻辑通过
dcc.Download直接返回内存中生成的文件内容,不需要走静态文件服务
修复后核心代码片段
# 修正后的tab1布局 tab1 = html.Div([ html.Div([ # 移除外层html.A标签,直接放置按钮 html.Button("Download Prioritization Table",id="btn_pt",n_clicks=0) ]), html.Br(), dash_table.DataTable(id='mesa', data=prior.to_dict('records'),columns=[{'name':i,'id':i} for i in prior.columns]), dcc.Download(id="download_pt") ]) # 修正后的下载回调 @app.callback( Output('download_pt','data'), Input('btn_pt','n_clicks'), State('mesa','data'), prevent_initial_callbacks=True # 修正参数拼写 ) def download_pt(n_clicks, table_data): if not n_clicks: raise PreventUpdate df = pd.DataFrame.from_dict(table_data) download_buffer = io.StringIO() df.to_csv(download_buffer, index=False) download_buffer.seek(0) return dict(content=download_buffer.getvalue(), filename='prioritization_table_all_fires.csv')
注:如果后续需要下载大文件,再考虑使用静态文件路由+临时文件的方案,小文件直接通过
dcc.Download返回内存内容性能更好,代码也更简洁。
内容的提问来源于stack exchange,提问作者desertBorn
相关产品推荐
相关产品推荐

