plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.39k stars 2.06k forks source link

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

Closed Aso1977 closed 5 years ago

Aso1977 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

Yes, looking for a similar example.

moonlight16 commented 6 years ago

dito

mpkuse commented 6 years ago

looking for the same!

shaunvxc commented 6 years ago

Likewise-- this would be super helpful!

ghost commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

(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 commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

Same issue as @jackwardell did you find a fix?

sandys commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

Same here . We are trying to incorporate dash into a larger flask application and it's been very hard.

Flask is extremely trivial to get started - I think Dash will be much more usable if it integrates with flask, rather than tries to replace it.

On Wed, 1 Aug, 2018, 00:42 Aso, notifications@github.com wrote:

My vote for making Dash more flask eco system friendly. To be able to use blueprints and @route https://github.com/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!).

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/plotly/dash/issues/214#issuecomment-409334478, or mute the thread https://github.com/notifications/unsubscribe-auth/AAEsU9kqY7EJ9SZNQMYSXroCaKdnKDhDks5uMKwxgaJpZM4SaZTN .

hoovler commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

Got the same error...please help!

lchapo commented 6 years ago

@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 commented 6 years ago

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

jazon33y commented 6 years ago

@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 commented 6 years ago

https://github.com/plotly/dash/pull/377 and dash==0.28.2 may be of help

0xAtomist commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

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

numpynewb commented 6 years ago

@rmarren1 just saw this PR; excited for it!

praskovy commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

@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 commented 5 years ago

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

@Volodymyrk suggested this structure: https://github.com/plotly/dash/pull/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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

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

The flask app uses:

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.

olmax99 commented 5 years ago

@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 commented 5 years ago

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

zty753951 commented 5 years ago

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

me too

fenghuoqiuqiu commented 5 years ago

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 commented 4 years ago

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

Giohn commented 3 years ago
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 commented 3 years ago

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