I’m trying to have a site with multiple pages that are all tied to a postgreSQL database, In each of the pages I move items between these tables.
I’ve read the information at Live Updates | Dash for Python Documentation | Plotly
but for multipage, if I try to return "app.layout = serve_layout
not app.layout = serve_layout()
" this results in an error. if I return serve_layout() - the movement occurs in the database but the datatable I generate does not update.
Has anyone found a solution for multipage dynamic callbacks?
Index.py:
import dash
import dash_bootstrap_components as dbc
from dash import Input, Output, dcc, html
from apps import deactlotassign, ovendash, packqueue, stuffboard
from app import app
# the style arguments for the sidebar. We use position:fixed and a fixed width
SIDEBAR_STYLE = {
"position": "fixed",
"top": 0,
"left": 0,
"bottom": 0,
"width": "16rem",
"padding": "2rem 1rem",
"background-color": "#f8f9fa",
}
# the styles for the main content position it to the right of the sidebar and
# add some padding.
CONTENT_STYLE = {
"margin-left": "18rem",
"margin-right": "2rem",
"padding": "2rem 1rem",
}
sidebar = html.Div(
[
html.H2("Liners", className="display-4"),
html.Hr(),
html.P(
"Manufacturing Dashboard", className="lead"
),
dbc.Nav(
[
dbc.NavLink("Home", href="/", active="exact"),
dbc.NavLink("Oven Queue", href="/oven", active="exact"),
dbc.NavLink("Stuffing Dashboard", href="/stuff", active="exact"),
dbc.NavLink("Deactivation Lot Assignment", href="/deactq", active="exact"),
dbc.NavLink("Packaging Dashboard", href="/pack", active="exact"),
],
vertical=True,
pills=True,
),
],
style=SIDEBAR_STYLE,
)
content = html.Div(id="page-content", style=CONTENT_STYLE)
### Page container ###
def page_container():
return html.Div(
children=[
# represents the URL bar, doesn't render anything
dcc.Location(
id='url', refresh=True
),
sidebar,
# content will be rendered in this element
html.Div(id='page-content', style=CONTENT_STYLE)
]
)
### Set app layout to page container ###
app.layout = page_container
### Assemble all layouts ###
app.validation_layout = html.Div(
children = [
page_container,
sidebar,
ovendash.layout,
stuffboard.layout,
deactlotassign.layout,
packqueue.layout
]
)
#app.layout = html.Div([dcc.Location(id="url"), sidebar, content])
@app.callback(
Output(
component_id='page-content',
component_property='children',
),
[Input(
component_id='url',
component_property='pathname',
)]
)
def render_page_content(pathname):
if pathname == "/":
return html.P("This is the content of the home page!")
elif pathname == "/oven":
return ovendash.layout
elif pathname == "/stuff":
return stuffboard.layout
elif pathname == "/deactq":
return deactlotassign.layout
elif pathname == "/pack":
return packqueue.layout
# If the user tries to reach a different page, return a 404 message
return dbc.Jumbotron(
[
html.H1("404: Not found", className="text-danger"),
html.Hr(),
html.P(f"The pathname {pathname} was not recognised..."),
]
)
if __name__ == "__main__":
app.run_server(debug = True, port=8888)
One of the Pages:
import logging
import dash
import datetime
from datetime import date
from dash.dash_table.Format import Align
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table as dt
from dash_table.Format import Format, Group
import dash_table as dt
import dash_bootstrap_components as dbc
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
import plotly.figure_factory as ff
from openpyxl import load_workbook
import config
import linerplan
import psycopg2
from app import app
# assume you have a "long-form" data frame
# see https://plotly.com/python/px-arguments/ for more options
CNXPOSTGRE = {"host":config.host, "port":config.port, "user":config.user, "password":config.pwd, "dbname":config.dbname}
conn = psycopg2.connect(**CNXPOSTGRE)
cursor = conn.cursor()
""" PostGRES Fetch command, returns Lookup Lists above """
def PGconnect():
try:
conn = psycopg2.connect(dbname=config.dbname, host=config.host, port=config.port,\
user=config.user, password=config.pwd)
cursor = conn.cursor()
pndata = linerplan.RAWlookup(cursor)
kitplan = linerplan.Kitlookup(cursor)
woolneed = linerplan.Woollookup(cursor)
woolplan = linerplan.Woolstuff(cursor)
packsize = linerplan.Packlookup(cursor)
packplan = linerplan.PackDist(cursor)
csubdata = linerplan.CSUBlookup(cursor)
req = linerplan.Reqplan(cursor)
stfq = linerplan.stuffqlookup(cursor)
deactq = linerplan.deactqlookup(cursor)
except (Exception, psycopg2.Error) as error :
logging.fatal("Error while fetching raw data from PostGRES DB")
finally:
#closing database connection.
if(conn):
cursor.close()
conn.close()
logging.debug("PG read connection successfully closed")
return req, pndata, packsize, woolneed, kitplan, woolplan, packplan, csubdata, stfq, deactq
req, pndata, packsize, woolneed, kitplan, woolplan, packplan, csubdata, stfq, deactq = PGconnect()
csubdict = dict(csubdata)
csubdict['G3901-56001'] = "5190-2292 (100 PK)"
csubdict['G3901-56002'] = "5190-2293 (100 PK)"
csubdict['G3901-56003'] = "5190-2294 (100 PK)"
csubdict['G3901-56004'] = "5190-2295 (100 PK)"
def deactiveq(cursor, csubdict):
tempq = linerplan.deactqlookup(cursor)
deactq = []
deactwip = 0
deacttable = {}
for item in tempq:
deactq.append(list(item))
for item in deactq:
deacttable[csubdict[item[0]]] = deacttable.get(csubdict[item[0]], 0)+item[1]
if item[0][0:5] == "G3901":
deactwip = deactwip + (item[1]/90)
return (deactwip, deacttable)
ovenfull = 42
deactwip, deacttable = deactiveq(cursor, csubdict)
df = pd.DataFrame(deacttable.items(), columns =['material', 'count'])
df['id'] = df['material']
df.set_index('id', inplace=True, drop=False)
def serve_layout():
deactwip, deacttable = deactiveq(cursor, csubdict)
df = pd.DataFrame(deacttable.items(), columns =['material', 'count'])
df['id'] = df['material']
df.set_index('id', inplace=True, drop=False)
return html.Div([
dcc.Store(id='local', storage_type='local'),
dcc.ConfirmDialog(
id='confirm-danger',
message='Deact ID About to be issued. Are you sure you want to continue?',
),
html.H1(children="Deactivation Lot Assignment", style={'textAlign':'center', 'font-family': 'Comic Sans MS'}),
html.Div(
dt.DataTable(
id='datatable-interactivity',
columns=[
{'name': i, 'id': i, 'deletable': True} for i in df.columns
# omit the id column
if i != 'id'
],
data=df.to_dict('records'),
editable=True,
filter_action="native",
sort_action="native",
sort_mode='multi',
row_deletable=True,
selected_rows=[],
page_action='native',
page_current= 0,
page_size= 10),
style={'margin-left':'25%', 'textAlign':'center', 'width':'50%'}),
html.Div(id='queue-container'),
html.Div(
dcc.ConfirmDialogProvider(
children=html.Button('Submit',),
id='danger-danger-provider',
message='Issuing Deactivation Batch Number! Are you sure you want to continue?'),
style={'margin-left':'25%', 'textAlign':'center', 'width':'50%'}),
html.Div(id='output-provider', style={'font-size': '44px', 'font-family': 'Comic Sans MS', 'textAlign':'center'})
])
layout = serve_layout()
@app.callback(
Output('queue-container', "children"),
Input('datatable-interactivity', "derived_virtual_data"),
Input('datatable-interactivity', "derived_virtual_selected_rows"))
def update_graphs(rows, derived_virtual_selected_rows):
# When the table is first rendered, `derived_virtual_data` and
# `derived_virtual_selected_rows` will be `None`. This is due to an
# idiosyncrasy in Dash (unsupplied properties are always None and Dash
# calls the dependent callbacks when the component is first rendered).
# So, if `rows` is `None`, then the component was just rendered
# and its value will be the same as the component's dataframe.
# Instead of setting `None` in here, you could also set
# `derived_virtual_data=df.to_rows('dict')` when you initialize
# the component.
conn = psycopg2.connect(dbname=config.dbname, host=config.host, port=config.port, user=config.user, password=config.pwd)
cursor = conn.cursor()
deactwip, deacttable2 = deactiveq(cursor, csubdict)
ovenfull = 42
df = pd.DataFrame(deacttable2.items(), columns =['material', 'count'])
df['id'] = df['material']
df.set_index('id', inplace=True, drop=False)
if derived_virtual_selected_rows is None:
derived_virtual_selected_rows = []
dff = df if rows is None else pd.DataFrame(rows)
dff['count'] = dff['count'].astype(int)
total = dff['count'].sum()
trace2 = go.Indicator(
mode = "gauge+number",
value = (total/ovenfull),
number = {'suffix': "%"},
title = {'text': "Oven Capacity", 'font': {'size': 36}},
gauge = {
'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"},
'bar': {'color': "darkblue"},
'bgcolor': "white",
'borderwidth': 2,
'bordercolor': "gray",
'steps': [
{'range': [0, 75], 'color': 'lightblue'},
{'range': [75, 90], 'color': 'yellow'},
{'range': [90, 100], 'color': 'lightgreen'}],
'threshold': {
'line': {'color': "red", 'width': 4},
'thickness': 0.75,
'value': 95}})
fig = go.Figure(data = trace2)
fig.update_layout(paper_bgcolor = "white", font = {'color': "darkblue", 'family': "Arial"})
products_list = dff.values.tolist()
# erase temptable
postgres_erase = """ TRUNCATE linertemp """
cursor.execute(postgres_erase)
conn.commit()
# populate table with edited dataframe
for item in products_list:
if '-' in item[1]:
try:
postgres_insert_query = """ INSERT INTO linertemp (material, count) VALUES (%s,%s)"""
record_to_insert = (item[1], item[0])
cursor.execute(postgres_insert_query, record_to_insert)
conn.commit()
except Exception as e:
print("Error: {}".format(str(e)))
conn.close()
return [
dcc.Graph(
id=column,
figure=fig,
style={"height": 400, "margin": {"t": 10, "l": 10, "r": 10}}
)
# check if column exists - user may have deleted it
# If `column.deletable=False`, then you don't
# need to do this check.
for column in ['count'] if column in dff
]
@app.callback(Output('output-provider', 'children'),
Input('danger-danger-provider', 'submit_n_clicks'))
def update_output(submit_n_clicks):
if not submit_n_clicks:
return ''
x = datetime.datetime.now()
year = str(x.year)
weekno = str(x.strftime("%U"))
lotno = year[3] + weekno + str(1).zfill(3)
try:
conn = psycopg2.connect(dbname=config.dbname, host=config.host, port=config.port,\
user=config.user, password=config.pwd)
cursor = conn.cursor()
linertemp = linerplan.linertemp(cursor)
maxid = linerplan.maxid(cursor)
if maxid == 0:
print ("in loop")
elif lotno <= maxid[0][0]:
lotno = int(maxid[0][0])+1
for item in linertemp:
livecount = deacttable[item[0]]
key = next(key for key, value in csubdict.items() if value == item[0])
if item[1] == livecount:
postgres_idupdate = """ UPDATE linerdeactq SET deactid = %s WHERE pn = %s """
recordupdate = (lotno, key)
cursor.execute(postgres_idupdate, recordupdate)
conn.commit()
postgres_insert_query = """ INSERT INTO linertestq (material, count, deactid, testcomplete) VALUES (%s,%s,%s,%s)"""
record_to_insert = (item[0], item[1], lotno, 'FALSE')
cursor.execute(postgres_insert_query, record_to_insert)
conn.commit()
postgres_copy_query = """ INSERT INTO linerarchive (pn, tech, qty, date, deactid) SELECT * FROM linerdeactq WHERE pn = %s"""
record_copy = (key,)
cursor.execute(postgres_copy_query, record_copy)
conn.commit()
PG_delete_query = """ DELETE FROM linerdeactq WHERE pn = %s """
record_to_delete = (key,)
cursor.execute(PG_delete_query, record_to_delete)
conn.commit()
else:
modvalue = livecount-item[1]
print (modvalue)
postgres_insert_query = """ INSERT INTO linertestq (material, count, deactid, testcomplete) VALUES (%s,%s,%s,%s)"""
record_to_insert = (item[0], item[1], lotno, 'FALSE')
cursor.execute(postgres_insert_query, record_to_insert)
conn.commit()
PG_update_query = """ UPDATE linerdeactq SET qty = %s WHERE pn = %s """
record_to_update = (modvalue, key)
cursor.execute(PG_update_query, record_to_update)
conn.commit()
# erase temptable
postgres_erase = """ TRUNCATE linertemp """
cursor.execute(postgres_erase)
conn.commit()
except (Exception, psycopg2.Error) as error :
logging.fatal("Error while fetching raw data from PostGRES DB")
finally:
#closing database connection.
if(conn):
cursor.close()
conn.close()
logging.debug("PG read connection successfully closed")
return """
Deactivation Lot Number: {}
""".format(str(lotno))