Multiapp and serialization problem

Hello there. I’m trying to create Multiapp, and idea is next:

Person opens page, on this page only datepicker and button “search”. Person choose start date,end date, then click “Search” and on page appears table with data,that was generated in back using chosen date range.
The problems I faced are:

  1. I don’t know how to track was button clicked or not. I tried n_clicks, but it returns numbers. I don’t want to save previous number and then compare with current. I tried to use n_clicks_timestamp, and compare it with current date and time -1 sec as is How to update component when button is clicked, but I always receive ‘list index out of range error’. All what I need is to generate table on click, and since generation of table for me is not a big deal,I would appreciate any samples of code with button click even callbacks
  2. I tried to separate the data for users, because at least 10 people will use my service, and every person should see their own data.
    First, ofc, I looked inside manual there:
    https://dash.plot.ly/sharing-data-between-callbacks
    I tried to use variant 4. but since I generate table via html.Table, I made a list with Tr ,Td,Th objects, that could not be serialized. And because of this it is not working. Maybe you can give me advice with samples how to split work for users.
    Because it’s multiapps I use next structure:
    |index.py
    |app.py
    | apps
    –|| app1.py
    –||app2.py
    There is the code for every file:
    index.py:
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

from app import app
from apps import app2, app1


app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])


@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/apps/app1':
        return app1.layout
    elif pathname == '/apps/app2':
        return app2.layout
    else:
        return app1.layout

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

app.py:

import dash
import dash_bootstrap_components as dbc

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

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
server = app.server
app.config.suppress_callback_exceptions = True

app1.py:

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import data_processor
from app import app
import pandas as pd
import dash_bootstrap_components as dbc
from datetime import datetime as dt
from itertools import islice
import time
import uuid
from flask_caching import Cache
import json
import datetime
import dash

data = data_processor.get_data("overview_critical_calls.csv",
                               1)  # There we read the data from first .csv,with Call statistics
data2 = data_processor.get_data('alfa_gewerke_counts.csv', 2)  # there we read the data from second .csv, with Gewerke

columnnames = ['Customer', 'Calls', 'Hours']  # columns name for the dataframe for 1st file

moderdf = pd.DataFrame.from_records(data,
                                    columns=columnnames)  # creation of the dataframe with pandas for the global statistics

# There we describe cache congif
cache = Cache(app.server, config={
    'CACHE_TYPE': 'redis',
    # Note that filesystem cache doesn't work on systems with ephemeral
    # filesystems like Heroku.
    'CACHE_TYPE': 'filesystem',
    'CACHE_DIR': 'cache-directory',

    # should be equal to maximum number of users on the app at a single time
    # higher numbers will store more data in the filesystem / redis cache
    'CACHE_THRESHOLD': 200
})

"""
In function below we generate links for every customer.
As input we use data2 (prepared above) and customer name
"""


def prepare_link_data(dataframe, customer):
    arglist = []
    if customer in dataframe.keys():
        for keys in dataframe[customer].keys():
            tempquantity = dataframe[customer][keys]
            arglist.append(html.A(keys + '(' + tempquantity + ')', target='_blank', href='http://google.com'))
            arglist.append("  ")

    return arglist


"""
Function below (prepare_table_data) prepare data for the Table we built on the page.
As input we use dataframe, that was prepared before.
"""


def prepare_table_data(dataframe, max_rows=26):
    rows = []
    columns = []
    for col in dataframe.columns:
        columns.append(html.Th(col))
    rows.append(html.Tr(columns))  # There we prepare headers for the table

    for i in range(min(len(dataframe), max_rows)):
        columns = []

        for col in dataframe.columns:  # There we prepare rows
            if col == "Customer":  # We don't need to make dropdown  elements for every cell, only for 1st column

                columns.append(  # Default color scheme is white background,font is black
                    html.Td(html.Details([html.Summary(dataframe.iloc[i][col], style={'backgroundColor': '#ffffff',
                                                                                      'color': 'black'}),
                                          *prepare_link_data(data2, dataframe.iloc[i][col])],
                                         style={'border': '1px solid black', 'border-collapse': 'collapse'})))
            else:  # This 'else' for all cells that are not included in 1st column

                columns.append(
                    html.Td(dataframe.iloc[i][col], style={'border': '1px solid black', 'border-collapse': 'collapse'}))

        rows.append(html.Tr(columns))  # There we append headers for the table and rows

    for elements in islice(rows, 1, None):  # in this cycle we change the style of every cell of needed
        if float(elements.children[1].children) >= 11:  # if "Calls" is >=11 - then red
            elements.children[0].children.children[0].style['backgroundColor'] = '#ff0000'
        elif 5 <= float(elements.children[1].children) < 11:  # if 5< = "Calls"<11 - then orange
            elements.children[0].children.children[0].style['backgroundColor'] = '#ff8000'
        elif 3 <= float(elements.children[1].children) < 5:  # if 3 < = "Calls" < 5 - then yellow
            elements.children[0].children.children[0].style['backgroundColor'] = '#ffff00'

    return rows


def get_dataframe(session_id):
    @cache.memoize()
    def query_and_serialize_data(session_id):
        data = json.dumps(prepare_table_data(moderdf))
        return data

    return pd.read_json(query_and_serialize_data(session_id))


# blue navigation bar on top of the page ( we can add some link in it later)
navbar = dbc.NavbarSimple(

    brand="Overview",
    brand_href="",
    color="primary",
    dark=True,
)

# There we prepare body using bootstrap enveloper for dash.
body = dbc.Container(
    [
        dbc.Row(
            [
                dbc.Col(children=[html.H2('Raw calls statistic') ,html.Table(prepare_table_data(moderdf), style={'width': '600px'})], id='table_column'
                        ),
                dbc.Col(
                    [
                        html.H1("Pick date range", id='date_range_header'),  # Date picker. Need to be replaced
                        dcc.DatePickerRange(
                            id='my-date-picker-range',
                            min_date_allowed=dt(1995, 8, 5),
                            max_date_allowed=dt(2030, 1, 1),
                            initial_visible_month=dt(dt.today().year, dt.today().month, dt.today().day),
                            end_date=dt(dt.today().year, dt.today().month, dt.today().day)
                        ),

                        dbc.Button('Search', id='submit_button', outline=True, color='primary')  # Button, not working

                    ]
                )

            ]
        )
    ],
    className="mt-4",
)


# There we prepare layout for user
def prepare_layout():
    session_id = str(uuid.uuid4())
    return html.Div([
        navbar,
        html.Div(session_id, id='session-id', style={'display': 'none'}),
        html.Div(children=[
            body

        ], id='div_for_table'),
        dcc.Location(id='url', refresh=False),
        html.Div(id='app2_content')

    ])


# Creation of the layout for app
# the sequence of the arguments is very important: if navbar will be second, then it will fall down as footer
layout = html.Div([
    navbar,
    body,
dcc.Location(id='url', refresh=False)
])

As you can see in this code there are some parts prepared for using cache and sessions, but since they didn’t work I didn’t include them in body. Note that I deleted callbacks in app1.py, because they didn’t work anyway.

I changed code in app1.py a little bit to show problem I faced with callback from button:

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import data_processor
from app import app
import pandas as pd
import dash_bootstrap_components as dbc
from datetime import datetime as dt
from itertools import islice
import time
import uuid
from flask_caching import Cache
import json
import datetime
import dash

data = data_processor.get_data("overview_critical_calls.csv",
                               1)  # There we read the data from first .csv,with Call statistics
data2 = data_processor.get_data('alfa_gewerke_counts.csv', 2)  # there we read the data from second .csv, with Gewerke

columnnames = ['Customer', 'Calls', 'Hours']  # columns name for the dataframe for 1st file

moderdf = pd.DataFrame.from_records(data,
                                    columns=columnnames)  # creation of the dataframe with pandas for the global statistics

# There we describe cache congif
cache = Cache(app.server, config={
    'CACHE_TYPE': 'redis',
    # Note that filesystem cache doesn't work on systems with ephemeral
    # filesystems like Heroku.
    'CACHE_TYPE': 'filesystem',
    'CACHE_DIR': 'cache-directory',

    # should be equal to maximum number of users on the app at a single time
    # higher numbers will store more data in the filesystem / redis cache
    'CACHE_THRESHOLD': 200
})

"""
In function below we generate links for every customer.
As input we use data2 (prepared above) and customer name
"""


def prepare_link_data(dataframe, customer):
    arglist = []
    if customer in dataframe.keys():
        for keys in dataframe[customer].keys():
            tempquantity = dataframe[customer][keys]
            arglist.append(html.A(keys + '(' + tempquantity + ')', target='_blank', href='http://google.com'))
            arglist.append("  ")

    return arglist


"""
Function below (prepare_table_data) prepare data for the Table we built on the page.
As input we use dataframe, that was prepared before.
"""


def prepare_table_data(dataframe, max_rows=26):
    rows = []
    columns = []
    for col in dataframe.columns:
        columns.append(html.Th(col))
    rows.append(html.Tr(columns))  # There we prepare headers for the table

    for i in range(min(len(dataframe), max_rows)):
        columns = []

        for col in dataframe.columns:  # There we prepare rows
            if col == "Customer":  # We don't need to make dropdown  elements for every cell, only for 1st column

                columns.append(  # Default color scheme is white background,font is black
                    html.Td(html.Details([html.Summary(dataframe.iloc[i][col], style={'backgroundColor': '#ffffff',
                                                                                      'color': 'black'}),
                                          *prepare_link_data(data2, dataframe.iloc[i][col])],
                                         style={'border': '1px solid black', 'border-collapse': 'collapse'})))
            else:  # This 'else' for all cells that are not included in 1st column

                columns.append(
                    html.Td(dataframe.iloc[i][col], style={'border': '1px solid black', 'border-collapse': 'collapse'}))

        rows.append(html.Tr(columns))  # There we append headers for the table and rows

    for elements in islice(rows, 1, None):  # in this cycle we change the style of every cell of needed
        if float(elements.children[1].children) >= 11:  # if "Calls" is >=11 - then red
            elements.children[0].children.children[0].style['backgroundColor'] = '#ff0000'
        elif 5 <= float(elements.children[1].children) < 11:  # if 5< = "Calls"<11 - then orange
            elements.children[0].children.children[0].style['backgroundColor'] = '#ff8000'
        elif 3 <= float(elements.children[1].children) < 5:  # if 3 < = "Calls" < 5 - then yellow
            elements.children[0].children.children[0].style['backgroundColor'] = '#ffff00'

    return rows


def get_dataframe(session_id):
    @cache.memoize()
    def query_and_serialize_data(session_id):
        data = json.dumps(prepare_table_data(moderdf))
        return data

    return pd.read_json(query_and_serialize_data(session_id))


# blue navigation bar on top of the page ( we can add some link in it later)
navbar = dbc.NavbarSimple(

    brand="Overview",
    brand_href="",
    color="primary",
    dark=True,
)

# There we prepare body using bootstrap enveloper for dash.
body = dbc.Container(
    [
        dbc.Row(
            [
                dbc.Col(children=[], id='experiment'
                        ),
                dbc.Col(
                    [
                        html.H1("Pick date range", id='date_range_header'),  # Date picker. Need to be replaced
                        dcc.DatePickerRange(
                            id='my-date-picker-range',
                            min_date_allowed=dt(1995, 8, 5),
                            max_date_allowed=dt(2030, 1, 1),
                            initial_visible_month=dt(dt.today().year, dt.today().month, dt.today().day),
                            end_date=dt(dt.today().year, dt.today().month, dt.today().day)
                        ),

                        dbc.Button('Search', id='submit_button', outline=True, color='primary')  # Button, not working

                    ]
                )

            ]
        )
    ],
    className="mt-4",
)


# There we prepare layout for user
def prepare_layout():
    session_id = str(uuid.uuid4())
    return html.Div([
        navbar,
        html.Div(session_id, id='session-id', style={'display': 'none'}),
        html.Div(children=[
            body

        ], id='div_for_table'),
        dcc.Location(id='url', refresh=False),
        html.Div(id='app2_content')

    ])


# Creation of the layout for app
# the sequence of the arguments is very important: if navbar will be second, then it will fall down as footer
layout = html.Div([
    navbar,
    body,
dcc.Location(id='url', refresh=False)
])

@app.callback(
    Output("experiment", "children"), [Input("submit_button", "n_clicks")]
)
def on_button_click(n):
    if n is None:
        return html.H1('Kookook!')
        pass
    else:
        print('Kookoo!')
        return html.H3('Pikaboo!')

The problem is, that ‘experiment’ element is always being rewritted by empty place, so when I click button text appears, but immediately disappears. I understood why - because I’m making multiapp. I made some tests in separate application, and in it everything works perfect with button. Anyone know how to make the same with multiapp?

Will appreciate any help.

Didn’t have time to look at part 2 of your post. For the first bit, you should use callback context to understand which button was clicked. Gives a lot of additional information, but you’d use something like ctx.triggered[0]['prop_id'].split('.')[0]

See this post for an example:

Yeah, I also tried to make it in this way. Maybe I don’t understand something, but:
when callback happens, that means that something was pressed. What exactly-another problem, but:

@app.callback(
    Output(component_id="experiment", component_property="children"), [Input(component_id="submit_button", component_property="n_clicks")]
)

def button_clicked(clicked):
     if clicked>0:
      print('Pikabook!')
      return html.H3('Kokook!')

Should send H3 to children property of experiment, that I defined above:

body = dbc.Container(
    [
        dbc.Row(
            [
                dbc.Col(html.Div(children=[],id='experiment')
                        ),
                dbc.Col(
                    [
                        html.H1("Pick date range", id='date_range_header'),  # Date picker. Need to be replaced
                        dcc.DatePickerRange(
                            id='my-date-picker-range',
                            min_date_allowed=dt(1995, 8, 5),
                            max_date_allowed=dt(2030, 1, 1),
                            initial_visible_month=dt(dt.today().year, dt.today().month, dt.today().day),
                            end_date=dt(dt.today().year, dt.today().month, dt.today().day)
                        ),

                        dbc.Button('Search', id='submit_button', outline=True, color='primary', n_clicks=0)
                        # Button, not working

                    ]
                )

            ]
        )
    ],
    className="mt-4",
)

however, this is not happening. When I’m clicking button as crazy-it appears for , like, 0.5 sec and then fade .
Maybe there is a mess with layouts, but I tried to reproduce from this example:
https://dash.plot.ly/urls
Structuring a Multi-Page App

My best friend found the problem - it was in dcc.Location, that were located in each app. So they were looped between each other, leaving only one dcc.Location in index.py solved the problem.