How to Handle Duplicate Callbacks?

Hello everyone,
following up on my question here , I have another question. So I tried to follow on Charming Data tutorial right here but I tried to use my own data. Here is the code that I have so far (sorry if the code is too long):

app = Dash(__name__)

datelist = pd.date_range(start="2022-09-01", periods=365,).date.tolist()


df=pd.DataFrame(OrderedDict([
                    ('Name',[]),
                    ('Group',[]),
                    ('Level',[]),
                    ('Enrollment Fees',[]),
                    ('Status',[]),
                    ('Payment Date', []),
                    ('Package Shipment', []),
                    ('Registration Status', [])
                ]))
df['Name'] = ['Anthony', 'Bernard', 'Chester', 'Dylan', 'Elon', 'Floyd', 'Gloria', 'Hendry', 'Isaac']
df['Group'] = ['Children','Children','Children', 'Teenager','Teenager','Teenager', 'Adult', 'Adult', 'Adult']
df['Level'] = ['1','1','1', '2', '2', '2', '3', '3', '3']

df['Status'] = ['Paid','Paid','Paid', 'Not Done', 'Not Done', 'Not Done', 'Cancelled', 'Cancelled', 'Cancelled']
df['Package Shipment'] =['Sent', 'Sent', 'Sent','On Process', 'On Process', 'On Process'
    ,'No Shipment', 'No Shipment', 'No Shipment']
df['Registration Status'] = ['Complete', 'Not Complete','Complete', 'Not Complete','Complete', 'Not Complete','Complete', 'Not Complete','Complete']


app.layout = html.Div([
    html.Div([
        dcc.Input(
            id='adding-rows-name',
            placeholder='Enter a column name...',
            value='',
            style={'padding': 10}
        ),
        html.Button('Add Column', id='adding-columns-button', n_clicks=0)
    ], style={'height': 50}),
    dash_table.DataTable(
                id='daftar-baru',
                data=df.to_dict('records'),
                columns=[
                    {'id':'Name', 'name':'Name'},
                    {'id':'Group', 'name':'Group', 'presentation':'dropdown'},
                    {'id': 'Level', 'name': 'Level', 'presentation': 'dropdown'},
                    {'id': 'Enrollment Fees', 'name': 'Enrollment Fees','type': 'numeric', 'format': Format(group=',')},
                    {'id': 'Status', 'name': 'Status', 'presentation': 'dropdown'},
                    {'id': 'Payment Date', 'name':'Payment Date', 'type': 'datetime'},
                    {'id': 'Package Shipment', 'name': 'Package Shipment', 'presentation': 'dropdown'},
                    {'id': 'Delivery Date', 'name': 'Delivery Date', 'type': 'datetime'},
                    {'id': 'Registration Status', 'name':'Registration Status'}
                ],
                editable=True,
                filter_action='native',
                style_cell={'textAlign': 'left', 'minWidth': '100px', 'width': '100px', 'maxWidth': '100px'},
                style_cell_conditional=[
                            {
                                'if': {'column_id': c},
                                'textAlign': 'right'
                            } for c in ['Price', 'Sales']
                        ],
                page_current=0,
                page_size=20,
                dropdown={
                    'Group': {
                        'options': [
                            {'label': i, 'value': i}
                            for i in df['Group'].unique()
                        ]
                    },
                    'Level': {
                        'options': [
                            {'label': i, 'value': i}
                            for i in df['Level'].unique()
                        ]
                    },
                    'Status': {
                        'options': [
                            {'label': i, 'value': i}
                            for i in df['Status'].unique()
                        ]
                    },
                    'Package Shipment': {
                        'options': [
                            {'label': i, 'value': i}
                            for i in df['Package Shipment'].unique()
                        ]
                    }
                }
            ),
            html.Button('Add Row', id='editing-rows-button', n_clicks=0),
            html.Button('Export to Excel', id='save_to_csv', n_clicks=0),

            # Create notification when saving to excel
            html.Div(id='placeholder', children=[]),
            dcc.Store(id="store", data=0),
            dcc.Interval(id='interval', interval=1000),


])
@app.callback(
    Output('daftar-baru', 'data'),
    Input('daftar-baru', 'data'),
)
def update_table(rows):
    for row in rows:
        if row['Group'] =='Children' and row['Level']=='1':
            row['Enrollment Fees'] = 150000
        if row['Group'] =='Children' and row['Level']=='2':
            row['Enrollment Fees'] = 200000
        if row['Group'] =='Children' and row['Level']=='3':
            row['Enrollment Fees'] = 250000

        if row['Group'] =='Teenager' and row['Level']=='1':
            row['Enrollment Fees'] = 225000
        if row['Group'] =='Teenager' and row['Level']=='2':
            row['Enrollment Fees'] = 275000
        if row['Group'] =='Teenager' and row['Level']=='3':
            row['Enrollment Fees'] = 350000

        if row['Group'] == 'Adult' and row['Level'] == '1':
            row['Enrollment Fees'] = 300000
        if row['Group'] == 'Adult' and row['Level'] == '2':
            row['Enrollment Fees'] = 400000
        if row['Group'] == 'Adult' and row['Level'] == '3':
            row['Enrollment Fees'] = 500000

        if row['Status'] == 'Cancelled':
            row['Payment Date'] = 'No Payment'
        if row['Status'] == 'Not Done':
            row['Payment Date'] = 'Processing'
        if row['Status'] == 'Paid':
            row['Payment Date'] = date.today()

        if row['Payment Date'] == date.today():
            row['Package Shipment'] = 'Sent'
        if row['Payment Date'] == 'Processing':
            row['Package Shipment'] = 'On Process'
        if row['Payment Date'] == 'No Payment':
            row['Package Shipment'] = 'No Shipment'

        if row['Package Shipment'] == 'Sent':
            row['Registration Status'] = 'Complete'
        if row['Package Shipment'] == 'On Process':
            row['Registration Status'] = 'Not Complete'
        if row['Package Shipment'] == 'No Shipment':
            row['Registration Status'] = 'Not Complete'
    return rows

@app.callback(
    Output('daftar-baru', 'columns'),
    [Input('adding-columns-button', 'n_clicks')],
    [State('adding-rows-name', 'value'),
     State('daftar-baru', 'columns')],
)
def add_columns(n_clicks, value, existing_columns):
    # print(existing_columns)
    if n_clicks > 0:
        existing_columns.append({
            'name': value, 'id': value,
            'renamable': True, 'deletable': True
        })
    # print(existing_columns)
    return existing_columns


@app.callback(
    Output('daftar-baru', 'data'),
    [Input('editing-rows-button', 'n_clicks')],
    [State('daftar-baru', 'data'),
     State('daftar-baru', 'columns')],
)

def add_row(n_clicks, rows, columns):
    # print(rows)
    if n_clicks > 0:
        rows.append({c['id']: '' for c in columns})
    # print(rows)
    return rows



@app.callback(
    [Output('placeholder', 'children'),
     Output("store", "data")],
    [Input('save_to_csv', 'n_clicks'),
     Input("interval", "n_intervals")],
    [State('daftar-baru', 'data'),
     State('store', 'data')]
)
def df_to_csv(n_clicks, n_intervals, dataset, s):
    output = html.Plaintext("The data has been saved to your folder.",
                            style={'color': 'green', 'font-weight': 'bold', 'font-size': 'large'})
    no_output = html.Plaintext("", style={'margin': "0px"})

    input_triggered = dash.callback_context.triggered[0]["prop_id"].split(".")[0]

    if input_triggered == "save_to_csv":
        s = 6
        df = pd.DataFrame(dataset)
        df.to_csv("Registration_Data.csv")
        return output, s
    elif input_triggered == 'interval' and s > 0:
        s = s-1
        if s > 0:
            return output, s
        else:
            return no_output, s
    elif s == 0:
        return no_output, s




if __name__ == '__main__':
    app.run_server(debug=True, port=9800)

Now I know that Dash can’t have similar callbacks and in my code I have similar callback which are:

@app.callback(
    Output('daftar-baru', 'data'),
    Input('daftar-baru', 'data'),

which has the long if statement and this one

@app.callback(
    Output('daftar-baru', 'data'),
    [Input('editing-rows-button', 'n_clicks')],
    [State('daftar-baru', 'data'),
     State('daftar-baru', 'columns')],

which is used to add new rows to the table. Now one of the solution recommended that I have seen is to combine these two callbacks into one callback but I have no idea how I could do this. I also have tried the solution of using dash extension provided here but it doesn’t work for me either. Can anyone please help me? any help will be greatly appreciated :slight_smile:

What is your issue with the dash extensions solution?

@Susanto - the dash extensions solution should work here, and it a great option.

However, if you decide not to use the MultiplexerTransform, here’s how to combine your callbacks:

Make this a regular python function rather than a callback

@app.callback(
    Output('daftar-baru', 'data'),
    Input('daftar-baru', 'data'),
)
def update_table(rows):

Then call this function from the other callback like this:


@app.callback(
    Output("daftar-baru", "data"),
    Input("editing-rows-button", "n_clicks"),
    Input("daftar-baru", "data"),
    State("daftar-baru", "columns"),
)
def add_row(n_clicks, rows, columns):
    if ctx.triggered_id == "editing-rows-button":
        if n_clicks > 0:
            rows.append({c["id"]: "" for c in columns})
    rows = update_table(rows)

    return rows

Note that you can use the new ctx.triggered_id to see which input triggered the callback rather than

input_triggered = dash.callback_context.triggered[0]["prop_id"].split(".")[0]

Here’s the complete example:

from datetime import date
from dash import Dash, dcc, html, dash_table, Input, Output, State, ctx
from dash.dash_table.Format import Format
import pandas as pd
from collections import OrderedDict

app = Dash(__name__)

datelist = pd.date_range(
    start="2022-09-01",
    periods=365,
).date.tolist()


df = pd.DataFrame(
    OrderedDict(
        [
            ("Name", []),
            ("Group", []),
            ("Level", []),
            ("Enrollment Fees", []),
            ("Status", []),
            ("Payment Date", []),
            ("Package Shipment", []),
            ("Registration Status", []),
        ]
    )
)
df["Name"] = [
    "Anthony",
    "Bernard",
    "Chester",
    "Dylan",
    "Elon",
    "Floyd",
    "Gloria",
    "Hendry",
    "Isaac",
]
df["Group"] = [
    "Children",
    "Children",
    "Children",
    "Teenager",
    "Teenager",
    "Teenager",
    "Adult",
    "Adult",
    "Adult",
]
df["Level"] = ["1", "1", "1", "2", "2", "2", "3", "3", "3"]

df["Status"] = [
    "Paid",
    "Paid",
    "Paid",
    "Not Done",
    "Not Done",
    "Not Done",
    "Cancelled",
    "Cancelled",
    "Cancelled",
]
df["Package Shipment"] = [
    "Sent",
    "Sent",
    "Sent",
    "On Process",
    "On Process",
    "On Process",
    "No Shipment",
    "No Shipment",
    "No Shipment",
]
df["Registration Status"] = [
    "Complete",
    "Not Complete",
    "Complete",
    "Not Complete",
    "Complete",
    "Not Complete",
    "Complete",
    "Not Complete",
    "Complete",
]


app.layout = html.Div(
    [
        html.Div(
            [
                dcc.Input(
                    id="adding-rows-name",
                    placeholder="Enter a column name...",
                    value="",
                    style={"padding": 10},
                ),
                html.Button("Add Column", id="adding-columns-button", n_clicks=0),
            ],
            style={"height": 50},
        ),
        dash_table.DataTable(
            id="daftar-baru",
            data=df.to_dict("records"),
            columns=[
                {"id": "Name", "name": "Name"},
                {"id": "Group", "name": "Group", "presentation": "dropdown"},
                {"id": "Level", "name": "Level", "presentation": "dropdown"},
                {
                    "id": "Enrollment Fees",
                    "name": "Enrollment Fees",
                    "type": "numeric",
                    "format": Format(group=","),
                },
                {"id": "Status", "name": "Status", "presentation": "dropdown"},
                {"id": "Payment Date", "name": "Payment Date", "type": "datetime"},
                {
                    "id": "Package Shipment",
                    "name": "Package Shipment",
                    "presentation": "dropdown",
                },
                {"id": "Delivery Date", "name": "Delivery Date", "type": "datetime"},
                {"id": "Registration Status", "name": "Registration Status"},
            ],
            editable=True,
            filter_action="native",
            style_cell={
                "textAlign": "left",
                "minWidth": "100px",
                "width": "100px",
                "maxWidth": "100px",
            },
            style_cell_conditional=[
                {"if": {"column_id": c}, "textAlign": "right"}
                for c in ["Price", "Sales"]
            ],
            page_current=0,
            page_size=20,
            dropdown={
                "Group": {
                    "options": [{"label": i, "value": i} for i in df["Group"].unique()]
                },
                "Level": {
                    "options": [{"label": i, "value": i} for i in df["Level"].unique()]
                },
                "Status": {
                    "options": [{"label": i, "value": i} for i in df["Status"].unique()]
                },
                "Package Shipment": {
                    "options": [
                        {"label": i, "value": i}
                        for i in df["Package Shipment"].unique()
                    ]
                },
            },
        ),
        html.Button("Add Row", id="editing-rows-button", n_clicks=0),
        html.Button("Export to Excel", id="save_to_csv", n_clicks=0),
        # Create notification when saving to excel
        html.Div(id="placeholder", children=[]),
        dcc.Store(id="store", data=0),
        dcc.Interval(id="interval", interval=1000),
    ]
)


def update_table(rows):
    for row in rows:
        if row["Group"] == "Children" and row["Level"] == "1":
            row["Enrollment Fees"] = 150000
        if row["Group"] == "Children" and row["Level"] == "2":
            row["Enrollment Fees"] = 200000
        if row["Group"] == "Children" and row["Level"] == "3":
            row["Enrollment Fees"] = 250000

        if row["Group"] == "Teenager" and row["Level"] == "1":
            row["Enrollment Fees"] = 225000
        if row["Group"] == "Teenager" and row["Level"] == "2":
            row["Enrollment Fees"] = 275000
        if row["Group"] == "Teenager" and row["Level"] == "3":
            row["Enrollment Fees"] = 350000

        if row["Group"] == "Adult" and row["Level"] == "1":
            row["Enrollment Fees"] = 300000
        if row["Group"] == "Adult" and row["Level"] == "2":
            row["Enrollment Fees"] = 400000
        if row["Group"] == "Adult" and row["Level"] == "3":
            row["Enrollment Fees"] = 500000

        if row["Status"] == "Cancelled":
            row["Payment Date"] = "No Payment"
        if row["Status"] == "Not Done":
            row["Payment Date"] = "Processing"
        if row["Status"] == "Paid":
            row["Payment Date"] = date.today()

        if row["Payment Date"] == date.today():
            row["Package Shipment"] = "Sent"
        if row["Payment Date"] == "Processing":
            row["Package Shipment"] = "On Process"
        if row["Payment Date"] == "No Payment":
            row["Package Shipment"] = "No Shipment"

        if row["Package Shipment"] == "Sent":
            row["Registration Status"] = "Complete"
        if row["Package Shipment"] == "On Process":
            row["Registration Status"] = "Not Complete"
        if row["Package Shipment"] == "No Shipment":
            row["Registration Status"] = "Not Complete"
    return rows


@app.callback(
    Output("daftar-baru", "columns"),
    [Input("adding-columns-button", "n_clicks")],
    [State("adding-rows-name", "value"), State("daftar-baru", "columns")],
)
def add_columns(n_clicks, value, existing_columns):
    # print(existing_columns)
    if n_clicks > 0:
        existing_columns.append(
            {"name": value, "id": value, "renamable": True, "deletable": True}
        )
    # print(existing_columns)
    return existing_columns


@app.callback(
    Output("daftar-baru", "data"),
    Input("editing-rows-button", "n_clicks"),
    Input("daftar-baru", "data"),
    State("daftar-baru", "columns"),
)
def add_row(n_clicks, rows, columns):
    if ctx.triggered_id == "editing-rows-button":
        if n_clicks > 0:
            rows.append({c["id"]: "" for c in columns})
    rows = update_table(rows)

    return rows


@app.callback(
    [Output("placeholder", "children"), Output("store", "data")],
    [Input("save_to_csv", "n_clicks"), Input("interval", "n_intervals")],
    [State("daftar-baru", "data"), State("store", "data")],
)
def df_to_csv(n_clicks, n_intervals, dataset, s):
    output = html.Plaintext(
        "The data has been saved to your folder.",
        style={"color": "green", "font-weight": "bold", "font-size": "large"},
    )
    no_output = html.Plaintext("", style={"margin": "0px"})

    if ctx.triggered_id == "save_to_csv":
        s = 6
        df = pd.DataFrame(dataset)
        df.to_csv("Registration_Data.csv")
        return output, s
    elif ctx.triggered_id == "interval" and s > 0:
        s = s - 1
        if s > 0:
            return output, s
        else:
            return no_output, s
    elif s == 0:
        return no_output, s


if __name__ == "__main__":
    app.run_server(debug=True)

2 Likes

@Emil Hi, thank you for reaching out. So when I try to use the dash-extension, it gives me this error:


What I did with the code is changing:

app = Dash(__name__)

into this:

app = DashProxy(prevent_initial_callbacks=True, transforms=[MultiplexerTransform()])

DId I do something wrong or did I miss to put something else on the code?

@AnnMarieW Hi, thank you for reaching out. So I tried using the code you provided. But there is one small problem for me. So previously the Enrollment Fees column will automatically fill based on the candidate Group and Level but this time to update the Enrollment Fees column (and maybe all the other column that was included in the IFs statement) I will have to press the “Add Row” button first. This is a small problem though, I will try to find the workaround for this :slight_smile:

@Susanto
Huh, that’s odd. It works when I try it:

table_update

@AnnMarieW Sorry, previously I did not remove : State('daftar-baru', 'data') from the callbacks. The code works perfectly fine now :slight_smile:

1 Like