
Welcome!
Everything is fine.
Flask Part 4¶
Let's finish the core work with Flask. You'll be prepared for the CRUD assignment
Plan¶
- Announcements
- Routing Patterns
- Questions
- Flask Starter
- Breakout
Announcements¶
- Grading status: about half have submitted H5. Don't delay!
- I'll grade proposals soon, but assume it's full speed ahead.
- 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 stringflash(msg, [category])
to store a message for displayget_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
- The example redirected this GET request
@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:
GET /insert-new-country/
requests a blank form- blank form sent to browser
POST /inset-new-country/
send a filled form to the server, which adds the country and- returns a
redirect /country-by-id/<cid>
to the browser, which GET /country-by-id/<cid>
requests information about the new city
Refreshes of the last GET request are fine.
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
- /country-by-id/2 Simple lookup of country with nice url for authoritative page
- /new-country-form/ GET form to fill out
- /insert-country/ this doesn't work as a URL; can only POST to it
- /insert-new-country/ GET a form; this one uses POST-REDIRECT-GET
take-aways
- Use GET when caching and refresh make sense
- An "ugly" URL with GET is okay
- Use redirect when there's a better URL
- Use POST when updating the database
- 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.
Exercise¶
You're welcome to work on Forms or Lookup, or ask questions.
Summary¶
You know about
- forms
- request methods
- redirects
- flashing