Editable DataTable, PreventUpdate

Hi!
Does no_update or exceptions.PreventUpdate works for DataTable ?

I need to block changes in some cells in editable table by some criteria (cells, rows or values).

Or it can be solved only with JavaScript hardcoding?

Hey Viktor (@texikator ),
:wave: welcome to the community,

As far as I know they are not necessarily connected, but I’m happy to look more into it if you provide a minimal reproducible example.
The dash.no_update means that, although the callback is triggered, it will not update certain callback outputs. Therefore, if the data property of the DataTable is a callback output, and you return dash.no_update, that DataTable should not be updated.

Thank you, adamschroeder !

I tried to use this code.
PreventUpdate and no_update does not work.

from dash import Dash, Input, Output, callback, dash_table, html, callback_context, dcc, State, no_update


app = Dash(__name__)

app.layout = html.Div([
    dash_table.DataTable(
        id='editing-prune-data',
        columns=[{
            'name': 'Column {}'.format(i),
            'id': 'column-{}'.format(i)
        } for i in range(1, 5)],
        data=[
            {'column-{}'.format(i): (j + (i-1)*5) for i in range(1, 5)}
            for j in range(5)
        ],
        editable=True
    ),
    html.Div(id='editing-prune-data-output')
])


@app.callback(Output('editing-prune-data', 'data'),
              Input('editing-prune-data', 'data'))
def display_output(rows):

    # raise dash.exceptions.PreventUpdate
    return no_update

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

Where is a mistake there …

My dash version is 2.6.1

Hi @texikator,

Here’s an example (extended from your code) of “locking” edits made on a specific column by returning the previous table state. You can take this further by locking down edits made on rows, or edits made on specific values as well. See the DataTable reference for more information, specifically active_cell, selected_cells, start_cell, and end_cell.

from dash import Dash, Input, Output, callback, dash_table, html, callback_context, dcc, State, no_update

app = Dash(__name__)

app.layout = html.Div([
    dash_table.DataTable(
        id='editing-prune-data',
        columns=[{
            'name': 'Column {}'.format(i),
            'id': 'column-{}'.format(i)
        } for i in range(1, 5)],
        data=[
            {'column-{}'.format(i): (j + (i - 1) * 5) for i in range(1, 5)}
            for j in range(5)
        ],
        editable=True
    ),
    html.Div(id='editing-prune-data-output')
])


@app.callback(
    Output('editing-prune-data', 'data'),
    Input('editing-prune-data', 'data'),
    [State('editing-prune-data', 'data_previous'),
     State('editing-prune-data', 'active_cell'), ],
    prevent_initial_call=True
)
def display_output(new_rows, previous_rows, selected_cell):

    # if we're editing column 2, revert any changes
    if selected_cell['column_id'] == 'column-2':
        return previous_rows
    else:
        return new_rows


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

2 Likes

Thank you, [3d65](Profile - 3d65 - Plotly Community Forum !

It works! but ai see a some strange thing: if i navigate in this column via UP and DOWN keys, or if i change cell via mouse click - it works fine, but when i use LEFT and RIGHT keys - it’s works partially, some times in another columns …

@texikator

That’s pretty interesting; I assume this is due to a table data update not being triggered until focusing a new row, as editing data on a record (row) basis is more common and expected.

If you want to keep it simple and lock down columns, you can follow the built in functionality, specifying editable=False in the table definition on columns you want to lock. Here’s an example where a column is locked, and styled appropriately.

from dash import Dash, dash_table, html

app = Dash(__name__)

app.layout = html.Div([
    dash_table.DataTable(
        id='editing-prune-data',
        columns=[{
            'name': 'Column {}'.format(i),
            'id': 'column-{}'.format(i),
            'editable': False if i == 3 else True
        } for i in range(1, 5)],
        data=[
            {'column-{}'.format(i): (j + (i - 1) * 5) for i in range(1, 5)}
            for j in range(5)
        ],
        style_data_conditional=[{
            'if': {'column_editable': False},
            'backgroundColor': '#9c9c9c',
            'border': '1px solid #5f5f5f',
            'color': '#535353'
        }],
        style_header_conditional=[{
            'if': {'column_editable': False},
            'backgroundColor': '#9c9c9c',
            'border': '1px solid #5f5f5f',
            'color': '#535353'
        }],
        editable=True
    ),
    html.Div(id='editing-prune-data-output')
])

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

No, settings on culumn-level isn’t variant in my case.

Hi @texikator

I think @3d65 is on the right track. You could try comparing just the data and data_previous If any of the changes are in the blocked fields you could return the data_previous. Then you wouldn’t need the active_cell prop.

1 Like

One thing to add based on my experience, data_timestamp will be triggered when a cell value has changed. Instead of actice_cell, use data_timestamp as the input. Then you know the data has been modified, not just clicked on.

For what it’s worth.

@texikator
Following up, here’s a working example of how to lock rows and columns using what @AnnMarieW mentioned.

from dash import Dash, Input, Output, callback, dash_table, html, callback_context, dcc, State, no_update

app = Dash(__name__)

app.layout = html.Div([
    dash_table.DataTable(
        id='editing-prune-data',
        columns=[{
            'name': 'Column {}'.format(i),
            'id': 'column-{}'.format(i)
        } for i in range(1, 5)],
        data=[
            {'column-{}'.format(i): (j + (i - 1) * 5) for i in range(1, 5)}
            for j in range(5)
        ],
        editable=True
    ),
    html.Div(id='editing-prune-data-output')
])


@app.callback(
    Output('editing-prune-data', 'data'),
    Input('editing-prune-data', 'data'),
    [State('editing-prune-data', 'data_previous')],
    prevent_initial_call=True
)
def display_output(new_rows, prev_rows):

    # check if every row in target_col is the same
    target_col = 'column-2'
    col_no_change = [prev[target_col] == new[target_col] for prev, new in zip(prev_rows, new_rows)]

    # check if every column in row 0 is the same
    target_row = 0
    row_no_change = [prev == new for prev, new in zip(prev_rows[target_row].values(), new_rows[target_row].values())]

    # if we haven't changed anything in target_col or target_row, proceed with the update
    if all(col_no_change) and all(row_no_change):
        return new_rows
    return prev_rows


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

2 Likes