Above solution was very helpful but I think it missed some things (wouldn’t fire if you deleted an item since it shows up as a null value. Solution might have other troubles with blanks too?). I modified to:
def diff_dashtable(data, data_previous, row_id_name=None):
"""Generate a diff of Dash DataTable data.
Modified from: https://community.plotly.com/t/detecting-changed-cell-in-editable-datatable/26219/2
Parameters
----------
data: DataTable property (https://dash.plot.ly/datatable/reference)
The contents of the table (list of dicts)
data_previous: DataTable property
The previous state of `data` (list of dicts).
Returns
-------
A list of dictionaries in form of [{row_id_name:, column_name:, current_value:,
previous_value:}]
"""
df, df_previous = pd.DataFrame(data=data), pd.DataFrame(data_previous)
if row_id_name is not None:
# If using something other than the index for row id's, set it here
for _df in [df, df_previous]:
# Why do this? Guess just to be sure?
assert row_id_name in _df.columns
_df = _df.set_index(row_id_name)
else:
row_id_name = "index"
# Pandas/Numpy says NaN != NaN, so we cannot simply compare the dataframes. Instead we can either replace the
# NaNs with some unique value (which is fastest for very small arrays, but doesn't scale well) or we can do
# (from https://stackoverflow.com/a/19322739/5394584):
# Mask of elements that have changed, as a dataframe. Each element indicates True if df!=df_prev
df_mask = ~((df == df_previous) | ((df != df) & (df_previous != df_previous)))
# ...and keep only rows that include a changed value
df_mask = df_mask.loc[df_mask.any(axis=1)]
changes = []
# This feels like a place I could speed this up if needed
for idx, row in df_mask.iterrows():
row_id = row.name
# Act only on columns that had a change
row = row[row.eq(True)]
for change in row.iteritems():
changes.append(
{
row_id_name: row_id,
"column_name": change[0],
"current_value": df.at[row_id, change[0]],
"previous_value": df_previous.at[row_id, change[0]],
}
)
return changes