Welcome, Goddard Python User's Group!
This guide outlines the steps we took in our October 6th, 2015 Meetup to write a web application, for data visualization, in Python using the Flask microframework.
When a user makes a request in the client, flask
will bring you back into the python program on the server side.
If a user asks for a yearly average dataset, you can use pandas
to provide the sampling, and flask
again helps with sending the data back to the user.
Anything you can do in python, you are suddenly able to interact with via the flask
module.
There are also flask
add-ons, which handle even more amazing tasks, like local language translataion, thwarting hackers, and creating beautiful barebones websites in seconds.
Let's get started!
I've tried to orgainze all the codes here:
git clone https://github.com/jakebrinkmann/2015-PUG-flask-data-vis.git
cd 2015-PUG-flask-data-vis
Starting off, we will need:
I will use other packages along the way, but these are really all you need
If you have used flask before, or know web development... let's make the next talk together!
2015-PUG-flask-data-vis/run.py
from flask import Flask
app = Flask(__name__) # WSGI application
@app.route('/')
def hello_world():
return 'Hello from Flask!'
if __name__ == '__main__':
app.run()
Congratulations, your first web app! It will be running at http://localhost:5000 (only accessible by your machine)
Flask can generate HTML from within Python
git checkout tags/v0.1.1
from flask import Flask
app = Flask(__name__)
@app.route('/')
def show_html():
return '<h1>Big heading</h1><p>And some <b>paragraph</b> text</p>'
if __name__ == '__main__':
app.run()
git checkout tags/v0.1.2
If you enable debug support the server will reload itself on code changes, and it will also provide you with a helpful debugger if things go wrong
from flask import Flask
app = Flask(__name__)
@app.route('/')
def create_error():
return 'a %s' % a
if __name__ == '__main__':
app.run(debug=True)
The interactive debugger allows the execution of arbitrary code.
This makes it a major security risk and therefore it must never be used on production machines.
app.run(debug=os.environ.get('DEBUG', False))
git checkout tags/v0.1.3
Flask can also handle more complex redirects, and request routes
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/proj')
def projects():
return '<h1>Project Page</h1><p><ul><li>PUG Data Visualization</li></ul></p>'
@app.route('/')
def hello_world():
return '<a href="'+ url_for('projects') +'">Projects Page</a>'
if __name__ == '__main__':
app.run()
! mkdir app
! mkdir app/static
! mkdir app/templates
! mkdir tmp
! touch app/__init__.py
! touch app/views.py
! touch run.py
2015-PUG-flask-data-vis/app/views.py
from app import app
@app.route('/')
@app.route('/index')
def index():
return "Hello, World!"
2015-PUG-flask-data-vis/app/__init__.py
from flask import Flask
app = Flask(__name__)
from app import views
2015-PUG-flask-data-vis/run.py
from app import app
app.run(debug=True)
git checkout tags/v0.1.5
2015-PUG-flask-data-vis/app/views.py
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'nickname': 'Dr. Robert'} # fake user
return '''
<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Hello, ''' + user['nickname'] + '''</h1>
</body>
</html>
'''
Keeps the logic of your flask application separate from the layout of your web pages using place holders
Jinja Templates are great, and the syntax is fairly straight forward
Useful for flask, but also useful for other HTML tasks, like sending emails with preset formating
git checkout v0.1.6
Control statements, like if, for, and also access to object attributes
2015-PUG-flask-data-vis/app/templates/index.html
<html>
<head>
{% if title %}
<title>{{ title }} - PUG</title>
{% else %}
<title>2015-PUG-Data-Vis</title>
{% endif %}
</head>
<body>
<h1>Hello, {{ user.nickname }}!</h1>
</body>
</html>
2015-PUG-flask-data-vis/app/views.py
from flask import render_template
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'nickname': 'Dr. Robert'}
return render_template('index.html',
title='Home',
user=user)
git checkout tags/v0.2.0
Add variable parts to a URL.
Optionall converters exist:
2015-PUG-flask-data-vis/app/views.py
from flask import render_template
from app import app
@app.route('/post/<int:post_id>')
def show_post(post_id):
# show the post with the given id, the id is an integer
return 'Post %d' % post_id
@app.route('/<username>')
@app.route('/index/<username>')
def index(username):
user = {'nickname': username}
return render_template('index.html',
title='Home',
user=user)
Allow the user to pass inputs back to the server via HTTP "POST" method
Using Flask-WTForms addon is very helpful for building input forms
git checkout tags/v0.2.1
2015-PUG-flask-data-vis/config.py
WTF_CSRF_ENABLED = True
SECRET_KEY = 'SHHH!'
2015-PUG-flask-data-vis/app/__init__.py
from flask import Flask
app = Flask(__name__)
app.config.from_object('config')
from app import views
2015-PUG-flask-data-vis/app/views.py
from flask import render_template, request
from flask_wtf import Form
from wtforms.fields.html5 import DecimalRangeField
from app import app
class MyForm(Form):
my_slider = DecimalRangeField('Slider')
@app.route('/', methods=('GET', 'POST'))
def index():
form = MyForm()
user = {'nickname': 'Dr. Robert'}
if request.method == 'POST':
value = request.form['my_slider']
user['nickname'] = value
return render_template('index.html',
title='Home',
user=user,
form=form)
2015-PUG-flask-data-vis/app/templates/index.html
<html>
<head>
{% if title %}
<title>{{ title }} - PUG</title>
{% else %}
<title>2015-PUG-Data-Vis</title>
{% endif %}
</head>
<body>
<h1>Hello, {{ user.nickname }}!</h1>
<form method="POST" action="/">
{{form.hidden_tag()}}
<p>{{ form.my_slider.label }}: {{ form.my_slider }}</p>
<input type="submit" value="Go">
</form>
</body>
</html>
HTTP communications can be faked, so make sure you parse your inputs before using them directly
from flask import Markup
Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello <blink>hacker</blink>!</strong>')
git checkout version-0.3
git checkout tags/v0.3.0
2015-PUG-flask-data-vis/app/views.py
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure
@app.route('/<int:mag>/plot.png')
def plot(mag):
fig = Figure()
axis = fig.add_subplot(1, 1, 1)
xs = range(100)
ys = [mag * random.randint(1, 50) for x in xs]
axis.plot(xs, ys)
canvas = FigureCanvas(fig)
output = StringIO.StringIO()
canvas.print_png(output)
response = make_response(output.getvalue())
response.mimetype = 'image/png'
return response
2015-PUG-flask-data-vis/app/templates/index.html
<body>
<form method="POST" action="/">
{{form.hidden_tag()}}
<p>{{ form.my_slider.label }}: {{ form.my_slider }}</p>
<input type="submit" value="Go">
</form>
<img src="{{ url_for('plot', mag=mag) }}"></img>
</body>
It is nice to generate unique plots, especially when exploring the data
git checkout tags/v0.3.3
2015-PUG-flask-data-vis/app/views.py
from bokeh.embed import components
from bokeh.plotting import figure
@app.route('/plot/')
def hello():
fig = figure(title="Polynomial")
fig.line(x, [i ** 2 for i in x], color=color, line_width=2)
script, div = components(fig)
return render_template(
'index.html',
script=script,
div=div,
)
2015-PUG-flask-data-vis/app/templates/index.html
<link rel="stylesheet" href="http://cdn.pydata.org/bokeh/release/bokeh-0.9.1.min.css" type="text/css" />
<script type="text/javascript" src="http://cdn.pydata.org/bokeh/release/bokeh-0.9.1.min.js"></script>
...
<body>
{{ div | safe }}
{{ script | safe }}
</body>
git checkout version-0.4
JavaScript can provide very helpful client-side frameworks (think module imports in Python), along with helpful interactions with the "Document Object Model" (or page), just like Python (compiled in the browser)
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
...
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
...
<input type="submit" value="Go" class="btn btn-md btn-success">
...
</div>
</nav>
Plotting can be customized to meet your needs using re-useable libraries.
Couple of options for deployment
If you trust your internal network, open up a port to listen on all public IPs:
app.run(port=8080, host='0.0.0.0', debug=False)
Then, others can access your host via requests at:
http://yourcomputer:8080/
Leave out the app.run()
call, and setup configurations as directed by the host
You'll want to setup Apache to be able to call python, using FastCGI
FcgidIPCDir /tmp
AddHandler fcgid-script .fcgi
ServerAlias my-web-app
<VirtualHost *:80>
DocumentRoot /var/www/datavis/app/static
Alias /static /var/www/datavis/app/static
ScriptAlias / /var/www/datavis/runp-mysql.fcgi/
</VirtualHost>
Where your python script is:
/var/www/datavis/runp-mysql.fcgi
Then, register the DNS on your computer, and it can be accessed at:
http://my-web-app/
Things which seemed to pop up reliably to trip me up early on
bootstrap
depends on jQuery
, which must be imported (sourced) first