I’m creating a Dash App that requires me to have a user log-in before they can use the rest of the app (the layout for the rest of the app will involve a header, navbar, and content like input fields). The log-in page requires a username and password, and from there the username/password will be sent to a web API through a GET request in the ctor module. If the request is successful, it will return a token.
To have the log-in page that comes up at first, but then disappears when the users enters the username/password and gets a token, I made use of dcc.Store(type = ‘session’) so the log-in page wouldn’t come back when I refreshed the page (based off the Store clicks example here: Store | Dash for Python Documentation | Plotly). Is this the best way to do this?
Here’s a simplified version of what I did:
import dash
from dash import dcc, html, Input, Output, State, callback
import dash_bootstrap_components as dbc
from dash.exceptions import PreventUpdate
from pages import login_page, home_page, page1, page2
import ctor # my module
# I'm using bootstrap.css saved locally in an assets folder
app = dash.Dash(__name__, suppress_callback_exceptions = True)
server = app.server
# Layouts ----------------------------------------------------------------------
# Header
header = html.Div([
html.H1('Header', style = {'textAlign': 'center', 'textDecoration' : 'underline'})
])
# Navbar
navbar = dbc.Nav([
dbc.NavItem(dbc.NavLink('Input Page', href = '/', active = 'exact')),
dbc.NavItem(dbc.NavLink('Analysis Page', href = '/page1', active = 'exact')),
dbc.NavItem(dbc.NavLink('Extra Page', href = '/page2', active = 'exact'))
],
pills = True,
fill = True,
justified = True
)
# Layout for actual analysis
main_layout = html.Div([
header,
navbar,
html.Div(id = 'page_content')
# Layout for log-in: Using dcc.input for username and password and html.Button for login button
login_layout = html.Div([
login_page.login_layout
])
app.layout = html.Div([
dcc.Location(id = 'url', refresh = False),
dcc.Store(id = 'session', storage_type = 'session'),
html.Div(login_layout, id = 'container')
)]
# Callbacks ----------------------------------------------------------------------
# Update the index when using main_layout
@app.callback(
Output('page_content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
if pathname == '/page1':
return page1.page1_layout
elif pathname == '/page2':
return page2.page_layout
else:
return home_page.home_layout
# 1) Add a click to the session store
@app.callback(
Output('session', 'data'),
Input('button_login', 'n_clicks'), # from login_page.login_layout (html.Button)
State('session', 'data'),
State('input_username', 'value'), # from login_page.login_layout (dcc.Input)
State('input_password', 'value') # from login_page.login_layout (dcc.Input)
)
def on_click(n_clicks, data, username, password):
if n_clicks is None:
# prevent the None callbacks is important with the store component.
# you don't want to update the store for nothing.
raise PreventUpdate
# Give a default data dict with 0 clicks if there's no data.
data = data or {'clicks': 0}
user = ctor.User(username = username, password = password)
# If the token was created (successful log-in), then we can consider the button clicked; otherwise not
if user.token:
data['username'] = username
data['token'] = user.token
data['clicks'] = data['clicks'] + 1
del password
return data
# 2) Output the stored click into the page
@app.callback(
Output('container', 'children'),
Input('session', 'modified_timestamp'),
State('session', 'data'),
State('container', 'children')
)
def on_data(ts, data, existing_content):
if ts is None:
raise PreventUpdate
data = data or {}
print(f'Data in on_data: {data}')
if data.get('clicks', 0) > 0:
return main_layout
else:
return existing_content
if __name__ == '__main__':
app.run_server(debug=True)
Is dcc.Store(type = ‘session’) the best way to do this? It seems to work for so far.
Apologies if I missed something obvious; I’m still new to the dash module. Would love to hear someone else’s thoughts on this!