Welcome!
Everything is fine.

Flask Part 4

Let's finish the core work with Flask. You'll be prepared for the CRUD assignment

Plan

  1. Announcements
  2. Routing Patterns
  3. Questions
  4. Flask Starter
  5. Breakout

Announcements

  1. Grading status: about half have submitted H5. Don't delay!
  2. I'll grade proposals soon, but assume it's full speed ahead.
  3. Git tomorrow. Please do a little reading and make sure you have a Github account.

CRUD demo

  • your solution will use forms with GET, forms with POST, redirects, and much more
  • the /update/ route POSTS to the same route that supplies the form, so that the user can revise endlessly.

Stuff from Last Time

Review

  • Where is my data?
    • endpoint parameters: function parameters
    • query string: request.args
    • message body of a POSTed form: request.form
    • Consider this URL /journal/2023/search?text=large+language+models
  • Flashing:
    • app.secret_key has to be set to a string
    • flash(msg, [category]) to store a message for display
    • get_flashed_messages([with_categories]) to pull the messages out
    • See message flashing for a more thorough explanation than the tutorialspoint one.
  • Redirects
    • The example redirected this GET request
      • /ask_city_redirect/?city=Paris to
      • /country/Paris
    • code below
@app.route('/ask_city_redirect/', methods=['GET','POST'])
def ask_city_redirect():
    city = request.args.get('city')
    if city is None:
        return render_template(
            'city_form.html',
            method='GET',
            action=url_for('ask_city_redirect'),
            cities=countries.known.keys())
    else:
        print(city)
        print(url_for('country',city=city))
        # redirect is a new import from flask
        return redirect(url_for('country',city=city))

Flask Starter

We'll look at this later today, so let's copy it now:

cd ~/cs304
cp -r ~cs304/pub/downloads/flask-starter flask-starter
cd flask-starter

Forms

Last time we learned that forms can be involved in two different handlers/routes/endpoints:

  • One that sends the browser a blank form to fill out, and
  • One that processes the filled-out form, executing a database search/update or whatever needs to be done.

It's easy to get confused about these and quite common, but make sure you understand it before we go on.

@route('/get-blank-form/')
def get_form():
    # this is just a blank form
    # note that the form has ACTION=url_for('do_form')
    return render_template('form.html')

@route('/do-form/', methods=['POST'])
def do_form():
    data = request.form
    ...

The first route just sends a blank form to the user. The second processes the form submission. That pattern works fine.

One-Route GET/POST Forms

A very common situation is that the form has ACTION=POST and so the handler function can distinguish these two situations by whether the request uses GET or POST. In particular:

  • GET will cause the handler function to send the browser a blank form, and
  • POST will cause the handler to process a filled-out form.

In this very common case that the form uses the POST method, we can collapse the two endpoints/routes/URLs to a single endpoint, where a GET of that URL sends the blank form, and the POST processes the form. Like this:

@route('/form1/', methods=['GET', 'POST'])
def form1():
    if request.method == 'GET':
        # this is just a blank form
        return render_template('form.html')
    else:
        # use request.form because of POST
        data = request.form
        ...

One Handler Example

There is a working example in the flask4 folder, which we'll copy in a bit.

Here's the HTML (look at the code in templates/new-ln.html):

{% extends "base.html" %}

{% block main_content %}

    <h1>New Country Form</h1>
    
    <p>Form to insert a new country. All fields are required. </p>

    <form method="POST" action="{{url_for('insert_new_country_post_redirect_get')}}">
        <p><label>Country Name:
                <input required type="text" name="new_cname">
            </label>
        <p><label>Capital City:
                <input required type="text" name="new_capital">
            </label>
        <p><label>Continent
                <select required name="continent">
                    <option value="">choose one</option>
                    <option>Africa</option>
                    <option>Asia</option>
                    <option>Europe</option>
                    <option>North America</option>
                    <option>South America</option>
                </select>
            </label>
        <p><button>submit</button></p>
    </form>

    {% endblock %}
    

(Notice that the ACTION specifies the correct destination.)

And the corresponding Flask route in app.py

@app.route('/insert-new-country/', methods=['GET', 'POST'])
def insert_new_country_post_redirect_get():
    if request.method == 'GET':
        # send a blank form
        return render_template('new-country-form-post-redirect-get.html',
                               page_title = 'New Country Form')
    else:
        # method has to be POST, so the form has been filled out
        cname = request.form.get('new_cname')
        capital = request.form.get('new_capital')
        continent = request.form.get('continent')
        new_cid = db.insert_country(cname, capital, continent)
        print(f'inserted {cname} which was given the ID {new_cid}')
        # we *could* return a template, and some cases we might, but
        # in this case, we are returning a redirect to the "authoritative"
        # endpoint for this country
        return redirect(url_for('country_by_id', cid=new_cid))
  • There is only one handler function and only one endpoint
  • We use GET to request an empty form
  • We use POST to request an answer

We will run this example today.

Redirects

We use redirects when there's a better, more authoritative (worth bookmarking) or nicer (shorter, prettier, less cluttered) URL.

We don't use them just to avoid a little coding; we can do that with shared functions:

def useful_function(x,y):
   pass

@app.route('route1')
def route1():
    z = useful_function(1,2)

@app.route('route2')
def route2():
    z = useful_function(56,78)

Above, we use redirect because we wanted to send the user to the "authoritative" page for the new country that was just inserted.

The POST-Redirect-GET Pattern

Anytime we POST to a URL, the user might want to refresh the page, which can cause re-submission of the form data (though the browser will try to prevent that). One way to reduce the risks and consequences of that is the POST-Redirect-GET pattern.

To do that, the route that we POST do returns a redirect to a route that is safe to refresh. Imagine an e-commerce site that, after you post to the route to submit your order, redirects you to a thanks/receipt page that is safe to refresh.

See: POST/REDIRECT/GET.

Example POST-Redirect-GET

I'll draw this (badly) on the board:

  1. GET /insert-new-country/ requests a blank form
  2. blank form sent to browser
  3. POST /inset-new-country/ send a filled form to the server, which adds the country and
  4. returns a redirect /country-by-id/<cid> to the browser, which
  5. GET /country-by-id/<cid> requests information about the new city

Refreshes of the last GET request are fine.

Questions

quiz questions

Today's Examples

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

cd ~/cs304/ 
cp -r ~cs304flask/pub/downloads/flask4 flask4
cd flask4

Demos

We've seen most of these, but I'm happy to return to any of them

take-aways

  1. Use GET when caching and refresh make sense
  2. An "ugly" URL with GET is okay
  3. Use redirect when there's a better URL
  4. Use POST when updating the database
  5. Use POST-REDIRECT-GET when there's a good place to redirect to; avoids refresh anomalies

Flask-Starter

The Flask starter app

cd ~/cs304/ 
cp -r ~cs304flask/pub/downloads/flask-starter flask-starter

We'll run that and explore a bit.

flask-starter

Exercise

You're welcome to work on Forms or Lookup, or ask questions.

Summary

You know about

  • forms
  • request methods
  • redirects
  • flashing