多页面Dash应用中的数据持久性
我有一个Dash应用,用户在一个页面data.py
上添加一些数据,然后可以在另一个页面grid.py
上查看和删除选中的行。用户应该能够随时返回到data.py
,继续添加更多数据。
问题是:在访问grid.py
时,数据没有被保存下来。我该怎么做才能解决这个问题呢?我尝试设置persistence
属性,但没有任何效果。在grid.py
中的existing_data
总是显示为None
。而当我使用单页面应用时,类似的代码就能正常工作。
以下是我的最小可复现示例:
app.py
from dash import html, dcc
import dash
app = dash.Dash(__name__, use_pages=True)
app.layout = html.Div(
[
dcc.Store(id="store", data={}),
html.H1("Multi Page App Demo: Sharing data between pages"),
html.Div(
[
html.Div(
dcc.Link(f"{page['name']}", href=page["path"]),
)
for page in dash.page_registry.values()
]
),
html.Hr(),
dash.page_container,
]
)
if __name__ == "__main__":
app.run_server(debug=True)
data.py
from dash import html, Input, Output, callback, register_page
from dash.exceptions import PreventUpdate
import random
register_page(__name__, path="/")
layout = html.Div(
[
html.H3("Data input"),
html.Button("Add row", id="button_id"),
html.Br(),
html.Div(id="my-output"),
]
)
@callback(
[Output("store", "data"), Output("my-output", "children")],
Input("button_id", "n_clicks"),
prevent_initial_call=True
)
def add_data(n_clicks):
if n_clicks:
new_data = [{"col1": "New row", "col2": random.randint(0, 1000)}]
return new_data, html.Pre(str(new_data))
else:
raise PreventUpdate
grid.py
from dash import html, dash_table, Input, Output, callback, register_page, State
register_page(__name__)
layout = html.Div([
html.H3("Data tables"),
dash_table.DataTable(
id="table",
row_deletable=True,
column_selectable="single",
page_size=5,
persistence=True,
persisted_props=[
"data",
"columns.name",
"filter_query",
"hidden_columns",
"page_current",
"selected_columns",
"selected_rows",
"sort_by",
],
),
])
@callback(
Output("table", "data"),
Input("store", "data"),
State("table", "data"),
)
def update(new_data, existing_data):
if existing_data is not None:
return existing_data + new_data
else:
return new_data
1 个回答
1
我找到了两种方法来实现这个功能:
- 共享的
Store
组件。 - 使用来自 dash_extensions.pages 的持久组件功能。
我会把这两种方法都分享出来,以便大家参考。选择第二种方法的一个原因是,我并不完全理解第一种方法为什么能工作,因为我知道它引入了循环依赖。通常情况下,这种情况应该会抛出异常,或者更糟糕的是,导致无限回调循环,但实际上并没有发生。这一点让我觉得 Dash
处理得很聪明,但我不能确定它总是能正常工作,因为没有相关文档说明。
共享的 Store
组件
app.py:
from dash import html, dcc
import dash
app = dash.Dash(__name__, use_pages=True)
app.layout = html.Div(
[
dcc.Store(id="store", data=[], storage_type="session"),
html.H1("Multi Page App Demo: Sharing data between pages"),
html.Div(
[
html.Div(
dcc.Link(f"{page['name']}", href=page["path"]),
)
for page in dash.page_registry.values()
],
id='navbar'),
html.Hr(),
dash.page_container
]
)
if __name__ == "__main__":
app.run_server(debug=True)
pages/data.py
from dash import html, Input, Output, callback, register_page, State
from dash.exceptions import PreventUpdate
import random
register_page(__name__, path="/")
layout = html.Div(
[
html.H3("Data input"),
html.Button("Add row", id="button_id"),
html.Br(),
html.Div(id="my-output"),
]
)
@callback(
[Output("store", "data"), Output("my-output", "children")],
Input("button_id", "n_clicks"),
State("store", "data")
)
def add_data(n_clicks, data):
if n_clicks:
new_data = [{"col1": "New row", "col2": random.randint(0, 1000)}]
return data + new_data, html.Pre(str(new_data))
else:
raise PreventUpdate
pages/grid.py:
from dash import html, dash_table, Input, Output, callback, register_page
register_page(__name__)
layout = html.Div([
html.H3("Data tables"),
dash_table.DataTable(
id="table",
data=[{"name": "Test", "label": "Test"}],
row_deletable=True,
column_selectable="single",
page_size=5,
persistence=True,
persisted_props=["columns.name", "data"],
),
])
@callback(
Output("table", "data"),
Input("store", "data"),
)
def add_rows(new_data):
return new_data
@callback(
Output("store", "data", allow_duplicate=True),
Input("table", "data"),
prevent_initial_call=True
)
def update_back(new_data):
return new_data
使用来自 dash_extensions.pages
的持久组件功能
app.py:
from dash import html, dcc
from dash_extensions.pages import setup_page_components
from pages.components import NAVBAR_ID
import dash
app = dash.Dash(__name__, use_pages=True)
app.layout = html.Div(
[
dcc.Store(id="store", data=[], storage_type="session"),
html.H1("Multi Page App Demo: Sharing data between pages"),
html.Div(
[
html.Div(
dcc.Link(f"{page['name']}", href=page["path"]),
)
for page in dash.page_registry.values()
],
id=NAVBAR_ID),
html.Hr(),
dash.page_container,
setup_page_components()
]
)
if __name__ == "__main__":
app.run_server(debug=True)
pages/components.py
from dash import dash_table
NAVBAR_ID = "navbar"
data_table = dash_table.DataTable(
id="table",
row_deletable=True,
column_selectable="single",
page_size=5,
persistence=True,
persisted_props=[
"data",
"columns.name",
]
)
pages/data.py
from dash import html, Input, Output, callback, register_page
from dash.exceptions import PreventUpdate
import random
register_page(__name__, path="/")
layout = html.Div(
[
html.H3("Data input"),
html.Button("Add row", id="button_id"),
html.Br(),
html.Div(id="my-output"),
]
)
@callback(
[Output("store", "data"), Output("my-output", "children")],
Input("button_id", "n_clicks")
)
def add_data(n_clicks):
if n_clicks:
new_data = [{"col1": "New row", "col2": random.randint(0, 1000)}]
return new_data, html.Pre(str(new_data))
else:
raise PreventUpdate
pages/grid.py
from dash import html, Input, Output, callback, register_page, State
from pages.components import data_table
register_page(__name__, page_components=[data_table])
layout = html.Div([
html.H3("Data tables"),
])
@callback(
Output("table", "data"),
Input("store", "data"),
State("table", "data")
)
def add_rows(new_data, old_data):
if old_data:
return old_data + new_data
return new_data