多页面Dash应用中的数据持久性

1 投票
1 回答
37 浏览
提问于 2025-04-14 16:21

我有一个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

我找到了两种方法来实现这个功能:

  1. 共享的 Store 组件。
  2. 使用来自 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

撰写回答