Flask 2

In this reading, we'll dig deeper into Flask, looking at issues like:

  • constructing URLs
  • extracting information from GET and POST requests
  • sending static files to the browser
  • rendering templates with Jinja2

Flask Tutorials

Please read the following:

Summary

The following information summarizes the above. I don't think it's a substitute, but it may supplement.

Constructing URLs

They don't motivate this well. Here's my attempt.

Constructing a URL doesn't seem hard, but nevertheless, Flask provides a function to do so, called url_for, which you should use instead of doing it yourself. This is important to make your Flask code portable and more easily deployed.

For example, when I developed the CS304 demo, I had urls like /lookup and /nm/123. But when I deployed the demo, every URL got prefixed with /cs304demo/ (by my choice). So I now had urls like /cs304demo/lookup and /cs304demo/nm/123. And I didn't have to re-write any of my code! All the urls were re-built by url_for to use the prefix. That would not have happened if I had just specified the URL.

always use url_for

The argument to url_for is not the URL but rather the name of the handler function as a string. (In practice, these are often the same, but not always.) For example:

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

I would generate the url for that page by saying url_for('home')

Note that you should use url_for anywhere that a URL occurs, including in your template files.

(If you need to generate url in a static file, see this Stack Overflow post on Flask url_for in a CSS file.)

HTTP Methods

The default way that a request comes in is as a GET request. Sometimes, it comes in as POST.

Flask provides a "global" variable (it's not really a global, but we won't worry about the distinction) called request that has a lot of information about the request. One attribute is the method.

When you define a route, you can specify which methods it should respond to. The default is just GET, but you can add POST or make it respond solely to POST. Examples:

# this route supports only GET
@app.route('/aaa/')
def aaa():
     return '<p>request arrived as GET'

# this route supports both, so we have to use an IF 
@app.route('/bbb/', methods=['GET','POST'])
def bbb():
    if request.method == 'GET':
        return '<p>request arrived as GET'
    else:
        return '<p>request arrived as POST'

# this route supports only POST
@app.route('/ccc/', methods=['POST'])
def ccc():
     return '<p>request arrived as POST'

Form Data

An HTML form can send data to the server. Suppose that the form has inputs: city, state, zip and the user submits Wellesley, MA, 02481

If the FORM uses the GET method, the data comes in the URL:

/path/to/place&city=Wellesley&state=MA&zip=02481

If the FORM uses the POST method, the data comes in the body, similarly encoded:

/path/to/place
other headers

city=Wellesley&state=MA&zip=02481

Accessing Form Data

Flask can access the data either way it's sent, but in different places.

  • Flask puts GET data in request.args
  • Flask puts POST data in request.form

Both objects are dictionary-like objects, and can be accessed the same way normal Python dictionaries are accessed.

This route would reflect back the form data:

@app.route('/reflect/', methods=['GET','POST'])
def reflect():
    if request.method == 'GET':
        city = request.args['city']
        state = request.args['state']
        zip = request.args['zip']
        return 'You sent, via GET, {}, {}, and {}'.format(city,state,zip)
    else:
        city = request.form['city']
        state = request.form['state']
        zip = request.form['zip']
        return 'You sent, via POST, {}, {}, and {}'.format(city,state,zip)

Static Files

Web sites have a lot of static pages: CSS, JS, logo images, etc.

Flask can serve those out, just like Apache does.

The static files are put in a folder called static that is a sibling of templates

URLs are generated with url_for but with 'static' as the first argument and a keyword argument of filename for the rest. An expression like this might be put in a template file:

url_for('static', filename='style.css')

Templates

We'll learn about templates in two parts. We'll do some simple templating, and deal with inheritance another day.

Jinja2

With Flask and Jinja2, all the templates in the templates folder are read into memory and parsed when the flask application loads. This is great for efficiency. It does, however, mean that an error in any template file, even one you're not using, can cause errors when your app loads. Fair warning.

The Template Language

Now let's turn to the template language.

Variables

Double braces surround python variables, and the variable's value is inserted:

Look at the templates/hello.html file.

<!DOCTYPE html>
<html>
    <head>
        <title>Hello</title>
    </head>
    <body>

    <h1>Hello</h1>

    <p>My name is {{name}}.</p>

    </body>
</html

Here's the route that uses that template. Notice that we use the Flask render_template function to combine the static template file with the random dynamic data (one of the four names).

@app.route('/hello/') 
def hello(): 
    nom = random.randomElt(['Inigo Montoya', 
                            'Harry','Ron','Hermione']) 
    return render_template('hello.html', 
                           name = nom) 

Here's an example of a template file with a static CSS file, as well as a placeholder for a name:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>CS304: Flask</title>
    <link rel="stylesheet" type="text/css" 
          href="{{ url_for('static', filename='style.css') }}">
</head>
<body>

    <h1>Hello</h1>

    <p>My name is {{name}}.</p>

    </body>
</html>

Dictionaries

You can also access elements of dictionaries:

@app.route('/tim/') 
def tim(): 
    art = {'name': 'Arthur, King of the Britons', 
           'quest': 'To seek the grail', 
           'color': 'blue'} 
    return render_template('tim.html', dic = art) 

Look at the templates/tim.html file:

<!doctype html>
<html>
    <head>
        <title>Tim</title>
    </head>
    <body>
        <p>Name: {{ dic.name }}. 
        <p>quest: {{ dic.quest }}. 
        <p>favorite color: {{ dic.color }}. 
    </body>
</html

Notice the shortcut syntax (like JavaScript's): you can say dic.key instead of dic['key'].

Arrays

If you have an list of data, you can loop over it. Here's a template that has a loop that uses Python syntax:

@app.route('/things/') 
def things(): 
    favs = ['Raindrops on roses', 
            'whiskers on kittens', 
            'bright copper kettles', 
            'warm woolen mittens', 
            'brown paper packages tied up with strings'] 
    return render_template('things.html', things = favs) 

Look at the templates/things.html file.

<!doctype html>
<html>
    <head>
        <title>Things</title>
    </head>
    <body>
        <h1>Favorite Things</h1>

        <ul>
            {% for thing in things %}
            <li>{{ thing }}</li>
            {% endfor %}
        </ul>
    </body>
</html>

Those types: values, lists and dictionaries, will get us a long way.

The Jinja2 language also has conditionals as well as loops.