Allow user to edit user-uploaded DataTable

Hi!

I’m having trouble structuring callbacks in my dash such that users are able to edit an uploaded DataTable. I have a test example below, which is a barely-modified version of the dcc.Upload example, where I intialize an empty DataTable with the columns I want (“in1”,“in2”, and “out”), and callbacks such that when the DataTable is edited, it is updated so that “out” is the result of adding “in1” and “in2” (row-wise).

Ideally the user should be able to upload an excel or csv file that looks like this:

in1 in2 out
10 15 NA

And see a table that looks like this:

in1 in2 out
10 15 25

Which updates the “out” column whenever the user changes a value in “in1” or “in2”.

My first idea was to listen to the State of the upload’s contents component, and take the timestamps from both the DataTable and the upload widget as inputs. When the upload widget gets a new file, it would trigger throwing the uploaded data into the DataTable, and then clear the contents of the upload widget. Then, if the user changes the DataTable, the callback would see that the contents of the upload widget are None, and just update the existing data. However, I keep getting

Invalid argument `data[0]` passed into DataTable with ID "output-data-upload".
Expected `object`.
Was supplied type `array`.

when I try this. Is there some much easier way of doing this that I’m missing completely? Full code below (ignore empty second tab).

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_table
import base64
import pandas as pd
import numpy as np

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Tabs([
        dcc.Tab(label='Main tab', children=[
            dcc.Upload(
                id='upload-data',
                children=html.Div([
                    'Drag and Drop or ',
                    html.A('Select Files')
                ]),
                style={
                    'width': '100%',
                    'height': '60px',
                    'lineHeight': '60px',
                    'borderWidth': '1px',
                    'borderStyle': 'dashed',
                    'borderRadius': '5px',
                    'textAlign': 'center',
                    'margin': '10px'
                },
                # Allow multiple files to be uploaded
                multiple=True
            ),
            html.Div(dash_table.DataTable(
                id='output-data-upload',
                data=[],
                columns=[{'name': i, 'id': i} for i in ['in1','in2','out']],
                editable=True))
        ]),

        dcc.Tab(label='Tab2', children=[
            dcc.Input(id='string',type='text',debounce=True)
        ])
    ])
])

def parse_contents(contents, filename, date):
    content_type, content_string = contents.split(',')

    decoded = base64.b64decode(content_string)
    try:
        if 'csv' in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(
                io.StringIO(decoded.decode('utf-8')))
        elif 'xls' in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded))
    except Exception as e:
        print(e)
        return html.Div([
            'There was an error processing this file.'
        ])

    return df.to_dict('records')


@app.callback([Output('output-data-upload', 'data'),
               Output('upload-data','contents')],
              [Input('upload-data', 'last_modified'),
               Input('output-data-upload','data_timestamp')],
              [State('upload-data', 'filename'),
               State('upload-data', 'contents'),
               State('output-data-upload','data')])
def update_output(upload_times, data_time, filenames, upload_contents, data_rows):
    if upload_contents is not None:
        data = [parse_contents(c, n, d) for c, n, d in zip(upload_contents, filenames, upload_times)]
        return data, None
    else:
        for row in data_rows:
            row['out'] = float(row['in1']) + float(row['in2'])
        return data_rows, None

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