Welcome!
Everything is fine.

Flask Part 3

Today, we'll continue to work with Flask, finishing all the essential pieces to create a working database app, particularly the request object.

Goal

By the end of today, you will be able to use redirects and flashing in Flask applications. You'll be completely prepared for H6 Lookup and almost prepared for H7 CRUD.

Plan

  1. Announcements
  2. Forms in Flask: the request object
  3. redirects
  4. flashing
  5. your questions
  6. Breakout: form to specify the birth month for search

Next time:

  • POST-Redirect-GET pattern
  • other form patterns

Announcements

  • H6 Lookup
    • Template Inheritance is not required (but could be useful). It's sufficient to put the form on the main page
    • POST is not required (form uses GET)
    • Redirects are required
    • You'll handle the form submission, possibly, redirecting to the authoritative page on a single movie or person.

Assignment Demo

H6 Lookup

Exam Stuff

I will briefly review the solutions to the two parts of the exam (but not the retakes), and hand out my solutions.

Recap for today

  • GET vs POST:
    • like a postcard vs a parcel
    • request.args vs request.form
    • both are dictionary-like, so you can use request.args[key] or request.form.get('key')
  • Rendering a template and passing in data using the keyword arguments to render_template
    • render_template('template-file.html', name_in_file = local_variable, ...)
  • Redirect
    • response is not a page, but a URL that the browser is requested to GET instead of this one
    • should be done for clean interface to the app, rather than the convenience of the programmer
    • Example: redirect from a search form to a response page: https://www.imdb.com/name/nm0000123/
    • Example: redirect from a login form to a "home page": /myapp/<username>
    • Example: redirect from an order form to a response page: /myapp/confirmation/<orderid>
    • The last example is also an example of POST-REDIRECT-GET, which helps to avoid problems with, for example, re-ordering if the user refreshes a page. By redirecting them, you put them on a page that is safe to refresh, while the original order form page is not.
  • Flashing:
    • A useful standardized way to get messages displayed to the user.
    • Have to set a secret key: app.secret_key='some string'
    • Use a function to squirrel away a message to be displayed: flash('some message')
    • Have to put a mechanism in the templates to display the squirreled-away message: for msg in get_flashed_messages()
    • There's a few standard incantations for the latter.
  • Template Inheritance
    • A good way to have a theme and variations
    • Put stuff you want in every page in a base template
    • Define child variations to inherit from the base
    • It's common to put the flashing incantation in the base.
    • The base looks like a normal Jinja2 template, except override-able blocks are marked.
    • The children just list the base and their overrides to one or more blocks

Where is my Data?

  • if the data in the endpoint, before the question mark,
    • get it from the function arguments
  • if the data is in the query string, the part after the question mark, (such as a form using GET),
    • get it from request.args
  • if the data is in the body of the request, as with a form using POST,
    • get it from request.form

Questions for Today

Lots of uncertainty on this material, so we'll take our time.

This is a good time to answer your quiz questions.

Starting

To start our work for today, make sure you activate your virtual environment:

source ~/cs304/venv/bin/activate 

Today's Examples

Copy the flask3 folder from the course account to your own, and you can re-create all of today's examples:

cd ~/cs304/ 
cp -r ~cs304/pub/downloads/flask3 flask3
cd flask3

Guide: That folder has these files. We'll punt many of those until next time.

  • all_examples.py defines the app object, and imports the examples. So, we run it, but read the code in other files.
  • example_template_inheritance.py is our first example, showing route that each use a different template, but the latter two inherit from the base template.
  • example_request_method.py is our first form example, showing different request methods.
  • example_form_processing_one_route.py is our second form example, showing GET to get an empty form and POST to use it.
  • example_post_redirect_get2.py is our third form example, showing POST that redirects to a GET route to do the real work.
  • example_flashing.py is an example of flashing to report errors in an easy way.

TO DO

I will run the all_examples.py app in the guest account. You're welcome to do the same with your copy. We'll look at the code using VSC.

Example: template inheritance

For that, we'll skip ahead to next time.

Example: request_method (sqrt)

As you know, forms are critically important in Web applications. Our first example is example_request_method.py, which also refers to templates/sqrt_form.html and templates/msg.html.

<!doctype html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <title>{{title}}</title>
</head>
<body>

<h2>GET</h2>
<form method="GET" action="{{processor}}">
    <p><label>input number
            <input name="num" type="number" step="any">
    </label></p>
    <p><input type="submit" value="GET"></p>
</form>

<h2>POST</h2>
<form method="POST" action="{{processor}}">
    <p><label>input number
            <input name="num" type="number" step="any">
    </label></p>
    <p><input type="submit" value="POST"></p>
</form>

<h2>Menu</h2>
<form method="GET" action="{{processor}}">
    <p><label>select number
            <select name="num">
                <option value="2">two</option>
                <option value="3">three</option>
                <option value="3.1416">pi</option>
                <option value="4">four</option>
            </select>
        </label></p>
    <p><input type="submit" value="submit"></p>
</form>

</body>
</html>

Notice:

  • There can be more than one form on a page
  • They can have different methods. Note the difference in how the data is sent.
  • Notice the different behavior on re-submission. (Click the reload button.)
  • the ACTION needs to be filled in. Typically I put this in the template, rather than in the route.
  • How to use a SELECT menu
  • We could use url_for(...) in the template, where we have {{processor}} if it'll always go to the same place, which is typical. I separated them to make the Python code clearer.

Here is the Flask code to process it:

from flask import (Flask, url_for, render_template, request,
                   redirect, flash)
import math
from all_examples import app

@app.route('/sqrt_form/')
def sqrt_form():
    # This route just returns the form.
    return render_template('sqrt_form.html',
                           processor=url_for('compute_sqrt'))
    
@app.route('/compute_sqrt/', methods=['GET','POST'])
def compute_sqrt():
    # the request object is a new import from flask
    if request.method == 'GET':
        num = request.args.get('num')
    else:
        num = request.form.get('num')
    try:
        x = float(num)
        y = math.sqrt(x)
        return render_template(
            'msg.html',
            msg=('Square root of {x} is {y}'
                 .format(x=x,y=y)))
    except:
        return render_template(
            'msg.html',
            msg=('Error computing square root of {num}'
                 .format(num=num)))
  • The request symbol needs to be imported
  • request.method is either GET or POST
  • request.args goes with GET
  • request.form goes with POST
  • Look at the URL for the different options

Redirects

A redirect is a short response to the browser telling it to make a different request. You can say

A redirect is when the server asks the browser to request a different URL

A redirect is when the server asks the browser to request a different URL

Redirects can be used for a variety of reasons.

  • To clean up a URL using query parameters, converting:
    • /find?q=123 to
    • /name/nm123
  • To transfer someone to a more appropriate page:
    • /login?name=fred&pass=ok
    • /user/fred

You don't want to over-use redirects. It should be more about what the user sees rather than convenient coding.

As with render_template() you return the redirect:

return redirect( url_for('index') ) 

Also, you have to import the redirect function; see below.

Flashing

A web application should always let the user understand what's going on:

  • Confirmation of updates
  • Show search criteria as well as search results
  • Appropriate error messages

You can do this by inserting information on the page:

    {% if errmsg %} 

    <p>{{ errmsg }}</p> 

    {% endif %} 

A more general solution is to have a collection of messages that you can iterate over to show them all. Like this:

{% with messages = get_flashed_messages() %} 
    {% if messages %} 
        <div id="messages"> 
        {% for msg in messages %} 
            <p>{{msg}}</p> 
        {% endfor %} 
        </div> 
    {% endif %} 
{% endwith %} 

How you format the messages, of course, is up to you. Here, we've used a bunch of paragraphs in a DIV. Style it with CSS.

To add a message to the list, use the flash() function:

flash('actor inserted') 

You have to remember to import the flash function into your app. See below.

Flask uses a special kind of cookie to implement sessions and flashing, thereby allowing the flashed messages to go across re-directs. We'll discuss the implementation later. However, it's important to know that you have to set a secret key. For security reasons, this should be unguessable. Set it like this (globally in your app).

from flask import (Flask, render_template, make_response, 
                   request, redirect, url_for, flash) 
app = Flask(__name__) 

app.secret_key = 'replace this string' 

In the all_examples.py file, we generate a random secret key each time.

Example of Flashing

The example_flashing.py file demonstrates flashing.

Here are the Flask routes:

from flask import (Flask, url_for, render_template, request,
                   redirect, flash)
import math
from all_examples import app

@app.route('/log2/', methods=['GET','POST'])
def log2():
    if request.method == 'GET':
        # this gets the blank form by visiting the URL
        return render_template('log2.html')
    else:
        if request.form.get('num') is None:
            flash('No "num" in the request')
            return render_template('log2.html')
        num = request.form.get('num')
        try:
            x = float(num)
        except:
            flash('Could not convert {} to float'.format(num))
            return render_template('log2.html')
        if x == 0:
            flash('Definitely cannot compute log of zero.')
            return render_template('log2.html')
        if x < 0:
            flash('''Cannot compute log of negative;
                     using absolute value instead''')
            x = abs(x)
        # finally!
        y = math.log(x,2.0)
        return render_template('log2.html',x=x,y=y)

Breakout Exercise: People Born in Month

Your flask3 folder has a subfolder pa1 which has a copy of the people_app with a few extras in it.

Your goal is to add a page with a form that allows the user to request a list by the birthmonth they specify:

a form to ask for the birth month

You'll also process that form input and respond with the correct list

Part 1 the Form

  1. add a route that sends the empty form to the user. This only needs to be a simple route that renders the template and returns it.
  2. test that; you'll have to type in the URL
  3. add a hyperlink to the main page that goes to the correct URL
  4. test that

Part 2 Processing the Form

  1. add a route that will process the submitted form. It's just vacous for now. Return a boring page, like hello_world
  2. modify the form to send the data to that route
  3. modify the route to print the submitted form input to the console. You'll have to import the request object.
  4. test that
  5. convert the value to an int
  6. Replace the vacuous response with a real response. You can copy/paste the code from earlier in the file (though copy/paste is usually a bad sign). We'll fix this soon.
  7. test that

Part 3 Redirect

  1. Instead of the copy/pasted code, return a redirect to the correct route. You'll have to import redirect.
  2. test that

Part 4 Flashing

  1. Import flash from Flask
  2. set the secret_key attribute of the app object.
  3. flash the same message as you printed
  4. add the flashed messages incantation to your templates. See below
  5. test this

The flash incantation is something like this. You can modify the HTML, such as using OL and LI instead of DIV and P.

{% with messages = get_flashed_messages() %}
    {% if messages %}
        <div id="messages">
        {% for msg in messages %}
            <p>{{msg}}</p>
        {% endfor %}
        </div>
    {% endif %}
{% endwith %}

Solution

The solution is in the pa-done folder. Look at the templates and compare the done.py file with the app.py file.

Summary

You know about

  • forms
  • request methods
  • redirects
  • flashing