This tutorial is designed to
- prepare your computer to run a flask-based application
- demonstrate how an application can interface with a SQL database
- demonstrate the use of templates
Flask is great at maximizing simplicity and flexibility, but it's not the only option for building a web application.
Read about tradeoffs here
Navigate to the folder that will store your application. In this directory, create new folders:
mkdir flaskexample
mkdir flaskexample/static
mkdir flaskexample/templates
mkdir tmp
The flaskexample folder will be where we will put our application package. The static sub-folder is where we will store static files like images, javascripts, and style sheets. The templates sub-folder is obviously where our templates will go.
Create a new file called flaskexample/__init__.py with the following content:
from flask import Flask
app = Flask(__name__)
from flaskexample import views
The script above simply creates the application object (of class Flask) and then imports the views module, which we haven't written yet.
The views are the handlers that respond to requests from web browsers. In Flask views are written as Python functions. Each view function is mapped to one or more request URLs.
Let's write our first view function in a new file called flaskexample/views.py:
from flaskexample import app
@app.route('/')
@app.route('/index')
def index():
return "Hello, World!"
This view is actually pretty simple; it just returns a string, to be displayed on the client's web browser. The two route decorators above the function create the mappings from urls / and /index to this function.
Next we need to point to python. On the command line, type which python and note the path.
The final step to have a fully working web app is to create a script that starts up the development web server with our application. Let's make a new file called run.py (in the folder a level above flaskexample):
#!/usr/bin/env python
from flaskexample import app
app.run(debug = True)
The script simply imports the app variable from our flaskexample package and invokes its run method to start the server. Remember that the app variable holds the Flask instance, we created it above.
The python script begins with: #!/usr/bin/env python
If you have several versions of Python installed, /usr/bin/env will ensure the interpreter used is the first one on your environment's $PATH. The alternative would be to hardcode something line #!/usr/bin/python or the like -- that's OK but less flexible.
To start the app you just run this script. First you have to indicate that this is an executable file before you can run it: chmod a+x run.py
Then the script can simply be executed as follows: ./run.py
After the server initializes it will listen on port 5000 waiting for connections. Now open up your web browser and enter the following URL in the address field:
This will also work: http://localhost:5000/index
Do you see the route mappings in action? The first URL maps to /, while the second maps to/index. Both routes are associated to our view function, so they produce the same result. If you enter any other route you will get an error, since only these two have been mapped to a view function.
When you are done playing with the server you can just hit Ctrl-C in the Terminal to stop it.
You should have this in your directory:
app\
static\
templates\
__init__.py
views.py
tmp\
run.py
Option 1 to expand your app is to edit your views file to be this:
from flaskexample import app
@app.route('/')
@app.route('/index')
def index():
user = { 'nickname': 'Miguel' } # fake user
return '''
<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Hello, ''' + user['nickname'] + '''</h1>
</body>
</html>
'''
I hope you agree that the solution above is very ugly. Consider how complex the code will become if you have to return a large and complex HTML page with lots of dynamic content. And what if you need to change the layout of your web site in a large app that has dozens of views, each returning HTML directly? This is clearly not a scalable option.
Templates to the rescue! If you could keep the logic of your application separate from the layout or presentation of your web pages things would be much better organized, don't you think? You could even hire a web designer to create a killer web site while you code the site's behaviors in Python. Templates help implement this separation.
Let's write our first template in a new file flaskexample/templates/index.html :
<html>
<head>
<title>{{title}} - microblog</title>
</head>
<body>
<h1>Hello, {{user.nickname}}!</h1>
</body>
</html>
As you see above, we just wrote a mostly standard HTML page, with the only difference that there are some placeholders for the dynamic content enclosed in {{ ... }} sections.
Now let's edit the file flaskexample/views.py to the following:
from flask import render_template
from flaskexample import app
@app.route('/')
@app.route('/index')
def index():
user = { 'nickname': 'Miguel' } # fake user
return render_template("index.html",
title = 'Home',
user = user)
Try the application at this point to see how the template works.
Once you have the rendered page in your browser you may want to view the source HTML and compare it against the original template.
To render the template we had to import a new function from the Flask framework called render_template. This function takes a template name and a variable list of template arguments and returns the rendered template, with all the arguments replaced.
Under the covers, the render_template function invokes the Jinja2 templating engine that is part of the Flask framework. Jinja2 substitutes {{...}} blocks with the corresponding values provided as template arguments.
We just covered about half of what’s found at http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ii-templates, but you should read the following sections later:
- Control statements in templates
- Loops in templates
- Template inheritance.
Let’s make a different page where we can pull data from postgresSQL. Change your views.py file to the following:
from flask import render_template
from flaskexample import app
from sqlalchemy import create_engine
from sqlalchemy_utils import database_exists, create_database
import pandas as pd
import psycopg2
user = 'Katie' #add your username here (same as previous postgreSQL)
host = 'localhost'
dbname = 'birth_db'
db = create_engine('postgres://%s%s/%s'%(user,host,dbname))
con = None
con = psycopg2.connect(database = dbname, user = user)
@app.route('/')
@app.route('/index')
def index():
return render_template("index.html",
title = 'Home', user = { 'nickname': 'Miguel' },
)
@app.route('/db')
def birth_page():
sql_query = """
SELECT * FROM birth_data_table WHERE delivery_method='Cesarean';
"""
query_results = pd.read_sql_query(sql_query,con)
births = ""
for i in range(0,10):
births += query_results.iloc[i]['birth_month']
births += "<br>"
return births
Check your python indentation matches that of above, save and do run.py, go to http://localhost:5000/ -- you’ll still see Miguel there.
Go to http://localhost:5000/db -- We just pulled months of C-sections from the birth_db database and displayed it on the website! But, it looks… not so great at this point.
i. <link href="../static/css/bootstrap.min.css" rel="stylesheet">
ii. Open it again -- This should look almost right!
f. Go to the starter-template.css and create a file with that content and the same name in the same directory as cesareans.html <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../../favicon.ico">
<title>Starter Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<link href="../../assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="starter-template.css" rel="stylesheet">
<!-- Just for debugging purposes. Don't actually copy these 2 lines! -->
<!--[if lt IE 9]><script src="../../assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
<script src="../../assets/js/ie-emulation-modes-warning.js"></script>
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
"templates/cesareans.html" 89L, 3618C
</div><!--/.nav-collapse -->
</div>
</nav>
<br><br>
<div class="container">
<div class="starter-template">
<h1>Bootstrap starter template</h1>
<p class="lead">Use this document as a way to quickly start any new project.<br> All you get is this text and a mostly barebones HTML document.</p>
</div>
<table class="table table-hover">
<tr><th>index</th><th>Attendant</th><th>birth_month</th></tr>
{% for birth in births %}
<tr><td>{{ birth['index'] }}</td><td>{{ birth['attendant']}}</td><td> {{ birth['birth_month'] }}</td></tr>
{% endfor %}
</table>
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="static/js/bootstrap.min.js"></script>
</div><!-- /.container -->
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="static/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../assets/js/ie10-viewport-bug-workaround.js"></script>
<script src="static/js/bootstrap.min.js"></script>
</body>
</html>
from flask import render_template
from flaskexample import app
from sqlalchemy import create_engine
from sqlalchemy_utils import database_exists, create_database
import pandas as pd
import psycopg2
user = 'Katie' #add your username here (same as previous postgreSQL)
host = 'localhost'
dbname = 'birth_db'
db = create_engine('postgres://%s%s/%s'%(user,host,dbname))
con = None
con = psycopg2.connect(database = dbname, user = user)
@app.route('/')
@app.route('/index')
def index():
return render_template("index.html",
title = 'Home', user = { 'nickname': 'Miguel' },
)
@app.route('/db')
def birth_page():
sql_query = """
SELECT * FROM birth_data_table WHERE delivery_method='Cesarean'\
;
"""
query_results = pd.read_sql_query(sql_query,con)
births = ""
print query_results[:10]
for i in range(0,10):
births += query_results.iloc[i]['birth_month']
births += "<br>"
return births
@app.route('/db_fancy')
def cesareans_page_fancy():
sql_query = """
SELECT index, attendant, birth_month FROM birth_data_table WHERE delivery_method='Cesarean';
"""
query_results=pd.read_sql_query(sql_query,con)
births = []
for i in range(0,query_results.shape[0]):
births.append(dict(index=query_results.iloc[i]['index'], attendant=query_results.iloc[i]['attendant'], birth_month=query_results.iloc[i]['birth_month']))
return render_template('cesareans.html',births=births)
Double-check your python indentation again!
Now, if you go to http://127.0.0.1:5000/db_fancy, you should see a pretty-ified version of the table (see below). Oh, the power of bootstrap.
Now let’s make a button to pull user input into your app / model!
Let’s first edit views.py to add routes for input and output pages to allow us to grab data inputted by the user:
from flask import request
to your flaskexample/views.py file@app.route('/input')
def cesareans_input():
return render_template("input.html")
@app.route('/output')
def cesareans_output():
return render_template("output.html")
Now lets add the templates that these views will call
cp flaskexample/templates/cesareans.html flaskexample/templates/input.html
cp flaskexample/templates/cesareans.html flaskexample/templates/output.html
In the next two edits, you will be removing the following block of code from input.html and output.html files you just created and replacing them with the text below:
remove this:
<div class="container">
<div class="starter-template">
<h1>Bootstrap starter template</h1>
<p class="lead">Use this document as a way to quickly start any new project.<br> All you get is this text and a mostly barebones HTML document.</p>
</div>
<table class="table table-hover">
<tr><th>index</th><th>Attendant</th><th>birth_month</th></tr>
{% for birth in births %}
<tr><td>{{ birth['index'] }}</td><td>{{ birth['attendant']}}</td><td> {{ birth['birth_month'] }}</td></tr>
{% endfor %}
</table>
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="../static/js/bootstrap.min.js"></script>
</div><!-- /.container -->
Next, replace it in input.html to add an input field and a button with the text below:
<div class="container">
<div class="starter-template">
<h2>Input Page</h2>
<p>Enter some user input</p>
</div>
<div class = "container">
<form action="/output" method="GET">
<div class="form-group">
<label for="birth_month">Birth Month:</label>
<input type="text" id="birth_month" name='birth_month' placeholder="e.g. ">
</div>
<div>
<button type="submit" class="btn btn-default btn-lg">Find these Cesareans!</button>
</div>
</form>
</div>
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="../static/js/bootstrap.min.js"></script>
</div> <!-- /.container-->
And then edit output.html to contain the text below where the old container was located:
<div class="container">
<div class="starter-template">
<h2>Output Page</h2>
<p>Enter some user input</p>
</div>
<div class = "container">
<form action="/output" method="GET">
<div class="form-group">
<label for="birth_month">Birth Month:</label>
<input type="text" id="birth_month" name='birth_month' placeholder="e.g. ">
</div>
<div>
<button type="submit" class="btn btn-default btn-lg">Find these Cesareans!</button>
</div>
</form>
</div>
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="../static/js/bootstrap.min.js"></script>
</div> <!-- /.container-->
<div class="container">
<div class="starter-template">
<h3>Results:</h3>
<p class="lead">Below is the result of your query.<br> You just took user input and looked up the information. Now we need to expand the functionality!</p>
</div>
<table class="table table-hover">
<tr><th>index</th><th>Attendant</th><th>Birth Month</th></tr>
{% for birth in births %}
<tr><td>{{ birth['index'] }}</td><td>{{ birth['attendant']}}</td><td> {{ birth['birth_month'] }}</td></tr>
{% endfor %}
</table>
</div><!-- /.container -->
<div class="container">
<div class="starter-template">
<h3>Another Result:</h3>
<p class="lead">Now we've taken the input and called a function from your package.<br>The result is {{the_result}}</p>
</div>
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="../static/js/bootstrap.min.js"></script>
</div><!-- /.container -->
Now let’s circle back to views.py and add the functionality we need to take the user input and do something (relatively) useful with it.
@app.route('/output')
def cesareans_output():
#pull 'birth_month' from input field and store it
patient = request.args.get('birth_month')
#just select the Cesareans from the birth dtabase for the month that the user inputs
query = "SELECT index, attendant, birth_month FROM birth_data_table WHERE delivery_method='Cesarean' AND birth_month='%s'" % patient
print query
query_results=pd.read_sql_query(query,con)
print query_results
births = []
for i in range(0,query_results.shape[0]):
births.append(dict(index=query_results.iloc[i]['index'], attendant=query_results.iloc[i]['attendant'], birth_month=query_results.iloc[i]['birth_month']))
the_result = ''
return render_template("output.html", births = births, the_result = the_result)
Now go to http://127.0.0.1:5000/input and input a 3 letter month abbreviation (e.g. Aug). The result should be that you are taken to http://127.0.0.1:5000/output?birth_month=Aug. You will see the returned index, birth_month, and attendant of the input month births, and the next section is a placeholder for now.
Let’s make an analysis package of sorts, where you might build out your model. In flaskexample/ create a file named a_Model.py with a simple function called ModelIt.
def ModelIt(fromUser = 'Default', births = []):
in_month = len(births)
print 'The number born is %i' % in_month
result = in_month
if fromUser != 'Default':
return result
else:
return 'check your input'
Add the following line to the top of views.py in order to include this ModelIt function:
from a_Model import ModelIt
and call it inside @app.route('/output') by changing the last two lines to:
the_result = ModelIt(patient,births)
return render_template("output.html", births = births, the_result = the_result)