Make the club page's sign-up form work: the form sends its answers, a route reads them, adds the new member and goes back home.
In HTML & CSS the Join button did nothing. Now Python is behind the form, so it can really sign people up.
In HTML & CSS your club page had a form that went nowhere. With method="post" action="/join", pressing the button sends the form's answers to the address /join. A route with methods=["POST"] receives them, and request.form["name"] looks up the box whose name attribute is name. members.append(...) adds the new member to the end of the list. Then redirect("/") sends the browser back to the home page.
Make your prediction first. Help opens once you've locked it in.
from flask import Flask, render_template_string, request, redirect
app = Flask(__name__)
members = ["Ada"]
PAGE = """<p>{{ members }}</p>
<form method="post" action="/join">
<input name="name">
<button>Join</button>
</form>"""
@app.route("/")
def home():
return render_template_string(PAGE, members=members)
@app.route("/join", methods=["POST"])
def join():
members.append(request.form["name"])
return redirect("/")
app.run()
Follow the answer: the form sends it to /join, join() appends it, then redirect("/") goes back home.
Run it and join the club twice, with different groups. Then press Run again: where did the new members go?
from flask import Flask, render_template_string, request, redirect
app = Flask(__name__)
members = [
{"name": "Ada", "group": "Champions"},
{"name": "Sam", "group": "Players"},
]
PAGE = """<h1>Chess club</h1>
<h2>Members</h2>
<ul>
{% for m in members %}
<li>{{ m["name"] }} ({{ m["group"] }})</li>
{% endfor %}
</ul>
<h2>Join the club</h2>
<form method="post" action="/join">
<label for="name">Name</label>
<input id="name" name="name">
<label for="group">Group</label>
<select id="group" name="group">
<option>Beginners</option>
<option>Players</option>
<option>Champions</option>
</select>
<button>Join</button>
</form>"""
@app.route("/")
def home():
return render_template_string(PAGE, members=members)
@app.route("/join", methods=["POST"])
def join():
name = request.form["name"].strip()
group = request.form["group"]
members.append({"name": name, "group": group})
return redirect("/")
app.run()
from flask import Flask, render_template_string, request, redirect
app = Flask(__name__)
members = [
{"name": "Ada", "group": "Champions"},
{"name": "Sam", "group": "Players"},
]
PAGE = """<h1>Chess club</h1>
<h2>Members</h2>
<ul>
{% for m in members %}
<li>{{ m["name"] }} ({{ m["group"] }})</li>
{% endfor %}
</ul>
<h2>Join the club</h2>
<form method="post" action="/join">
<label for="name">Name</label>
<input id="name" name="name">
<label for="group">Group</label>
<select id="group" name="group">
<option>Beginners</option>
<option>Players</option>
<option>Champions</option>
</select>
<button>Join</button>
</form>"""
@app.route("/")
def home():
return render_template_string(PAGE, members=members)
@app.route("/join", methods=["POST"])
def join():
name = request.form["name"].strip()
group = request.form["group"]
members.append({"name": name, "group": group})
return redirect("/")
app.run()
Why a form ends with a redirect.
What a select sends.
A box the form does not send.
Pressing Join gives 400 Bad Request. Click the line that is wrong.
Press Join with an empty box: a member with no name appears. Make the app refuse a blank name and say why.
In join(), after reading name: if name == "": return the page with an error message instead of adding the member.
render_template_string(PAGE, members=members, error="Please type your name."), and in the template {% if error %}<p>{{ error }}</p>{% endif %}.
Ask for each member's year as well, and show it in the list.
Add a Year box with name="year", read it with request.form["year"], store it in the member's dictionary and show it in the list.
If the name is blank, show the page again with a message, and do not add anyone.
Run your program. This ticks when your code does all of the above.
Also ask for each member's year level, and show it in the list.
Run your program. This ticks when your code does all of the above.
Add a form under the results table: a dropdown of the players (made by the template) and an Add a win button that adds 1 to that player's wins.
Add a form to record a win: choose a player, press the button, and their wins go up by 1.
Run your program. This ticks when your code does all of the above.
The members disappear on every Run, because they live in a variable. Next: save them to a file so they last.
Students make the club page's sign-up form work with Flask, reading the answers with request.form and redirecting home, then refuse blank names, add a year level and build a form that records a win. About 50 minutes.
Open the teacher guide: answers, solutions, slides and a worksheet.
Read the unit primer first. <form method="post" action="/join"> sends the form to /join when the button is pressed; each box is sent only if it has a name attribute, under that name (the id is for the label; the bug task has an id and no name, and Flask replies 400 Bad Request). A route accepts a sent form with methods=["POST"] and reads it with request.form["name"]. After changing the data it returns redirect("/"), so the browser shows the home page again (and a reload does not send the form twice). The server checks what it gets (a blank name), because anyone can send anything to an address. Members live in a variable, so they vanish on Run: that motivates the next lesson. In the playground the form is passed to the app by the App tab; on a computer the browser sends it to Flask.
The name attribute on form boxes. Ask students to list what the form sends, box by box, before they look at the route.
Stretch: Refuse a name that is already a member, with its own message.
Support: Give students the if with the error return, and ask them only to add the error to the template.
Next lesson saves the members to a CSV file so they survive a Run. Later, a JSON route lets other programs read the list.