
Welcome!
Everything is fine.
Flask 4
Let's finish the core work with Flask. You're now more than prepared for the Lookup assignment
Plan¶
- Announcements
- Questions
- Routing Patterns
- Flask Starter
- Breakout
Announcements¶
- I'm grading the Proposals, hoping to be done today. But never delay starting the next phase.
- Partners for Lookup are assigned
- Next we, we'll switch Tuesday and Wednesday, so we can followup with GIT but let you use Tuesday as a work day.
Lookup Partners¶
2 Adhel+Anushé 4 Alexandra+Olivia 6 Allie+Ivy 8 Amish+Rachel 10 Angel Liu+Toshali 12 Angel Xu+Manasvi 14 Annie+Carol 16 Ariel+Daniela 18 Autumn+Grace 20 Celeste+Michelle 22 Cheryl+Heeba 24 Jennifer+Peyton 26 Soumaya+Zeph 28 zsolo Cherie
Questions¶
[quiz questions](../../quizzes/quiz
One-Route GET/POST Forms¶
So far, we've seen forms that post to a different route:
@route('/get-form/')
def get_form():
# this is just a blank form
return render_template('form.html', action=url_for('do_form'))
@route('/do-form/')
def do_form():
if request.method == 'GET':
data = request.args
else:
data = request.form
...
The first route just sends a blank form to the user. The second processes the form submission. That pattern works fine.
In the very common case that the form uses the POST method, we can collapse the two routes/URLs to a single URL, where a GET of that URL sends the blank form, and the POST processes the form. Like this:
@route('/form1/')
def form1():
if request.method == 'GET':
# this is just a blank form
return render_template('form.html')
else:
data = request.form
...
Furthermore, the FORM tag in the HTML can just have the empty string as the ACTION, like this:
<form method=POST action="">
...
</form>
That's because an empty URL goes to the current URL. Thus, a single route does it all, and the form's HTML is independent of the route, which is a nice bit of modularity.
However, there's a possible vulnerability with this technique, so it's better to always specify the ACTION:
<form method=POST action="{{url_for('form1')}}">
...
</form>
Example
There is a working example in the flask3
folder, in the file
example_form_processing_one_route.py
.
Here's the HTML (look at the code in templates/ln.html
:
<!doctype html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Natural Logs</title>
</head>
<body>
<h1>Compute a Natural Logarithm</h1>
{% if msg %}
<p>{{msg}}</p>
{% endif %}
{% if y %}
<p>The Natural Log of {{x}} is {{y}}</p>
{% endif %}
<form method="POST" action="{{url_for('ln')}}">
<p><label>Number to Logify
<input name="x" type="number" step="any">
</label></p>
<p><input type="submit" value="POST"></p>
</form>
<h2>Menu</h2>
<form method="POST" action="{{url_for('ln')}}">
<p><label>Number (radicand)
<select name="x">
<option>Choose One</option>
<option value="2.718281828459">e</option>
<option value="7.389">e squared</option>
</select></label></p>
<p><input type="submit" value="submit"></p>
</form>
</body>
</html>
(Notice that the ACTION specifies the correct destination.)
And the corresponding Flask route in
example_form_processing_one_route.py
:
from flask import (Flask, url_for, render_template, request)
import math
from all_examples import app
@app.route('/ln/', methods=['GET','POST'])
def ln():
if request.method == 'GET':
return render_template('ln.html')
else:
x = request.form.get('x')
try:
y = math.log(float(x))
return render_template(
'ln.html',x=x,y=y)
except:
return render_template(
'ln.html',
msg=('Error computing ln of {x}'
.format(x=x)))
- There is only one route
- We use GET to request an empty form
- We use POST to request an answer
You can run this example by running the all_examples.py
file in the
flask3
folder and choosing the routes shown above.
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)
Updated Code¶
I've updated the code in the ~cs304/pub/downloads/flask3/
folder, so
you could re-copy that, but it's probably better to keep the old and
copy a new folder for flask4
:
cd ~/cs304/
cp -r ~cs304/pub/downloads/flask4 flask4/
I'll run the all_examples
file and we'll walk through some examples.
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
You can look at a working example in your flask4
folder. The code is
in example_post_redirect_get2.py
. This code refers to some
templates.
- One is
templates/msg.html
which just displays a message to the user. - Another is a form to ask the user for something to look up
templates/city_form.html
- The last is a form to allow the user to update the database with a new city/country pair.
The lookup form:
<!doctype html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Country Lookup</title>
</head>
<body>
<h1>Look up a Country</h1>
<h2>{{method}} Form</h2>
<!-- specifying the url in the action avoids certain vulnerabilities -->
<form method="{{method}}" action="{{action}}">
<p><label>City
<input type="text" name="city">
</label></p>
<p><input type="submit" value="submit"></p>
</form>
<h2>{{method}} Menu</h2>
<form method="{{method}}" action="{{action}}">
<p><label>City
<select id="city2" name="city">
<option>Choose One</option>
{% for city in cities %}
<option>{{city}}</option>
{% endfor %}
</select></label></p>
<p><input type="submit" value="submit"></p>
</form>
</body>
</html>
- We will specify the
method
andaction
. Not because that's normal, but because it lets us do some comparisons. - Note how the menu is generated from data!
The ADD form:
<!doctype html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Add City</title>
</head>
<body>
<h1>Add a New City/Country Pair</h1>
<h2>POST</h2>
<form method="POST" action="{{action}}">
<p><label>City
<input type="text" name="new_city">
</label></p>
<p><label>Country
<input type="text" name="new_country">
</label></p>
<p><input type="submit" value="POST"></p>
</form>
</body>
</html>
Here are the Flask routes, from example_post_redirect_get2.py
:
from flask import (Flask, url_for, render_template, request,
redirect, flash)
from all_examples import app
import countries
def lookup(city):
if city in countries.known:
return ('{city} is the capital of {country}'
.format(city=city,
country=countries.known[city]))
else:
return ('''I don't know the country whose capital is {city}'''
.format(city=city))
# Simple, informational, GET request, cacheable and refreshable
@app.route('/country/<city>')
def country(city):
return render_template('msg.html', msg=lookup(city))
# ================================================================
# Lookups via a form
# this example uses forms with the GET method, which works fine, but
# with an ugly, non-authoritative URL.
@app.route('/ask_city_get/', methods=['GET'])
def ask_city_get():
city = request.args.get('city')
if city is None:
# return a blank form
return render_template(
'city_form.html',
method='GET',
action=url_for('ask_city_get'),
cities=countries.known.keys())
else:
return render_template('msg.html', msg=lookup(city))
# this example processes POST in a way we can "refresh" showing the
# annoyance of the browser's question.
@app.route('/ask_city_post/', methods=['GET','POST'])
def ask_city_post():
if request.method == 'GET':
return render_template(
'city_form.html',
method='POST',
action=url_for('ask_city_post'),
cities=countries.known.keys())
else:
city = request.form.get('city')
return render_template('msg.html', msg=lookup(city))
@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))
# ================================================================
# This example shows a use of POST to *update* a data structure
@app.route('/add_city_post/', methods=['GET','POST'])
def add_city_post():
if request.method == 'GET':
return render_template('add_city_form.html')
else:
new_city = request.form.get('new_city')
new_country = request.form.get('new_country')
countries.known[new_city] = new_country
return render_template(
'msg.html',
msg=('Thanks for entering {city} as the capital of {country}'
.format(city=new_city,
country=new_country)))
# This example improves the previous to use POST-REDIRECT-GET pattern
# So, this is the BEST example of how to update.
@app.route('/add_city_prg/', methods=['GET','POST'])
def add_city_prg():
if request.method == 'GET':
return render_template('add_city_form.html')
else:
new_city = request.form.get('new_city')
new_country = request.form.get('new_country')
countries.known[new_city] = new_country
return redirect(url_for('country',city=new_city))
Demos¶
I'll run through the demos, expanding on the following:
- /country/Paris Simple lookup of with nice url for authoritative page
- /ask_city_get/ form w/ GET to request lookup; can be refreshed, no nice url
- /ask_city_post/ form w/ POST to request lookup; can't be refreshed
- /ask_city_redirect/ form w/ GET to request lookup; redirects. Can be refreshed, and nice url
- /add_city_post/ form to ADD a city via POST; Can't be refreshed
- /add_city_prg/ form to ADD a city via POST-REDIRECT-GET; can be refreshed
Flask-Starter¶
Any questions about the Flask Starter?
Breakouts¶
You're welcome to work on Forms or Lookup, or ask questions.