Save each new member to a CSV file, and read the file to show the list, so sign-ups last after the app stops.
A web app that forgets everyone when it restarts is no use. Real apps keep their data in files and databases.
Last lesson the members vanished on every Run, because they lived in a variable. Now they live in a file, members.csv (open it in the Files tab). The home page reads the file every time; joining adds a row to the end of it. open(name, "a") opens a file to add to the end, and csv.writer(f).writerow([name, group]) writes one row, and list(csv.DictReader(f)) reads the file back as a list of dictionaries, one per row, using the first line (name,group) as the keys.
Make your prediction first. Help opens once you've locked it in.
with open("log.txt", "w") as f:
f.write("Ada\n")
with open("log.txt", "a") as f:
f.write("Sam\n")
with open("log.txt") as f:
print(f.read())
"w" opens a file for writing (it empties it first). What does "a" do differently?
Join the club, then press Run again, and reload the page: the new member is still there. Open members.csv in the Files tab to see the new row.
import csv
from flask import Flask, render_template_string, request, redirect
app = Flask(__name__)
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>
{% if error %}<p style="color: crimson">{{ error }}</p>{% endif %}
<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>"""
def load_members():
with open("members.csv") as f:
return list(csv.DictReader(f))
def save_member(name, group):
with open("members.csv", "a", newline="") as f:
csv.writer(f).writerow([name, group])
@app.route("/")
def home():
return render_template_string(PAGE, members=load_members())
@app.route("/join", methods=["POST"])
def join():
name = request.form["name"].strip()
group = request.form["group"]
if name == "":
return render_template_string(PAGE, members=load_members(), error="Please type your name.")
save_member(name, group)
return redirect("/")
app.run()
import csv
from flask import Flask, render_template_string, request, redirect
app = Flask(__name__)
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>
{% if error %}<p style="color: crimson">{{ error }}</p>{% endif %}
<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>"""
def load_members():
with open("members.csv") as f:
return list(csv.DictReader(f))
def save_member(name, group):
with open("members.csv", "a", newline="") as f:
csv.writer(f).writerow([name, group])
@app.route("/")
def home():
return render_template_string(PAGE, members=load_members())
@app.route("/join", methods=["POST"])
def join():
name = request.form["name"].strip()
group = request.form["group"]
if name == "":
return render_template_string(PAGE, members=load_members(), error="Please type your name.")
save_member(name, group)
return redirect("/")
app.run()
Why the page reads the file each time.
What newline="" is for.
A save that wipes the club.
After saving Zoe, the file reads back as an empty list, and Ada and Sam have gone. Click the line that is wrong.
Refuse a name that is already in the file, whatever its capitals, with a message saying it is already a member.
In join(), before saving: loop over load_members(), and if a member's name matches (compare with .lower()), return the page with an error.
error=name + " is already a member."
Show the number of members in the Members heading.
In home(): members = load_members(), then pass count=len(members) to the template, and show {{ count }} in the heading.
Refuse a name that is already a member, with a message that says it is "already a member".
Run your program. This ticks when your code does all of the above.
Show how many members there are in the heading.
Run your program. This ticks when your code does all of the above.
The Add a win form is there, but win() does nothing yet. Make it add 1 to the chosen player's wins and save the whole table back to results.csv, header first, so the wins last after a Run. ("a" only adds rows; changing a row means writing the file again.)
Add a win to the chosen player and save every row back to results.csv, so wins last.
Run your program. This ticks when your code does all of the above.
On a computer, members.csv would sit next to app.py. Next: share the members with other programs as JSON.
Students save sign-ups to members.csv (adding a row with "a" and csv.writer) and read the file for every page, so members survive a Run and a reload, then refuse repeats, count members and save wins by rewriting results.csv. About 50 minutes.
Open the teacher guide: answers, solutions, slides and a worksheet.
Read the unit primer first. members.csv and results.csv are put in each student's Files tab by the lesson (the Files tab can put the originals back). Files are kept per course in the browser, so they survive Run and a page reload. open(name, "a") adds to the end of a file; open(name, "w") empties it first (the Predict and the bug task: saving with "w" wipes the header and every member). csv.writer(f).writerow([...]) writes one row; newline="" stops blank lines appearing between rows on Windows. Every page reads the file again, so it is always up to date. Changing a row (the Make) means reading all rows, changing one, and writing the whole file back with "w", header first. The info question is about why busy sites use a database instead of a file.
Read-change-write for the Make. Ask students to write the three steps as comments before any code.
Stretch: Add a Leave form that removes a member (read all rows, keep the others, write them back).
Support: Give students the loop that changes the wins, and ask them only to write the rows back.
Next lesson sends the members as JSON for other programs. A database (SQLite) is the next step for real apps; Python has sqlite3 built in.