This tutorial is out of date! Try my longer, updated Flask-SQLite webapp tutorial here

Building a Flask webapp

Why Flask is the hero that we need

The Problem

If we were normal people, going to make a normal website, we might start off our website with a real big header proclaiming how great it is. The HTML we’d write might look like this:

<h1>Cool Website about Stuff</h1>

But since we’re professionals, and we’re data wranglers, and we’re specifically data wrangling school data, we’ll probably start it off with the name of a school.

<h1>PS 15 Roberto Clemente</h1>

But then eventually we’ll have to make one about a different school, which will have a completely different headline associated with it

<h1>The Urban Assembly School of Business for Young Women</h1>

and if we have several hundred high schools in NYC it is going to take a very long time, much less if we want to expand to the thousands and thousands of schools across America.

Welcome to the backend

Luckily, we don’t have to write that HTML. Instead, we write Python code that writes the HTML.

That Python code looks into a database, finds the name of the school we’re looking at, and slaps the <h1>PS 15 Roberto Clemente</h1> right there on the page. If we’re looking at a different school, it gets a different name from the database, and the HTML gets a different name inside of the <h1> tag.

This is called the backend of a site. The frontend is all of the HTML and CSS and JavaScript that the user interacts with, while the backend is the hard work that puts that HTML & co. together.

The backend can be any sort of programming language, and can be more or less complicated based on what it’s intended to do. Today we’ve decided we’re going with Python, and we’re using a rather simple framework called Flask

Introducing Flask

Flask is a web microframework built in Python.

  1. A framework is more or less similar to a library (just read this) in that it gives you a barrelful of tools to get a task done. The particular barrel we’re being given helps us build web sites.
  2. A microframework is just a small, lightweight framework. An example of a Python site-building framework that pulls zero punches and packs in the functionality would be Django. Our site isn’t going to be terribly complicated, so we’re going to stick with Flask.

Another popular web framework you might have heard of is Ruby on Rails (which, of course, uses Ruby).

A Flask Hello World

While you can put everything in your Flask project into a single file, it’s most always better to organize it into multiple files - put your Python code over here, put your HTML templates over there, some other stuff somewhere else.

Our project is going to be called nyc-schools. Add a folder called static and another one called templates.

nyc-schools/
  app.py
  static/
  templates/

static is for files that aren’t dynamically generated, like images and JavaScript files and the like.

templates is the templated versions of the dynamically generated web pages (i.e., the previously-mentioned <h1> with a placeholder inside of it).

app.py is our application file. It should contain the following code:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>Hello world!</h1>'

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

From terminal, in the nyc-schools directory, run the command

python app.py

And then do me the favor of visiting http://127.0.0.1:5000 to check out your freshly minted web application.

Templates and Databases and Routing, Oh My

Starting with Templates

So you know how we mashed up a bunch of HTML in the “Hello world” section? That’s horrid, and we should be drawn and quartered for it. HTML belongs in an .html file, and we’re going to put it there before anyone notices.

Add a file called index.html into your templates folder and have it contain the text <h1>Hello world</h1>. We’re going to render that page instead of just sending back some text, which takes two changes:

Alter app.py’s index function to read as follows

@app.route('/')
def index():
  return render_template('index.html')

Add a new import line to the top of app.py

from flask import render_template

Now refresh. Cool, right? How nothing changes? render_template is just reaching down into the templates/ directory, grabbing index.html, and rendering it instead of the text we had there before.

To make use of this (kind of), let’s edit the code in index.html just to make a list of a few schools.

<h1>NYC High Schools</h1>
<ul>
  <li>Henry Street School for International Studies</li>
  <li>University Neighborhood High School</li>
  <li>East Side Community School</li>
  <li>Marta Valle High School</li>
</ul>

Refresh, rejoice.

Communicating with the template

Let’s say you have a cool variable in our application, like what we think the number of schools is in NYC.

@app.route('/')
def index():
  school_count = 350
  return render_template('index.html')

You can actually send that value to index.html, and then use it there to fill in empty spots in the template.

First we’ll edit our render_template to send the information to the template.

@app.route('/')
def index():
  school_count = 350
  return render_template('index.html', count=school_count)

Then we’ll edit our index.html to use the variable, by using `` as a placeholder, with the variable name inside.

<h1>NYC High Schools</h1>
<p>There are  high schools in NYC</p>
<ul>
  <li>Henry Street School for International Studies</li>
  <li>University Neighborhood High School</li>
  <li>East Side Community School</li>
  <li>Marta Valle High School</li>
</ul>

But that number doesn’t seem quite right. How are we going to change it?

Adding in a Database

A database is going to be the saving grace of this project - by pulling information out of the database, we don’t have to guess at the number of schools, nor do we have to type each and every one of their names out. It’s a paradise!

We’re going to use our models.py file from the peewee tutorial - go ahead and copy it into the nyc-schools/ directory, along with schools.db.

NOTE: If you don’t have the files, you can download them here: models.py and schools.db.

Using our database in app.py is the same as when we imported it to learn how peewee works. Add another import to the top of the file:

from models import *

Now we can use School and Score just like we did before! In order to get an accurate count of the number of schools, we’re going to use School.select().count(). It selects all of the schools, then laboriously counts them.

@app.route('/')
def index():
  school_count = School.select().count()
  return render_template('index.html', count=school_count)

Refresh! Rejoice! 437, right?

Leveling up

I’m still angry about typing out all of those school names in the index.html - peewee can help with that, as well. Instead of just sending boring variables like integers to the template, we can send an entire selection of schools.

@app.route('/')
def index():
  school_count = School.select().count()
  schools = School.select().order_by(School.school_name.asc())
  return render_template('index.html', count=school_count, schools=schools)

Then in our index.html, we can loop through them all and display the school_names.

<h1>NYC High Schools</h1>
<p>There are {{ count }} high schools in NYC</p>
<ul>
  {% for school in schools %}
    <p>{{ school.school_name }}</p>
  {% endfor %}
</ul>

Then edit our index() to pull all of the schools out of the database. We’ll pull them down in alphabetical order.

@app.route('/')
def index():
  schools = School.select().order_by(School.school_name.asc())
  return render_template('index.html')

Step three: Send them to the template

Now we have all the schools, we just need to send them to the template, and then use them in the template. Edit index one more time to send them to the template:

@app.route('/')
def index():
  schools = School.select().order_by(School.school_name.asc())
  return render_template('index.html', schools=schools)

Routing around

Cool, ‘eh? But it just isn’t enough, I know. If we’re going to win a Pulitzer we need individual school pages.

If we take a look at our index, there’s a suspicious line right about it:

@app.route('/')
def index():
  schools = School.select().order_by(School.school_name.asc())
  return render_template('index.html')

@app.route('/') is a route - a URL you can type in to get to a specific function in the Flask app. That one defines / and tells Flask to execute index() whenever someone runs across it. Let’s make another one!

@app.route('/schools/clemente')
def school():
  return 'You are looking at PS15 Roberto Clemente'

And now you should be able to visit http://127.0.0.1:5000/schools/clemente and see that route run!

Of course, we should probably set up a template for it. Let’s call it school.html and put it into templates/.

<h1>PS15 Roberto Clemente</h1>
<p>Phone number: 212-555-5555</p>

And edit app.py to point to the template:

@app.route('/schools/clemente')
def school():
  return render_template('school.html')

Refresh to double-check it worked, and we’re on our way to the next step!

Want to hear when I release new things?
My infrequent and sporadic newsletter can help with that.