Dash on Multi-Page Site, @app.route? (Flask to Dash)


#1

I am somewhat familiar with building multi-page sites using Flask, and I am now exploring the use of Dash because I need an easy to implement methodology for getting interactive data on some pages of a website.
I read in yesterday’s announcement that “Dash applications are web servers running Flask and communicating JSON packets over HTTP requests.”

So, Flask apps use @app.route(’/’), @app.route(’/datapage’), et cetera to direct towards different pages…

I am hoping that there is a way to do this with Dash. However, the only examples I have found so far are made for single page Dash apps, and they build the layout directly into the main app (app.layout) and they use @app.callback

Is there a way to use @app.route(’/target’) to specify a page and then to define the app.layout subordinate to whatever route is called?
And, if so, what is the appropriate way to do an @app.callback from within an @app.route(‘x’)?

Thanks!
Chad


Multiple Dashboards?
Autocomplete with Dropdown object when setting app.layout dynamically
URL Not Found - Multi-Page Dash App
#2

Edit - This behaviour has changed. See Dash on Multi-Page Site, @app.route? (Flask to Dash) for an updated answer or https://plot.ly/dash/urls for a tutorial on multi-page apps.


(This answer is now outdated.)

Good question @repower! URLs are possible right now but they are very beta and so I have not documented them. The way we do URLs is going to change in the next few weeks.

You’re welcome to use the beta functionality for now if you’d like. The Dash User Guide uses the beta functionality to render the multi-page user guide, and so you can dig through the source here: https://github.com/plotly/dash-docs/blob/master/tutorial/run.py#L283-L288 to see how they were used (app.routes). I also have an old tutorial on this here: https://github.com/plotly/dash-docs/blob/master/tutorial/urls.py. Again, this functionality is likely to change which is why I haven’t documented it publically.

Dash has the capabilities to render websites as “single-page-apps” meaning that a full page refresh is not performed when you visit new pages. This will make navigation in the website extremely quick. Instead of reloading the page, the children property of some container element just changes values. For example:

app.layout = html.Div([
    dcc.RadioItems(options=[
        {'label': i, 'value': i} for i in ['Chapter 1', 'Chapter 2']
    ], value='Chapter 1',
    id='navigation-links'),
    html.Div(id='body')
])
@app.callback(Output('body', 'children'), [Input('navigation-links', 'value')])
def display_children(chapter):
    if chapter == 'Chapter 1':
        return Div('Welcome to Chapter 1')
    elif chapter == 'Chapter 2':
        return Div('Welcome to Chapter 2')
```

This example hides and shows content when you click on different radio items. Instead of a `RadioItems` component, I'd like to add a `URLBar` component that contains state of the current address bar. This component could be used as an input to hide and show content depending on what URL you're on. Another component, like a `Tab` component or that `RadioItems` component could act as a table of contents and update the `URLBar` component when you change tabs. 

That's the idea at least. There is a little bit more work to make this happen, but it's on my immediate roadmap.

#3

That’s great that you have this on your immediate roadmap. Glad to hear it.

My first thought is that it could become unwieldy to have a singular, all-inclusive app.layout section written into the app.py document.
I find that it helps me keep organized to have pagename.html files in the /templates folder nested on the folder where I keep app.py, main.py, wsgi.py, et cetera.

Thoughts on how you might break-out / compartmentalize the app.layout?
I am also thinking about compatibility with Flask Bootstrap / Jinja2 templates…


#4

My first thought is that it could become unwieldy to have a singular, all-inclusive app.layout section written into the app.py document.
I find that it helps me keep organized to have pagename.html files in the /templates folder nested on the folder where I keep app.py, main.py, wsgi.py, et cetera.

Thoughts on how you might break-out / compartmentalize the app.layout?

Yeah, you can always just import layouts from separate files. Take a look at the organization of the docs here: https://github.com/plotly/dash-docs/tree/master/tutorial. Each file basically represents a separate “page”. A central run.py file collects the URLs and has the hiding and showing logic.

I am also thinking about compatibility with Flask Bootstrap / Jinja2 templates…

For large existing sites that happen to made in Flask, I’m recommending that Dash is just run along side those apps and that the apps are embedded into the existing site using <iframe/>s (for now!). Dash itself won’t provide compatibility with Jinja templates. For CSS, you could embed the stylesheets inside the dash app examples too.


#5

Some updates:

  • I just wrote a Link and Location component that could be used for creating “single page apps”. Here is the issue on GitHub (please provide feedback there!). It’s in the pre-release channel right now but will be added to the core library within the week.
  • There is a community PR to add multiple apps with a base URL here: https://github.com/plotly/dash/pull/70

#6

Hi there,

How would one go about embedding a Dash app in an iframe? I’m pretty new to Plotly, Dash, and Python in general, so maybe this is obvious to other users. I’ve created a Flask app with several Plotly graphs, and when Dash was released it seemed like a great tool to use for some additional content that I had been thinking about creating. The Dash app works great on its own, however, I’d really like to get it into my overall Flask app.

Thanks,
Nick


#7

I believe what you are asking is not specific to dash. Embedding dash apps is like embedding anything else:

<iframe src="http://youdashapp.company.com/product1_kpis" scrolling="no" width="1200" height="2000" frameBorder="0"></iframe>


#8

Related to the original question, in Flask you can do things like

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return 'User %s' % username

How do you do something similar in Dash, so that I can retrieve the value username and then plot something that depends on that value?

Thanks


#9

Ok. So I would basically have to have the Dash app running elsewhere with it’s own URL. I can’t just have the Dashapp.py sitting in my current Flask app folder, and somehow “run” the Dash app from the iframe.

Cheers,
Nick


#10

Updates!

You can now create a multi-page Dash app using links and the new dash_core_components.Location component. I just wrote a tutorial on this here: https://plot.ly/dash/urls. This is what the multi-page Dash user guide (https://plot.ly/dash, github.com/plotly/dash-docs) uses.


#11

To embed the app as an Iframe, that is correct.


#12

This may not be exactly what you asked, didac but its working for me. The new Location object lets you scrape parameters from a URL only you have to encode it in the function. Then use those parameters and a callback to create content dynamically through app.layout.

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 generate_layout(url):
     """ url:  not a full url, but just the part after the host ip and port.  
                  ex:  [full url] http://my-dash-server:8080/username=duncan&userid=1234&graphsettings=23]
                         [actual url string:   /username=duncan&userid ... ]
     """                                                                                 
     if not url: url='/'                                                                                   
     config = dict(urllib.parse.parse_qsl(url.strip('/')))
     ### alternative way to parse url
     #config = urllib.parse.parse_qs(url)                                                           

     return generate_content(config)

def generate_content(config):
    """ Uses config dictionary as parameters to generate a list of html or dcc components """
    return []

#14

A post was split to a new topic: URL Not Found - Multi-Page Dash App


#15

Hi, we are having a flask application, here I am extending it by using “BaseView”. I want to integrate Dash into our existing application at some other endpoint “/abc” . How can we do it

from flask_admin import BaseView, expose
class MyView(BaseView):
    @expose('/')
    def index(self):
        app = dash.Dash()
         app.layout = html.Div( ... )
        return **' ??? '**

#16

This would be the simplest way to add a Dash app into an existing Flask app at a specific endpoint, which in this case is at /dash

from flask import Flask
from dash import Dash
from dash.dependencies import Input, State, Output
import dash_core_components as dcc
import dash_html_components as html


server = Flask(__name__)

@server.route('/')
def index():
    return "hellow world"

app = Dash(server=server, url_base_pathname='/dash')
app.layout = html.Div([
    html.Div(id='target'),
    dcc.Input(id='input', type='text', value=''),
    html.Button(id='submit', n_clicks=0, children='Save')
])


@app.callback(Output('target', 'children'), [Input('submit', 'n_clicks')],
              [State('input', 'value')])
def callback(n_clicks, state):
    return "callback received value: {}".format(state)


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



#17

Hi nedned thanks for replying, I needed to create class of “BaseView”, and add route there so that it will work as extension to existing Apache Airflow flask server application. I tried above code like

class StatsView(BaseView):

    @expose('/')
    def index(self):
        return "hello world"

app = Dash(server=server, url_base_pathname=’/statsview’)
app.layout = html.Div([
html.Div(id=‘target’),
dcc.Input(id=‘input’, type=‘text’, value=’’),
html.Button(id=‘submit’, n_clicks=0, children=‘Save’)
])

@app.callback(Output(‘target’, ‘children’), [Input(‘submit’, ‘n_clicks’)],
[State(‘input’, ‘value’)])
def callback(n_clicks, state):
return “callback received value: {}”.format(state)

but i am only seeing “hello world”.


#18

This is now very much an issue specific to the Flask-AppBuilder framework that you’re using. But it looks to me like the issue is that the Dash url_base_pathnam has the same value as the name of the BaseView subclass. Changing the name seems to make it behave:

import os
from flask import Flask
from flask_appbuilder import SQLA, AppBuilder, BaseView, expose
from dash import Dash
from dash.dependencies import Input, State, Output
import dash_core_components as dcc
import dash_html_components as html

# init Flask                                                                                                                                                                                   
app = Flask(__name__)

# Basic config with security for forms and session cookie                                                                                                                                      
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'app.db')
app.config['CSRF_ENABLED'] = True
app.config['SECRET_KEY'] = 'thisismyscretkey'

# Init SQLAlchemy                                                                                                                                                                              
db = SQLA(app)
# Init F.A.B.                                                                                                                                                                                  
appbuilder = AppBuilder(app, db.session)


class Foo(BaseView):
    @expose('/')
    def index(self):
        return "hello world"

appbuilder.add_view_no_menu(StatsView())

dash_app = Dash(server=app, url_base_pathname='/foo')

dash_app.layout = html.Div([
    html.Div(id='target'),
    dcc.Input(id='input', type='text', value=''),
    html.Button(id='submit', n_clicks=0, children='Save')
])


@dash_app.callback(Output('target', 'children'), [Input('submit', 'n_clicks')],
              [State('input', 'value')])
def callback(n_clicks, state):
    return "callback received value: {}".format(state)

# Run the development server                                                                                                                                                                   
app.run(host='0.0.0.0', port=8080, debug=True)


#19

I want to do LDAP integration with dash, flask application which does the authentication, after successful authentication, I want to load the default dash page (display_page)

Anyone tried this earlier ? any suggestions
My code as follows,

import dash
import dash_renderer
import plotly

from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

from flask import Flask, request, redirect, url_for
import ldap

auth = Flask(name)
ldap_url = “LDAP://10.12.15.18:1234”

def check_auth(username, password):
username = username
conn = ldap.initialize(ldap_url)
conn.protocol_version = ldap.VERSION3
conn.set_option(ldap.OPT_REFERRALS, 0)
try:
conn.simple_bind_s(username, password)
return True
except ldap.INVALID_CREDENTIALS:
print (“Invalid Credentials”)
return False

@auth.route(’/logout’)
def logoutpage():
return “LDAP Authentication unsuccessful…”

@auth.route(’/home’, methods=[‘GET’, ‘POST’]) #allow both GET and POST requests
def form_login():
if request.method == ‘POST’: #this block is only entered when the form is submitted
username = request.form.get(‘username’)
password = request.form[‘password’]
#import pdb; pdb.set_trace()
ret = check_auth(username, password)
if ret:
return redirect(url_for(‘display_page’), app=app)
else:
return redirect(url_for(‘logoutpage’))

  else:
        return '''</head>
			<body>
			<h2>Dash Login Form</h2>
			</head>
			<form method="POST">
              Username: <input type="text" name="username"><br>
              Password: <input type="password" name="password"><br>
              <input type="submit" value="Submit"><br>
          </form>
		  </body>
		  '''

app = dash.Dash(“DB”, server=auth, url_base_pathname=’/home’, loginSupported=True)

app.layout = html.Div([
create_header(),
create_links(),
html.Div(id=‘page-content’),

])

@app.callback(Output(‘page-content’, ‘children’),
[Input(‘url’, ‘pathname’)])
def display_page(pathname):
return analytics.layout

app.run_server(debug=True, host=‘0.0.0.0’, port= get_port_number(), threaded=True)


#20
> ''' 
> flask_app.py : Attempt to run dash and flask based routes in one instance.
> '''
> 
> from flask import Flask, render_template
> from dash import Dash
> from dash.dependencies import Input, State, Output
> import dash_core_components as dcc
> import dash_html_components as html
> 
> import json
> import plotly
> import pandas as pd
> import numpy as np
> 
> server = Flask(__name__)
> ########################################################################
> @server.route('/graph') # Interactive Dash Graph in predefined HTML
> def index():
>     rng = pd.date_range('1/1/2011', periods=7500, freq='H')
>     ts = pd.Series(np.random.randn(len(rng)), index=rng)
> 
>     graphs = [
>         dict(
>             data=[
>                 dict(
>                     x=[1, 2, 3],
>                     y=[10, 20, 30],
>                     type='scatter'
>                 ),
>             ],
>             layout=dict(
>                 title='first graph'
>             )
>         ),
> 
>         dict(
>             data=[
>                 dict(
>                     x=[1, 3, 5],
>                     y=[10, 50, 30],
>                     type='bar'
>                 ),
>             ],
>             layout=dict(
>                 title='second graph'
>             )
>         ),
> 
>         dict(
>             data=[
>                 dict(
>                     x=ts.index,  # Can use the pandas data structures directly
>                     y=ts
>                 )
>             ]
>         )
>     ]
> 
>     # Add "ids" to each of the graphs to pass up to the client
>     # for templating
>     ids = ['Graph-{}'.format(i) for i, _ in enumerate(graphs)]
> 
>     # Convert the figures to JSON
>     # PlotlyJSONEncoder appropriately converts pandas, datetime, etc
>     # objects to their JSON equivalents
>     graphJSON = json.dumps(graphs, cls=plotly.utils.PlotlyJSONEncoder)
>     return render_template('layouts/graph.html',
>                            ids=ids,
>                            graphJSON=graphJSON)
> 
> ########################################################################
> @server.route('/hello') # Static predefined HTML
> def hello_index():
>     return render_template('hello.html',)
> 
> ########################################################################
> app = Dash(server=server, url_base_pathname='/dash') # Interactive Dash input box with callback.
> app.layout = html.Div([
>     html.Div(id='target'),
>     dcc.Input(id='input', type='text', value=''),
>     html.Button(id='submit', n_clicks=0, children='Save')
> ])
> 
> @app.callback(Output('target', 'children'), [Input('submit', 'n_clicks')],
>               [State('input', 'value')])
> def callback(n_clicks, state):
>     return "callback received value: {}".format(state)
> 
> ######################################################################        
> app = Dash(__name__, server=server, url_base_pathname='/dashed') #Another Bash Graph inline, no callbacks.
> app.layout = html.Div(children=[
>     html.Div(children='''
>     Dash: A web application framework for Python
>     '''),
> 
>     dcc.Graph(
>         id='example-graph',
>         figure={
>             'data': [
>                 {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
>                 {'x': [1, 2, 3], 'y': [2, 4, 6], 'type': 'bar', 'name': 'Montreal'},
>             ],
>             'layout': {
>                 'title': 'Dash Data Visualization'
>             }
>         }
>     )
> ])
> ########################################################################
> '''
> Content from 'index.py'
> 
> page1c, page2c, page3c are dash separate layout files for a multipage website with dash, which is working perfect.
> These are called in 'index.py' (main page) respectively as below.
> Running 'python index.py' (standalone dash instance), all the interactive pages are responsive and plot the data (with callbacks) they're intended to.
> But running with flask, pages only show HTML content, sliders and dropdown boxes, but the backend processes aren't triggering so no graphs are generated.
> '''
> # Note: 'index_page' is the layout with 3 links to above items.
> # All 3 files have multiple layout (with graphs and callbacks), different files for everyone to keep it simple and less cluttered.
> 
> import page1c, page2c, page3c
> from index import index_page
> 
> d_app = Dash(server=server, url_base_pathname='/', )
> 
> d_app.layout = html.Div([
>     html.Div(id='page-content'),
>     dcc.Location(id='url', refresh=True),
> ])
> 
> @d_app.callback(Output('page-content', 'children'),
>               [Input('url', 'pathname')])
> def display_page(pathname):
>     if pathname == '/page1':
>          return page1c.layout
>     elif pathname == '/page2':
>          return page2c.layout
>     elif pathname == '/page3':
>          return page3c.layout
>     else:
>         return index_page
> 
> ######################################################################        
> if __name__ == '__main__':
>     app.run_server(port=9999, debug=True)

Here is my code, i’m able to get things working to an extent, but what i’m looking as an intended behavior, still having trouble with it.
Last part is where i’m having trouble.

Thanks

Also posted the same on StackOverflow.


#21

Thanks very much, this is the answer I am looking for :grinning::hugs: