Integrate Dash into Flask - python

I am trying to integrate a Dash app into an existing Flask program with Flask-Login. If I add server=server to the Dash instance every route (even ones that don't exist!!!), except the specified routes in run (ie. /home, /login), take me to the Dash app.
Example:
localhost:500/ - Takes me to Dash app
localhost:5000/home - Takes me to home route
localhost:5000/da - Takes me to Dash App
If I remove the server=server from the Dash instance, all the path's work as I would like, but when I pull up the dashboard at localhost:5000/test I get the error 'Error Loading Layout'.
If I add a url_base_pathname='/base/' to the dash instance, the routes work, but the dashboard bypasses my login.
Code Files:
server.py:
from flask import Flask
server = Flask(__name__,instance_relative_config=False)
server.config['SECRET_KEY'] = 'secretkey'
run.py:
from flask import Flask, render_template, request, redirect
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user
from server import server
from dashboard import app as app_1
from werkzeug.middleware.dispatcher import DispatcherMiddleware
from werkzeug.serving import run_simple
server.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///login.db'
db = SQLAlchemy(server)
login_manager = LoginManager()
login_manager.init_app(server)
class User(UserMixin,db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(30), unique=True)
password = db.Column(db.String(30))
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
#server.route('/login')
def login():
return render_template('login.html')
#server.route('/homepage', methods=['POST'])
def homepage():
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username,password=password).first()
if not user:
return 'User not Found'
else:
login_user(user)
return render_template('homepage.html')
#server.route('/logout')
def logout():
logout_user()
return index()
#server.route('/')
#server.route('/home')
def index():
return render_template('index.html')
#server.route('/test')
#login_required
def dashboard():
return redirect('/dash1')
app = DispatcherMiddleware(server, {
'/dash1': app_1.server})
if __name__ == '__main__':
run_simple('0.0.0.0', 5000, app, use_reloader=True, use_debugger=True)
dashboard.py
import dash
import dash_html_components as html
from server import server
app = dash.Dash(__name__)
app.layout = html.Div('Dashboard Page')
I would appreciate any insight on this. I've been scouring the internet for the past 2 days and can't solve this. Ultimately, the best solution would be if I can figure out why there is a layout error when server=server is removed because the entire app functions other than that.
Thanks

Related

Flask-sqlalchemy with Blueprints - RuntimeError: No application found. Either work inside a view function or push an application context

There are a lot of questions regarding this but none of them are using Flask Blueprints.
I am trying to set up a QuerySelectField with a sqlite3 lookup.
But I get then error:
RuntimeError: No application found. Either work inside a view function or push an application context.
Here is my application structure:
My init.py
from flask import Flask, render_template, current_app
from flask_mail import Mail
from flask_login import LoginManager
from flask_bootstrap import Bootstrap
from flask_ckeditor import CKEditor
from instance.config import app_config
from app.views.ticket import ticket as ticket_blueprint
from app.views.users import users as user_blueprint
from app.views.main import main as main_blueprint
from app.models import User, db
login_manager = LoginManager()
def page_not_found(e):
return render_template('404.html'), 404
def register_blueprints_on_app(app):
app.register_blueprint(user_blueprint)
app.register_blueprint(ticket_blueprint)
app.register_blueprint(main_blueprint)
def login_manager_init(app):
login_manager.login_view = 'users.login'
login_manager.init_app(app)
# associate id in the cookie with the User object
#login_manager.user_loader
def load_user(user_id):
print(user_id)
return User.query.get(user_id)
# application factory
def create_app(config):
# create application instance
app = Flask(__name__, instance_relative_config=True)
app.register_error_handler(404, page_not_found)
app.config.from_object(app_config[config])
app.config.from_pyfile('config.py')
Bootstrap(app)
mail = Mail()
mail.init_app(app)
db.init_app(app)
ckeditor = CKEditor()
ckeditor.init_app(app)
login_manager_init(app)
register_blueprints_on_app(app)
return app
And here is the problematic forms.py which is under ticket/views/
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField, FieldList, DateTimeField, SelectField
from wtforms_sqlalchemy.fields import QuerySelectField
from wtforms.fields.html5 import EmailField, TelField
from wtforms.validators import InputRequired, Email
from flask_ckeditor import CKEditorField
from app.models import User
def user_query():
query = User.query()
return query
class TicketForm(FlaskForm):
requested_by = QuerySelectField(label='Requested by',
query_factory=user_query(),
allow_blank=False,
get_label='fullname')
department = SelectField(label='Department')
phone = TelField(label='Phone')
email = EmailField(label='Email', validators=[Email('Enter a valid email address')])
short_desc = StringField(label='Short Description', validators=[InputRequired('Must enter a short description')])
description = CKEditorField(label='Description')
classification = StringField(label='Classification')
priority = StringField(label='Priority')
sla_respond_by = DateTimeField(label='Respond by', render_kw={'readonly': True})
sla_resolve_by = DateTimeField(label='Resolve by', render_kw={'readonly': True})
sla_status = StringField(label='SLA Status', render_kw={'readonly': True})
related_tickets = FieldList(StringField(label='Ticker #'))
client_journal = TextAreaField(label='Client Area')
work_notes = TextAreaField(label='Work Notes')
closed_at = DateTimeField(label='Closed')
closed_by = StringField(label='Closed by')
followed_by = StringField(label='Followed by')
submit = SubmitField('Save')
The problem part is when this runs
def user_query():
query = User.query()
return query
I get the error.
For context here is the routes file under ticket/view
from flask import render_template
from app.views.ticket.forms import TicketForm
from flask_login import login_required
from . import ticket
#ticket.get('/ticket')
#ticket.put('/ticket')
#login_required
def ticket():
form = TicketForm()
return render_template('ticket.html', form=form)
And in ticket/view/init.py
from flask import Blueprint
ticket = Blueprint('ticket', __name__, static_folder='static', template_folder='templates')
from . import routes
I read the doc here but it was no help. https://flask-sqlalchemy.palletsprojects.com/en/2.x/contexts/
I can't work out how to pass the application context so this function works.
I got around this by not using a query_factory in the QuerySelectfield.
I changed the field declaration from this:
class TicketForm(FlaskForm):
requested_by = QuerySelectField(label='Requested by',
query_factory=user_query(),
allow_blank=False,
get_label='full_name')
To this:
class TicketForm(FlaskForm):
requested_by = QuerySelectField(label='Requested by',
allow_blank=False,
get_label='full_name')
(so, just removed query_factory=user_query(),)
I deleted this:
def user_query():
query = User.query()
return query
Then added this to the route in my Blueprint:
#ticket.get('/ticket')
#ticket.put('/ticket')
#login_required
def ticket():
form = TicketForm()
form.requested_by.query = User.query.all() # Added this
print(current_app.app_context())
return render_template('ticket.html', form=form)
I hope that helps someone as it was doing my head in.
I'd still like to know how to do it the other way with a query_factory as I'm sure there is a way. So if anyone knows please share!

Passing current_user from Flask-Login to Plotly Dash app

I am trying to build a Dash app which is integrated in Flask app. Everything seems to be working fine but when I try to show logged user in the Dash app it comes out as 'None'.
My app structure is as below:
example/
example/
dashapp/
static/
custom-css.css
templates/
base.html
home.html
login.html
__init__.py
app1.py
forms.py
models.py
routes.py
application.py
config.py
users.db
My Dash app is in app1.py. I've tried several ways to pass current_user but no success. It comes out fine in home.html though. I guess the problem is with my app being in separate file and not in the routes.py.
Here is the code for app.py:
import dash
import dash_html_components as html
from dashapp import application
from flask_login import login_required, current_user
app1 = dash.Dash(__name__, server = application, routes_pathname_prefix = '/app1/', assets_folder = 'static', assets_url_path = '/static')
app1.scripts.config.serve_locally = True
app1.css.config.serve_locally = True
app1.layout = html.Div(
children = '{}'.format(current_user)
)
for view_func in app1.server.view_functions:
if view_func.startswith('/app1/'):
app1.server.view_functions[view_func] = login_required(app1.server.view_functions[view_func])
routes.py code:
from flask import render_template, flash, redirect, url_for, request
from flask_login import login_user, logout_user, current_user, login_required
from werkzeug.urls import url_parse
from dashapp import application, db
from dashapp.forms import LoginForm
from dashapp.models import User
from dashapp import app1
#application.route('/')
#application.route('/home')
#login_required
def home():
return render_template('home.html')
#application.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('home'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('home')
return redirect(next_page)
return render_template('login.html', form=form)
#application.route('/app1/')
#application.route('/logout')
def logout():
logout_user()
return redirect(url_for('login'))
Other scripts are pretty much standard and I won't include them in the question unless they are really needed.
Please suggest how to overcome my problem. Thanks!
I managed to fix this with some help. Just in case somebody gets stuck see below updated code.
Added session rows to store current username in routes.py:
from flask import render_template, flash, redirect, url_for, request, session
from flask_login import login_user, logout_user, current_user, login_required
from werkzeug.urls import url_parse
from dashapp import application, db
from dashapp.forms import LoginForm
from dashapp.models import User
from dashapp import app1
#application.route('/')
#application.route('/home')
#login_required
def home():
return render_template('home.html')
#application.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
session['username'] = current_user.username
return redirect(url_for('home'))
form = LoginForm()
if form.validate_on_submit():
session['username'] = form.username.data
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('home')
return redirect(next_page)
return render_template('login.html', form=form)
#application.route('/logout')
def logout():
session.pop('username', None)
logout_user()
return redirect(url_for('login'))
And session in the callback for app1.py:
import dash
import dash_html_components as html
from dash.dependencies import Input, Output
from dashapp import application
from flask_login import login_required
from flask import session
app1 = dash.Dash(__name__, server = application, routes_pathname_prefix = '/app1/', assets_folder = 'static', assets_url_path = '/static')
app1.scripts.config.serve_locally = True
app1.css.config.serve_locally = True
app1.layout = html.Div(
children = [
html.Div(id='div2'),
html.Div(id='div3', children = 'xxxx'),
],
)
#app1.callback(
Output('div2', 'children'),
[Input('div3', 'children')])
def update_intervalCurrentTime(children):
return session.get('username', None)
for view_func in app1.server.view_functions:
if view_func.startswith('/app1/'):
app1.server.view_functions[view_func] = login_required(app1.server.view_functions[view_func])
Faced the same problem. I guess the problem is that current user hasn't been initialised when the 'application' registers Dash app. Nice workaround, though seems a bit insecure since Flask sessions are just encoded.
https://github.com/RafaelMiquelino/dash-flask-login/blob/master/app.py - more robust solution in my opinion. It checks current_user in the callbacks and uses contents of the page as Input, so the callbacks are called on page load. You can also skip the protection of the views if user experience isn't an issue.
Here's a simplified example of how I used it:
#app.callback(
Output('graph-wrapper', 'children'),
[Input('page-content', 'children')])
def load_graph(input1):
if current_user.is_authenticated:
return dcc.Graph() # Dash Graph populated with current_user data from db
else:
return ''

Flask + Wsgi + Docker - No user loader has been installed

In an attempt to run a flask project, which uses both wsgi, nginx in a docker configuration, I am facing the following error:
No user_loader has been installed for this LoginManager. Refer to https://flask-login.readthedocs.io/en/latest/#how-it-works for more info. However, if no login is considered, the system works fine.
How could I solve this?
Some more info:
My system files are as follows:
__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
app = Flask(__name__)
app.config.from_object('instance.config')
db = SQLAlchemy(app)
migrate = Migrate(app, db)
flask_bcrypt = Bcrypt(app)
manager = Manager(app)
Code to control the user session:
from instance.config import SECRET_KEY_LOGIN
login_manager = LoginManager(app)
---------------------------------------------------------------------------
login.py
from flask import render_template, request, Blueprint, url_for, redirect
from flask_login import login_user, logout_user, current_user
from app import flask_bcrypt, login_manager
from app.models.login import LoginForm
from app.models.tabelas import Usuario
sign_in = Blueprint('sign_in', __name__)
#login_manager.user_loader
def load_user(id_usuario):
return Usuario.query.filter_by(id_usuario=id_usuario).first()
#login_manager.request_loader
def request_loader(_request):
# print ('using request loader')
# return "load_user_token(_request)"
#sign_in.route('/', methods=['GET', 'POST'])
#sign_in.route('/login', methods=['GET','POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('home'))
else:
error = None
form = LoginForm()
if request.method == 'POST':
if form.validate_on_submit():
usuario = Usuario.query.filter_by(ds_email = form.ds_email.data).first()
if usuario and flask_bcrypt.check_password_hash(usuario.ds_password, form.ds_password.data):
login_user(usuario)
return redirect(url_for('home'))
else:
error = 'Invalid Login .'
else:
error = 'Error'
print(form.errors)
return render_template('admin/login.html', form=form, error=error)
Add in your __init__.py this code below the login_manager instance.
#login_manager.user_loader
def load_user(id):
return User.query.filter_by(user_id=id).first()
After that you can delete the #login_manager decorator and its respect function from login.py

Flask: Pass object between pages

i want to create a simple web application who use a connection to a vCenter Server, and i want to pass the variable connection between pages, instead of recreate this connection on every page.
This is the code:
#!/bin/env python
from flask import Flask, request, redirect, render_template, session
from flask import Flask, request, redirect, render_template, session
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired
from modulo import MyForm
from pyVim import connect
from pyVim.connect import SmartConnectNoSSL, Disconnect
from pyVmomi import vim
app = Flask(__name__)
#app.route('/')
def index():
return redirect('/submit')
#app.route('/submit', methods=('GET', 'POST')) #ENTER USERNAME AND PASSWORD, SAVE ON /SUCCESS
def submit():
form = MyForm()
if form.validate_on_submit():
return redirect('/success')
return render_template('submit.html', form=form)
#app.route('/success', methods=('GET', 'POST')) #ESTABILISH CONNECTION USING USERNAME AND PASSWORD CREDENTIALS
def success():
form = MyForm()
username = form.username.data
password = form.password.data
c = SmartConnectNoSSL(host='10.116.xxx.xxx', user=username, pwd=password)
datacenter = c.content.rootFolder.childEntity[0]
clusters = datacenter.hostFolder
cluster = clusters.childEntity[0]
esxi = cluster.host
return render_template('success.html', esxi=esxi)
#app.route('/hosts', methods=('GET', 'POST'))
def hosts():
macchine = request.form.getlist('host')
for i in esxi:
for x in macchine:
if i.name == x:
do something..
return FINISH
if __name__ == '__main__':
app.secret_key='Secret'
app.debug = True
app.run(host = '0.0.0.0', port = 3000)
i want to reuse the c variable (the connection to the server) in other pages, and the object who derived from this variable for example esxi (list of object).
if i run the code, flask say: global name 'esxi' is not defined
How can i do this?
In flask you can store variables on your app object and reuse them later. Example:
app = Flask(__name__)
app.c = SmartConnectNoSSL(host='10.116.xxx.xxx', user=username, pwd=password)
# You can now reuse the connection like so
datacenter = app.c.content.rootFolder.childEntity[0]

How do I handle login in flask with multiple blueprints?

I have multiple blueprints that needs to be integrated into a single app. I'm using flask-login to handle logins. However I'm confused on how to handle the LoginManager() and the .user_loader for my blueprints.
This is my current file structure.
system/
run.py
config.py
app/
__init__.py
models.py
views/
blueprint1.py
blueprint2.py
static/
templates/
<templates>
What's the correct way to implement them? Do I just call them at the __init__.py and import the login manager variable at the blueprints? or Do I need to call them individually in the blueprints?
Hopefully I'm able to portray the question clearly. Thank you for reading and answering
You must understand that for one application you must use one login manager no matter how many blueprints you use (of course there can be specific exceptions for example when blueprints are independent, but in this case you probably can't use flask-login). Because:
You have 1 entry point
If user is not logged in, he will be redirected to login/registration page
You have 1 user loader
How login manager works:
It registers current_user in request context
before_request reads your session, gets user id, loads the user with user_loader and set it to current_user or AnonymousUser
When you visit the private page, login_required checks current_user.is_authenticated() else redirects to login page
On login, it adds user id to the session
So you must initialize only one login manager instance for flask application and then use login_required and current_user in all your blueprints.
This is how I have handled it:
This is where I am initialising everything:
import logging
import logging.config
import flask
import flask.globals as flask_global
import flask_login
from config import flask as flask_config
from rest.api import dashboard
from rest.api.util import login_decorator
logger = logging.getLogger(__name__)
# app
flask_app = flask.Flask(__name__)
flask_app.config.from_object(flask_config)
# login manager needs be set before blueprint registration
login_manager = flask_login.LoginManager()
login_manager.init_app(flask_app)
flask_app.register_blueprint(dashboard.blueprint)
# setting blueprint specific login view
# login_manager.login_view = "login"
#login_manager.user_loader
def load_user(user_id):
"""
This will be used many times like on using current_user
:param user_id: username
:return: user or none
"""
# http://librelist.com/browser/flask/2012/4/7/current-blueprint/#44814417e8289f5f5bb9683d416ee1ee
blueprint = flask_global.current_app.blueprints[request.blueprint]
if hasattr(blueprint, load_user):
return blueprint.load_user(user_id)
# https://flask-login.readthedocs.org/en/latest/#how-it-works
return None
Here is my blueprint with its own handling of login:
from __future__ import absolute_import
import flask
import flask_login
from flask import Blueprint
from core.models.profile import Agent
from core.utils import thread_local
from rest.api.util import login_decorator
blueprint = Blueprint('human', __name__, url_prefix='/human')
def load_user(user_id):
"""
This will be used many times like on using current_user
:param user_id: username
:return: user or none
"""
agent = None
try:
agent = Agent.objects.get(username=user_id)
except:
# https://flask-login.readthedocs.org/en/latest/#how-it-works
pass
return agent
#blueprint.record_once
def on_load(state):
"""
http://stackoverflow.com/a/20172064/742173
:param state: state
"""
blueprint.load_user = load_user
state.app.login_manager.blueprint_login_views[blueprint.name] = 'human.login'
#blueprint.route('/login', methods=['POST'])
#login_decorator.login_not_required
def login():
username = flask.request.args.get('username')
password = flask.request.args.get('password')
try:
agent = Agent.objects.get(username=username)
except:
return 'Invalid username'
if not agent.check_password(password):
return 'Invalid password'
flask_login.login_user(agent)
return 'Valid login'
#blueprint.route("/logout")
def logout():
flask_login.logout_user()
return 'Logout done'
#blueprint.before_request
def before_request():
agent = flask_login.current_user
# https://flask-login.readthedocs.org/en/latest/#anonymous-users
is_logged_in = agent.get_id() is not None
login_not_required = getattr(flask.current_app.view_functions[flask.request.endpoint], 'login_not_required', False)
is_static_resource_call = flask.request.endpoint.startswith('static/')
if is_static_resource_call or is_logged_in or login_not_required:
if is_logged_in:
thread_local.set_current_brand_id(agent.brand_id)
else:
flask.abort(401)
# if we want to redirect to some page then we can use this. The appropriate login_view should be set
# return flask.current_app.login_manager.unauthorized()
Hope it helps.
In case anyone still faces this challenge due to the documentation not being so clear, here is a solution
In your case, you need to place the login manager declaration in the same file as the flask app instance. This is commonly an __init__.py file with the app = Flask(__name__).
At the top, import LoginManager class
from flask_login import LoginManager
Then tie it to the app instance.
login_manager = LoginManager()
login_manager.init_app(app)
(This was not asked but just incase someone needs it) Lets say you have admins and normal users and you are authenticating from different tables:
#login_manager.user_loader
def load_user(user_id):
x = Users.query.get(str(user_id))
if x == None:
x = Admins.query.get(str(user_id))
return x
Finally after importing blueprints you can define the login views for each in a dictionary
login_manager.blueprint_login_views = {
'admin': '/admin/login',
'site': '/login',
}
Since you tied the login manager to the flask app instance, there is no need of importing it into any blueprint
The documentation is unclear and I will put down what I come up with after spending some time researching.
You only need to define login_manager once in the flask app.py and init_app.
Then at each blueprint, add from flask_login import login_required at top and use the #login_required as usual. Turns out it can be used without stating the login_manager.
Code example (app with single blueprint)
app.py
import flask
from flask import redirect, url_for
import flask_login
from blueprint.secret import secret_bp
from model.user import User
login_manager = flask_login.LoginManager()
app = flask.Flask(__name__)
app.register_blueprint(secret_bp, url_prefix="/secret")
login_manager.init_app(app)
#login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
#app.route('/login', methods=['GET', 'POST'])
def login():
if flask.request.method == "GET":
return "<form action='/login' method='POST'><input type='text' name='user'><button type='submit'>Submit</button></form>"
user = flask.request.form.get('user')
if user == "user":
# Login and validate the user.
# user should be an instance of your `User` class
flask_login.login_user(user)
flask.flash('Logged in successfully.')
return flask.redirect(next or flask.url_for('index'))
return flask.redirect(flask.url_for('login'))
#app.route('/admin')
def admin():
return "Admin page"
#login_manager.unauthorized_handler
def unauthorized():
# do stuff
return redirect(url_for('login'))
secret.py
from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound
from flask_login import login_required
secret_bp = Blueprint('secret', __name__,
template_folder='templates')
#secret_bp.route('/noneed')
def no_need():
return "You can see this without login."
#secret_bp.route('/needlogin')
#login_required
def show():
return "can't see this if not login"
As expected, /secret/noneed can be access without login and /secret/needlogin will redirect you with the function stated with #unauthorized_handler.

Categories

Resources