Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A simple working example for embedding dash in flask under a path #214

Closed
Aso1977 opened this issue Mar 2, 2018 · 48 comments
Closed

A simple working example for embedding dash in flask under a path #214

Aso1977 opened this issue Mar 2, 2018 · 48 comments
Assignees

Comments

@Aso1977
Copy link

Aso1977 commented Mar 2, 2018

Dash documentation lacks a simple but full working example of how to embed dash layout as a path in a flask app, so that the dash layout becomes a div in the flask and follows the base template. For example path '/plot&type=plot1' return dash layout for plot type 1.
I know that one can get the underlying flask app in dash, but how to get rendered dash layout in flask is not well documented.

@jkgenser
Copy link

I'm also very interested in this. I want to use a Flask app for things like authentication, and use Flask for setting up the base html. But then use Dash for setting up dashboard pages. Anyway figured out how to do this?

@fordanic
Copy link

Yes, looking for a similar example.

@moonlight16
Copy link

dito

@mpkuse
Copy link

mpkuse commented Apr 28, 2018

looking for the same!

@shaunvxc
Copy link

Likewise-- this would be super helpful!

@ghost
Copy link

ghost commented May 2, 2018

Okay. So I tried this out and created a gist -

Link to app.py gist

The main part of the code is creating the Flask Server, creating routes and passing the server to the dash app.

@ghost
Copy link

ghost commented May 23, 2018

@lazyspark
I think you also want to add url_base_pathname to Dash to give it a route:

eg.
app = dash.Dash(name, server=server, url_base_pathname='/dash')

This would leave the rest of your Flask app the same, i.e. route '/' could stay the same. And you would specify where the URL path of the dash app. I think that would be more useful in the example.

@moonlight16
Copy link

I think I figured out the answer to the original question. Your able to use the native Flask route decorator, and pass back the results from index() - which is a Dash instance method that
generates the HTML and returns.

For example:

server = Flask(__name__)
app = dash.Dash(__name__, server=server, url_base_pathname='/dummypath')
app.layout = <setup your Dash app here>

@server.route("/dash")
def MyDashApp():
    return app.index()

When you go to the URL:

http://<ip>/dash

This will render the HTML output as shown in the Dash source code for the index() method - it contains a header, body, css, scripts, etc... which basically runs your app.layout inside a script tag.

At this point you can modify the MyDashApp to return any HTML as you desire. You can Jinja with templating. OR you can create your own customer Dash class and override the index() method and create whatever you want.

For example:

class CustomIndexDash(Dash):
    """Custom Dash class overriding index() method for local CSS support"""
    def _generate_css_custom_html(self):
        link_str = '<link rel="stylesheet" href="{}/{}">'
        static_url_path = self.server.config['STATIC_URL_PATH']
        return '\n'.join(link_str.format(static_url_path, path)
                         for path in self.server.config['STYLESHEETS'])

    def index(self, *args, **kwargs):
        scripts = self._generate_scripts_html()
        css = self._generate_css_dist_html()
        custom_css = self._generate_css_custom_html()
        config = self._generate_config_html()
        title = getattr(self, 'title', 'Dash')
        return f'''
        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="UTF-8">
                <title>{title}</title>
                {css}
                {custom_css}
            </head>
            <body>
                <div id="react-entry-point">
                    <div class="_dash-loading">
                        Loading...
                    </div>
                </div>
                <footer>
                    {config}
                    {scripts}
                </footer>
            </body>
        </html>
        '''

@oriolmirosa
Copy link

Am I correct that this doesn't help if the goal is to use authentication through the Flask app? I think that the Dash app will still be accessible without the authentication on the /dummypath. I've spent the last two days hitting my head against this wall as I cannot use Dash at work unless I manage to make it play nice with Google Sign In. I can easily make it work on Flask, but not on the Dash route. If anyone has any ideas, I'd love to hear them.

@ghost
Copy link

ghost commented May 24, 2018

(note i accidentally setup my github account with two different handles and emails. i'm both @jacohn16 and @moonlight16)

Hi @oriolmirosa
fwiw....I used url_base_pathname='/dummypath' in my example so that Dash wouldn't default to use '/'. I did this too demonstrate that you can then use @server.route("/dash") to place the Dash app on any page you want. (although I know the Dash user guide also shows a way to use multiple pages. See https://dash.plot.ly/urls. But my goal was to figure out how to tie Dash into an existing Flask app). I think my example accomplishes the goal.

As far as authentication, can you clarify your issue? How are you dealing with authentication currently? Maybe this is a separate topic?

@oriolmirosa
Copy link

Hi, @moonlight16, you are right that the authentication issue is only tangential to this. I asked a question with the code I'm trying to use (and doesn't work) in the community forum in case you want to take a look: https://community.plot.ly/t/login-with-google-sign-on-dash-app/10447

@lchapo
Copy link

lchapo commented May 24, 2018

@oriolmirosa I'm also working on using Google OAuth for a Dash app. I have a basic working example of using Google Login to authenticate against a list of valid email addresses. It's a little janky, so if you find a better approach I'd love to hear it.

@oriolmirosa
Copy link

@lucaschapin This is great! I have adapted your example for my purposes (instead of a list of email addresses, I need to give access to everyone in my organization) and I think it works. I haven't been able to test completely because of issues with my server, but hopefully I'll be able to confirm in a couple of days. By the way, I think that your approach (overwriting the index) is the way to go given how Dash works. I'll link to your work in the community discussion I referred to above. Thanks!

@jackwardell
Copy link

jackwardell commented May 26, 2018

I wanted to get in on this thread becasue there isn't much help out there.

@jacohn16's solution loaded the page but not the dash. When I $ flask run I just get 404 errors about not being able to "GET /dummy_dash-layout HTTP/1.1" & "GET /dummy_dash-dependencies HTTP/1.1"

dashboards.py:

from flask import Blueprint, flash, g, redirect, render_template, request, url_for, Flask
from werkzeug.exceptions import abort
from flaskr.auth import login_required
from flaskr.db import get_db
import os
import dash
import dash_renderer
import dash_core_components as dcc
import dash_html_components as html

server = Flask(__name__)
dash_app = dash.Dash(__name__, server=server, url_base_pathname='/dummy') 

dash_app.layout = html.Div(children=[
    html.H1(children='Hello Dash'),
    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, 5], 'type': 'bar', 'name': u'Montréal'},
            ],
            'layout': {
                'title': 'Dash Data Visualization'
            }
        }
    )
])

bp = Blueprint('dashboards', __name__, url_prefix='/dashboards')

@bp.route('/select', methods=['GET', 'POST'])
def index():
    return render_template('dashboards/select.html')

@bp.route('/dash_one', methods=['GET', 'POST'])
def dash_one():
	return dash_app.index()

screen shot 2018-05-26 at 09 58 36

Sorry to jump in for troubleshooting help but I can't find much help out there.

EDIT: When running on chromium without addblock (mentioned here), the error changes:
screen shot 2018-05-26 at 11 57 28

@fettay
Copy link

fettay commented Jun 10, 2018

Same issue as @jackwardell did you find a fix?

@sandys
Copy link

sandys commented Jul 31, 2018

hi guys,
is there any progress on this ? this is very important for us. currently the way we hack this currently is to define a function and define ALL dash stuff within that function (including dashboards and routes). Its pretty messy.

Ideally we would want all Dash stuff to be available as a blueprint that i can use as part of my flask app. It should support standard @app.route . Is there any chance this will be supported ?

@Aso1977
Copy link
Author

Aso1977 commented Jul 31, 2018

My vote for making Dash more flask eco system friendly. To be able to use blueprints and @route and other decorators, flask-login's authentication, ...
I still like to use flask and user injected variables (e.g. current_user) in the html template possiblly through jinja syntax. I like to create shiny bootstrap base templates, them customise it through jinja with injected Dash core components.
I know the philosophy is different in that Dash creates html front-end, but there should be a way to around that.
It has great plotting functionality but current html components only suit tableau / dashboard like pages, not a complex and fully featured web app (maybe that's why it's named Dash!).

@sandys
Copy link

sandys commented Jul 31, 2018 via email

@hoovler
Copy link

hoovler commented Aug 10, 2018

I also would like to see Dash become more of a component / app that can be deployed on another service like Flask or Django. However -- according to the Dash docs -- you can apparently pass your own Flask app to Dash -- so, the complete opposite of what we're all saying:

import flask

server = flask.Flask(__name__)
app = dash.Dash(__name__, server=server)

Essentially, it just means what we already knew: that Dash uses Flask under the hood. But this specific bit of functionality reveals a bit more about how that's being done...

@delaleva
Copy link

Hi, I tried using dash-google-auth but I'm getting an error. I did all of the steps from the README.md file and replaced the variables for client ID and client secret with real values like this:

app.server.config["GOOGLE_OAUTH_CLIENT_ID"] = os.environ.get("client-id-here")
        app.server.config["GOOGLE_OAUTH_CLIENT_SECRET"] = os.environ.get("client-secret-here")

However, when I ran python app.py and opened "localhost:5000" in my browser I got the following error:

401. That’s an error.

Error: invalid_client

The OAuth client was not found.

Request Details
response_type=code
client_id=None
redirect_uri=http://localhost:5000/login/google/authorized
scope=profile email
state=1kPQ9gCwR3EkGbXV5LdnPLu1TgQEpn
access_type=offline
approval_prompt=force
That’s all we know.

I put the following into the “Authorized redirect URIs” field: http://localhost:5000/login/google/authorized

Any idea why I might be getting this error? Thanks.

@natisangarita
Copy link

Got the same error...please help!

@lchapo
Copy link

lchapo commented Sep 3, 2018

@delaleva you can see from the error message that the client_id isn't being set properly. os.environ.get() attempts to locate an environment variable in your bash profile, so the value here should be a key rather than the client id itself.

If you want to set the config values directly (fine for testing but never do this in production), your code would need to look like this:

app.server.config["GOOGLE_OAUTH_CLIENT_ID"] = "client-id-here"
app.server.config["GOOGLE_OAUTH_CLIENT_SECRET"] = "client-secret-here"

@cixingzhici
Copy link

I using @login_required with @route in my app 。But i don't know how do insert current_user to the dash

@jazon33y
Copy link

jazon33y commented Oct 5, 2018

@sandys This sounds interesting (Dash app all within a single function). Can you give a little working example of how you are doing this? Would be greatly appreciated.

@rmarren1
Copy link
Contributor

rmarren1 commented Oct 8, 2018

#377 and dash==0.28.2 may be of help

@0xAtomist
Copy link

@lucaschapin I have been trying to recreate your example straight out of the box however I'm getting an Internal Server Error displayed on the webpage with the following error in the terminal:

File "/Users/tomberry/dash-google-auth/venv/lib/python3.6/site-packages/flask/json/__init__.py", line 98, in _dump_arg_defaults bp.json_encoder if bp and bp.json_encoder AttributeError: 'OAuth2ConsumerBlueprint' object has no attribute 'json_encoder'

Any ideas how I can get around this?

@numpynewb
Copy link

numpynewb commented Oct 9, 2018

I had been struggling with this, and might have found a solution by cobbling together a few things that I found on Stack Overflow and deep within other GitHub threads:

from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
import flask
from flask import Flask, Response, redirect, url_for, request, session, abort
from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user 
from dash import Dash
import dash_html_components as html


def protect_views(app):
    for view_func in app.server.view_functions:
        if view_func.startswith(app.url_base_pathname):
            app.server.view_functions[view_func] = login_required(app.server.view_functions[view_func])
    
    return app


server = flask.Flask(__name__)

server.config.update(
    DEBUG = True,
    SECRET_KEY = 'secret_xxx'
)

# flask-login
login_manager = LoginManager()
login_manager.init_app(server)
login_manager.login_view = "login"


# user model
class User(UserMixin):

    def __init__(self, id):
        self.id = id
        self.name = str(id)
        self.password = "secret"
        
    def __repr__(self):
        return "%d/%s/%s" % (self.id, self.name, self.password)


# create some users with ids 1 to 20       
users = [User("numpynewb")]
 
# somewhere to login
@server.route("/login", methods=["GET", "POST"])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']        
        if password == "secret":
            id = username
            user = User(id)
            login_user(user)
            return flask.redirect(request.args.get("next"))
        else:
            return abort(401)
    else:
        return Response('''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=password name=password>
            <p><input type=submit value=Login>
        </form>
        ''')

# somewhere to logout
@server.route("/logout")
@login_required
def logout():
    logout_user()
    return Response('<p>Logged out</p>')


# handle login failed
@server.errorhandler(401)
def page_not_found(e):
    return Response('<p>Login failed</p>')
    
    
# callback to reload the user object        
@login_manager.user_loader
def load_user(userid):
    return User(userid)

dash_app1 = Dash(__name__, server = server, url_base_pathname='/dashboard' )
dash_app2 = Dash(__name__, server = server, url_base_pathname='/reports')
dash_app1.layout = html.Div([html.H1('Hi there, I am app1 for dashboards')])
dash_app2.layout = html.Div([html.H1('Hi there, I am app2 for reports')])


dash_app1 = protect_views(dash_app1)
dash_app2 = protect_views(dash_app2)

@server.route('/')
@server.route('/hello')
@login_required
def hello():
    return 'hello world!'

@server.route('/dashboard')
def render_dashboard():
    return flask.redirect('/dash1')


@server.route('/reports')
def render_reports():
    return flask.redirect('/dash2')


app = DispatcherMiddleware(server, {
    '/dash1': dash_app1.server,
    '/dash2': dash_app2.server
})

run_simple('0.0.0.0', 8080, app, use_reloader=True, use_debugger=True)

Let me know if anyone finds this useful!

@lchapo
Copy link

lchapo commented Oct 10, 2018

@berryt08 looks like this issue happens with flask 0.12.3. I've updated my requirements file to use flask 0.12.4 which should solve it. If you're still having issues, I'd also try using Python 2.7 as I haven't tested out 3.X yet.

@0xAtomist
Copy link

@lucaschapin great stuff, thanks a lot! I can confirm it all works fine on Python 3.6.

@numpynewb
Copy link

@rmarren1 just saw this PR; excited for it!

@praskovy
Copy link

@numpynewb , thank you so much!
Your approach is just amazing!

However, could you please help me to solve the following problem.

I have several .html templates and several .py files with the Dash dashboards

Using your suggestion I finally reach the point when my .html templates are rendering as well as the layout of my Dash apps, but the layout only.

Do you know by chance what am I missing to be able to see the Dash app (not the layout, but all the data inside) in the depicted path?

I desperately hope for your help or a hint. Thanks!

@numpynewb
Copy link

@praskovy Great! I am glad that you found use in it.

If you set up a simple working example of your issue as a snippet in GitHub, or even if it fits in this thread (the length of my post was pushing it, I think...), then I can take a gander. I cannot guarantee that my looking will help, but I am happy to do so.

@praskovy
Copy link

@numpynewb , thank you, I appreciate your help a lot!

Here is the main.py script, which runs the website (for this one I used your solution, but just cut the login part):

from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
import flask
from flask import Flask, Response, redirect, url_for, request, session, abort, render_template
from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user
from dash import Dash
import dash_html_components as html
import dash_core_components as dcc
from app import app
from apps import app1, app2

def protect_views(app):
for view_func in app.server.view_functions:
if view_func.startswith(app.url_base_pathname):
app.server.view_functions[view_func]
return app

server = flask.Flask(__name__)

server.config.update(
DEBUG = True,
SECRET_KEY = 'secret_xxx')

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

dash_app1 = Dash(__name__, server = server, url_base_pathname='/dashboard/')
dash_app2 = Dash(__name__, server = server, url_base_pathname='/reports/')
dash_app1.layout = app1.layout
dash_app2.layout = app2.layout

dash_app1 = protect_views(dash_app1)
dash_app2 = protect_views(dash_app2)

@server.route('/')
@server.route('/hello/')
def hello():
return 'hello world!'

@server.route('/about/')
def render_about():
return render_template('home.html')

@server.route('/dashboard/')
def render_dashboard():
return flask.redirect('/dash1')

@server.route('/reports/')
def render_reports():
return flask.redirect('/dash2')

app = DispatcherMiddleware(server, { '/dash1': dash_app1.server, '/dash2': dash_app2.server})

run_simple('0.0.0.0', 8050, app, use_reloader=True, use_debugger=True)

Here is my app1.py script:

When I comment this line #app.layout = html.Div([ and put this one instead layout = html.Div([ the main.py script loads this dash_app1.layout = app11.layout
But the problem here that it loads the layout only, so there is no data on the Dash chart.
If you uncomment this line #app.layout = html.Div([ and simply run the app1.py script then you will see the data on the Dash chart.

app1.py script:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import pandas_datareader.data as data
import datetime
import plotly.graph_objs as go

start = datetime.datetime(2017,1,1)
end = datetime.datetime.today()

app = dash.Dash()

app.config['supress_callback_exceptions']=True
app.css.config.serve_locally = True
app.scripts.config.serve_locally = True

#app.layout = html.Div([
layout = html.Div([
html.H1('Stock Tickers'),
dcc.Dropdown(
id='my-dropdown',
options=[
{'label': 'Google', 'value': 'GOOG'},
{'label': 'Tesla', 'value': 'TSLA'},
{'label': 'Apple', 'value': 'AAPL'}],
value='GOOG'),
dcc.Graph(id='my-graph')])

@app.callback(Output('my-graph', 'figure'), [Input('my-dropdown', 'value')])
def update_graph(selected_dropdown_value):
df = data.DataReader(name=selected_dropdown_value,data_source="yahoo",start=start,end=end).reset_index()
trace= go.Candlestick(
x=df.Date,
open=df.Open,
high=df.High,
low=df.Low,
close=df.Close)
return {'data':[trace]}

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

Here is my app2.py (the same situation that with app1.py):

import dash
from dash.dependencies import Output, Event
import dash_core_components as dcc
import dash_html_components as html
import plotly
import random
import plotly.graph_objs as go
from collections import deque

X=deque(maxlen=20)
Y=deque(maxlen=20)
X.append(1)
Y.append(1)

app=dash.Dash(__name__)

app.config['supress_callback_exceptions']=True
app.css.config.serve_locally = True
app.scripts.config.serve_locally = True

#app.layout=html.Div([
layout=html.Div([
dcc.Graph(id='live-graph',animate=True),
dcc.Interval(
id='graph-update',
interval=1000)])

@app.callback(Output('live-graph', 'figure'),
events=[Event('graph-update', 'interval')])
def update_graph():
global X
global Y
X.append(X[-1]+1)
Y.append(Y[-1]+(Y[-1]*random.uniform(-0.1,0.1)))

data=go.Scatter(
    x=list(X),
    y=list(Y),
    name='Scatter',
    mode='lines+markers')

return {'data':[data], 'layout':go.Layout(
xaxis=dict(range=[min(X), max(X)]), yaxis=dict(range=[min(Y), max(Y)]))}

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

Here is my home.html page:

{% block content %}
<div class="home">
<h1>My homepage</h1>
<h1>My homepage</h1>
<p>This is a test home page .html</p>
</div>
{% endblock %}

I've tried different solutions, but only yours works for me somehow, it allows to load both .html templates and Dash apps.
The only problem is how to make these Dash apps work fully (app1.py and app2.py).

When I'm using this solution for the main.py script, I'm able to return the working Dash apps, but not able to render the .html templates:

from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
from app import app
from apps import app1, app2
from app import server
from flask import Flask, render_template

server=Flask(__name__)

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
elif pathname == '/about':
@server.route('/about/')
def about():
return render_template("about.html")
else: return '404'

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

From what I've read, I didn't find any solution so far which would be closest to the final outcome than yours. I would be vure thankful for any hint or advice.
Thank you!

@praskovy
Copy link

praskovy commented Oct 31, 2018

Hey @numpynewb , maybe if it would be interesting for you, I found a solution :)

@Volodymyrk suggested this structure: #70 and it perfectly worked for my case - allowed me to render the .html template and return my Dash apps.

Thank you, @Volodymyrk!

server.py
from flask import Flask
server = Flask(__name__)

app1.py
import dash
from server import server
app = dash.Dash(name='app1', sharing=True, server=server, url_base_pathname='/app1')

app2.py
import dash
from server import server
app = dash.Dash(name='app2', sharing=True, server=server, url_base_pathname='/app2')

run.py
from server import server
from app1 import app as app1
from app2 import app as app2
if __name__ == '__main__':
server.run()

Thanks a lot!

@ned2
Copy link
Contributor

ned2 commented Oct 31, 2018

Just a heads up that I'm keen to finally get something into the docs about this and have created an issue over at plotly/dash-docs#246. If you have any thoughts to add about the strategies I've suggested there, or any additional strategies, please chime in :)

@sidd-hart
Copy link

Hey @numpynewb , maybe if it would be interesting for you, I found a solution :)

@Volodymyrk suggested this structure: #70 and it perfectly worked for my case - allowed me to render the .html template and return my Dash apps.

Thank you, @Volodymyrk!

server.py
from flask import Flask
server = Flask(__name__)

app1.py
import dash
from server import server
app = dash.Dash(name='app1', sharing=True, server=server, url_base_pathname='/app1')

app2.py
import dash
from server import server
app = dash.Dash(name='app2', sharing=True, server=server, url_base_pathname='/app2')

run.py
from server import server
from app1 import app as app1
from app2 import app as app2
if __name__ == '__main__':
server.run()

Thanks a lot!

Were you able to use flask-login with this solution?

@numpynewb
Copy link

numpynewb commented Dec 14, 2018

That does indeed seem to work, and in a more concise way. Awesome PR! That in combination with guidance in #377 as @rmarren1 suggests makes this actually quite simple and natural. Dash is really moving fast :)

@motassimbakali
Copy link

I had been struggling with this, and might have found a solution by cobbling together a few things that I found on Stack Overflow and deep within other GitHub threads:

from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
import flask
from flask import Flask, Response, redirect, url_for, request, session, abort
from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user 
from dash import Dash
import dash_html_components as html


def protect_views(app):
    for view_func in app.server.view_functions:
        if view_func.startswith(app.url_base_pathname):
            app.server.view_functions[view_func] = login_required(app.server.view_functions[view_func])
    
    return app


server = flask.Flask(__name__)

server.config.update(
    DEBUG = True,
    SECRET_KEY = 'secret_xxx'
)

# flask-login
login_manager = LoginManager()
login_manager.init_app(server)
login_manager.login_view = "login"


# user model
class User(UserMixin):

    def __init__(self, id):
        self.id = id
        self.name = str(id)
        self.password = "secret"
        
    def __repr__(self):
        return "%d/%s/%s" % (self.id, self.name, self.password)


# create some users with ids 1 to 20       
users = [User("numpynewb")]
 
# somewhere to login
@server.route("/login", methods=["GET", "POST"])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']        
        if password == "secret":
            id = username
            user = User(id)
            login_user(user)
            return flask.redirect(request.args.get("next"))
        else:
            return abort(401)
    else:
        return Response('''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=password name=password>
            <p><input type=submit value=Login>
        </form>
        ''')

# somewhere to logout
@server.route("/logout")
@login_required
def logout():
    logout_user()
    return Response('<p>Logged out</p>')


# handle login failed
@server.errorhandler(401)
def page_not_found(e):
    return Response('<p>Login failed</p>')
    
    
# callback to reload the user object        
@login_manager.user_loader
def load_user(userid):
    return User(userid)

dash_app1 = Dash(__name__, server = server, url_base_pathname='/dashboard' )
dash_app2 = Dash(__name__, server = server, url_base_pathname='/reports')
dash_app1.layout = html.Div([html.H1('Hi there, I am app1 for dashboards')])
dash_app2.layout = html.Div([html.H1('Hi there, I am app2 for reports')])


dash_app1 = protect_views(dash_app1)
dash_app2 = protect_views(dash_app2)

@server.route('/')
@server.route('/hello')
@login_required
def hello():
    return 'hello world!'

@server.route('/dashboard')
def render_dashboard():
    return flask.redirect('/dash1')


@server.route('/reports')
def render_reports():
    return flask.redirect('/dash2')


app = DispatcherMiddleware(server, {
    '/dash1': dash_app1.server,
    '/dash2': dash_app2.server
})

run_simple('0.0.0.0', 8080, app, use_reloader=True, use_debugger=True)

Let me know if anyone finds this useful!

Is there also a possibility to implement a "roles_required" functionality this way? I tried playing a bit with the protect_views function and changing login_required into roles_required, but I can't get it working.

I would ideally only show certain pages to the administrator. With plain flask I would do:

@server.route('/reports')
@roles_required('admin')
def render_reports():
return flask.redirect('/dash2')

But this only seems to work for html pages that I render within the app (as expected). Anyone?

@numpynewb
Copy link

@Motta23 : yes; I believe you can achieve this by subclassing via the suggestion(s) in #377 . Just wrap the add_url method with roles_required . I have not tested for roles_required, but login_required works just fine.

@motassimbakali
Copy link

@Motta23 : yes; I believe you can achieve this by subclassing via the suggestion(s) in #377 . Just wrap the add_url method with roles_required . I have not tested for roles_required, but login_required works just fine.

Thanks for your reply @numpynewb . However, I don't see how this works, login_required takes a function as an argument, which is why #377 uses view_func=login_required(view_func). roles_required takes a role as an argument, so doing the same thing view_func=roles_required(view_func) results in an error: wrapper() missing 1 required positional argument: 'fn'.

Wrapping the function _add_url as follows doesn't do anything as well:

if roles_required('admin'):
            self.server.add_url_rule(
                name,
                view_func=login_required(view_func),
                endpoint=name,
                methods=list(methods))

            # record the url in Dash.routes so that it can be accessed later
            # e.g. for adding authentication with flask_login
            self.routes.append(name)
else:
            return

Unfortunately I don't understand much of the whole view_funct and the inners of dash, which makes this difficult. Any help is much appreciated! If this question is better asked on the plotly forum, let me know and I will ask it there. Thanks!

@okomarov
Copy link

okomarov commented Jan 2, 2019

You can find a fully working solution at: https://github.com/okomarov/dash_on_flask

The flask app uses:

  • the application factory pattern
  • the Flask-Login (and other extensions)

Details at
https://medium.com/@olegkomarov_77860/how-to-embed-a-dash-app-into-an-existing-flask-app-ea05d7a2210b

If you are NOT using the application factory, then the earlier answer is probably better suited.

@ned2 ned2 self-assigned this Jan 13, 2019
@olmax99
Copy link

olmax99 commented Feb 12, 2019

@Motta23 Importing layouts seems working fine, and there are multiple ways to redirect html. So far, I haven't been able redirecting dash callbacks in a multi app ( nested ) situation.
NOTE: Having all callbacks located in run.py solves the issue, but I want to have them in the module containing the respective layout as shown in the official dash documentation for multi-page apps.
Any help highly appreciated!

A dash app may be located in a sub folder, and look like this:
dashapp1.py

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

from app import create_app

server = create_app()
dashapp = dash.Dash(__name__, server=server, url_base_pathname='/page-1/')
dashapp.config['suppress_callback_exceptions'] = True


layout_page_1 = html.Div([
    html.H2('Page 1'),
    dcc.Input(id='input-1-state', type='text', value='Montreal'),
    dcc.Input(id='input-2-state', type='text', value='Canada'),
    html.Button(id='submit-button', n_clicks=0, children='Submit'),
    html.Div(id='output-state')
])


# Page 1 callbacks
@dashapp.callback(Output('output-state', 'children'),
              [Input('submit-button', 'n_clicks')],
              [State('input-1-state', 'value'),
               State('input-2-state', 'value')])
def update_output(n_clicks, input1, input2):
    return ('The Button has been pressed {} times,'
            'Input 1 is "{}",'
            'and Input 2 is "{}"').format(n_clicks, input1, input2)

Then, I modified your initial run.py:


import flask
import dash
import dash_html_components as html
from flask_login import login_required

from app import create_app

from dashboard.apps import dashapp1

def protect_dashviews(dashapp):
    for view_func in dashapp.server.view_functions:
        if view_func.startswith(dashapp.url_base_pathname):
            dashapp.server.view_functions[view_func] = login_required(dashapp.server.view_functions[view_func])

            return dashapp


server = create_app()

dash_app1 = dash.Dash(__name__, server=server, url_base_pathname='/dashboard/')
dash_app2 = dash.Dash(__name__, server=server, url_base_pathname='/reports/')
dash_app1.layout = dashapp1.layout_page_1
dash_app2.layout = html.Div([html.H1('Hi there, I am app2 for reports')])


dash_app1 = protect_dashviews(dash_app1)
dash_app2 = protect_dashviews(dash_app2)


@server.route('/dashboard')
def render_dashboard():
    return flask.redirect('/dash1')


@server.route('/reports')
def render_reports():
    return flask.redirect('/dash2')


app = DispatcherMiddleware(server, {
    '/dash1': dashapp1.dashapp.server,
    '/dash2': dash_app2.server
})```

@ned2
Copy link
Contributor

ned2 commented Feb 21, 2019

The Dash guide now has a Chapter on integrating Dash with existing web apps, which includes a simple recipe for embedding Dash under a path.

https://dash.plot.ly/integrating-dash

Since this issue has now been addressed, please jump on over the the Dash community Forums for further discussion around this topic: https://community.plot.ly/c/dash

@ned2 ned2 closed this as completed Feb 21, 2019
@zty753951
Copy link

我对此也很感兴趣。我想使用Flask应用程序进行身份验证等操作,并使用Flask来设置基本html。但是然后使用Dash设置仪表板页面。无论如何想出怎么做?

me too

@fenghuoqiuqiu
Copy link

I had been struggling with this, and might have found a solution by cobbling together a few things that I found on Stack Overflow and deep within other GitHub threads:

from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
import flask
from flask import Flask, Response, redirect, url_for, request, session, abort
from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user 
from dash import Dash
import dash_html_components as html


def protect_views(app):
    for view_func in app.server.view_functions:
        if view_func.startswith(app.url_base_pathname):
            app.server.view_functions[view_func] = login_required(app.server.view_functions[view_func])
    
    return app


server = flask.Flask(__name__)

server.config.update(
    DEBUG = True,
    SECRET_KEY = 'secret_xxx'
)

# flask-login
login_manager = LoginManager()
login_manager.init_app(server)
login_manager.login_view = "login"


# user model
class User(UserMixin):

    def __init__(self, id):
        self.id = id
        self.name = str(id)
        self.password = "secret"
        
    def __repr__(self):
        return "%d/%s/%s" % (self.id, self.name, self.password)


# create some users with ids 1 to 20       
users = [User("numpynewb")]
 
# somewhere to login
@server.route("/login", methods=["GET", "POST"])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']        
        if password == "secret":
            id = username
            user = User(id)
            login_user(user)
            return flask.redirect(request.args.get("next"))
        else:
            return abort(401)
    else:
        return Response('''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=password name=password>
            <p><input type=submit value=Login>
        </form>
        ''')

# somewhere to logout
@server.route("/logout")
@login_required
def logout():
    logout_user()
    return Response('<p>Logged out</p>')


# handle login failed
@server.errorhandler(401)
def page_not_found(e):
    return Response('<p>Login failed</p>')
    
    
# callback to reload the user object        
@login_manager.user_loader
def load_user(userid):
    return User(userid)

dash_app1 = Dash(__name__, server = server, url_base_pathname='/dashboard' )
dash_app2 = Dash(__name__, server = server, url_base_pathname='/reports')
dash_app1.layout = html.Div([html.H1('Hi there, I am app1 for dashboards')])
dash_app2.layout = html.Div([html.H1('Hi there, I am app2 for reports')])


dash_app1 = protect_views(dash_app1)
dash_app2 = protect_views(dash_app2)

@server.route('/')
@server.route('/hello')
@login_required
def hello():
    return 'hello world!'

@server.route('/dashboard')
def render_dashboard():
    return flask.redirect('/dash1')


@server.route('/reports')
def render_reports():
    return flask.redirect('/dash2')


app = DispatcherMiddleware(server, {
    '/dash1': dash_app1.server,
    '/dash2': dash_app2.server
})

run_simple('0.0.0.0', 8080, app, use_reloader=True, use_debugger=True)

Let me know if anyone finds this useful!

@numpynewb
I tried your codes and it helped a lot! But there is still one point left.
I 'd like to set passwords for two different accounts whose suffixs are 【/dashboard】 and 【/reports】. Changed a bit of your code like this.
But when I log in the account http://localhost:8080/dashboard/ with the password , and then I can log in to /report account directly, without password.
Could you let me know how to deal with the problem.
I hope that both accounts have their password independently. And the other account won't be access by changing suffix.
Thanks a lot!!

`# somewhere to login
@server.route("/login", methods=["GET", "POST"])
def login():
global username,password
if request.method == 'POST':
username = request.form['username']
password = request.form['password']

    if username=='riskdm' and password == "123":
        id = username
        user = User(id)
        login_user(user)
        return flask.redirect('/dashboard/')
        #return flask.redirect(request.args.get("next"))
    if username=='riskdm' and password == "789":
        id = username
        user = User(id)
        login_user(user)
        return flask.redirect('/reports/')
    else:
        return abort(401)
else:
    return render_template('login.html')`

@satoi8080
Copy link

I currently put a empty page in dummy path and return stuff with callback after authentication.

@Giohn
Copy link

Giohn commented Feb 4, 2021

from flask import Flask
import dash
import dash_html_components as html
from dash.dependencies import Input, Output


class DashApp:
    def __init__(self, flask_server):
        self.app = dash.Dash(name=self.__class__.__name__,
                             routes_pathname_prefix='/dash/',
                             server=flask_server)

    def setup(self):
        self.setup_layout()
        self.setup_callbacks()

    def setup_layout(self):
        self.app.layout = html.Button(id='some_id')

    def setup_callbacks(self):
        @self.app.callback(
            Output('some_id', 'children'),
            Input('some_id', 'n_clicks'))
        def update_button_text(n_clicks):
            return f"Hello Dash {n_clicks} clicks"


app = Flask(__name__)
dash_app = DashApp(app)
dash_app.setup()


@app.route('/')
def hello_world():
    return 'Hello Flask World!'


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

Consider using Mixins and splitting your code logically across files when the code gets large.
DashApp in the code above is really a class to manage the dash app, perhaps it could be better named.
Also since you are not running the dash server you may want to use self.app.enable_dev_tools(debug=True)

@Prakashgupta23
Copy link

Am I correct that this doesn't help if the goal is to use authentication through the Flask app? I think that the Dash app will still be accessible without the authentication on the /dummypath. I've spent the last two days hitting my head against this wall as I cannot use Dash at work unless I manage to make it play nice with Google Sign In. I can easily make it work on Flask, but not on the Dash route. If anyone has any ideas, I'd love to hear them.

So, for authentication you can block the view where dash was initially declared. In your case (/dummypath'). You need to create a flask log in manager, and there is a way to block the view . Try looking for creating protected view for dash apps using login manager

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests