Birthdays
Problem to Solve
Create a web application to keep track of friends’ birthdays.
Distribution Code
Download the distribution code
Open VS Code.
Start by clicking inside your terminal window, then execute cd
by itself. You should find that its “prompt” resembles the below.
$
Click inside of that terminal window and then execute
wget https://cdn.cs50.net/2023/spring/psets/9/birthdays.zip
followed by Enter in order to download a ZIP called birthdays.zip
in your codespace. Take care not to overlook the space between wget
and the following URL, or any other character for that matter!
Now execute
unzip birthdays.zip
to create a folder called birthdays
. You no longer need the ZIP file, so you can execute
rm birthdays.zip
and respond with “y” followed by Enter at the prompt to remove the ZIP file you downloaded.
Now type
cd birthdays
followed by Enter to move yourself into (i.e., open) that directory. Your prompt should now resemble the below.
birthdays/ $
If all was successful, you should execute
ls
and you should see the following files and folders:
app.py birthdays.db static/ templates/
If you run into any trouble, follow these same steps again and see if you can determine where you went wrong!
Understanding
In app.py
, you’ll find the start of a Flask web application. The application has one route (/
) that accepts both POST
requests (after the if
) and GET
requests (after the else
). Currently, when the /
route is requested via GET
, the index.html
template is rendered. When the /
route is requested via POST
, the user is redirected back to /
via GET
.
birthdays.db
is a SQLite database with one table, birthdays
, that has four columns: id
, name
, month
, and day
. There are a few rows already in this table, though ultimately your web application will support the ability to insert rows into this table!
In the static
directory is a styles.css
file containing the CSS code for this web application. No need to edit this file, though you’re welcome to if you’d like!
In the templates
directory is an index.html
file that will be rendered when the user views your web application.
Implementation Details
Complete the implementation of a web application to let users store and keep track of birthdays.
- When the
/
route is requested viaGET
, your web application should display, in a table, all of the people in your database along with their birthdays.- First, in
app.py
, add logic in yourGET
request handling to query thebirthdays.db
database for all birthdays. Pass all of that data to yourindex.html
template. - Then, in
index.html
, add logic to render each birthday as a row in the table. Each row should have two columns: one column for the person’s name and another column for the person’s birthday.
- First, in
- When the
/
route is requested viaPOST
, your web application should add a new birthday to your database and then re-render the index page.- First, in
index.html
, add an HTML form. The form should let users type in a name, a birthday month, and a birthday day. Be sure the form submits to/
(its “action”) with a method ofpost
. - Then, in
app.py
, add logic in yourPOST
request handling toINSERT
a new row into thebirthdays
table based on the data supplied by the user.
- First, in
Optionally, you may also:
- Add the ability to delete and/or edit birthday entries.
- Add any additional features of your choosing!
Hints
Use flask run
to start your Flask application
Before you begin, it’s helpful to understand the current state of your application. Run flask run
in your terminal while in your birthdays
directory to start a web server that serves your Flask application. Click on the link provided by Running on URL where URL is the URL of your application.
Use HTML form
, input
, and button
elements to create a form via which to submit birthdays
To allow users to add birthdays to your database, birthdays.db
, you’ll first need to provide them a form via which to input the data. To create a form, you can use an HTML form
element.
HTML forms are composed of input
elements, where each input
allows the user to enter some piece of information. Recall that birthdays.db
is a SQLite database with one table, birthdays
, and that this table has four columns: id
, name
, month
, and day
. No need for the user to give the birthday an id themselves (SQLite will take care of such!), but you should have the user input a name, a month, and a day for each birthday. Try adding the following form:
<form>
<input name="name" placeholder="Name" type="text">
<input name="month" placeholder="Month" type="number" min="1" max="12">
<input name="day" placeholder="Day" type="number" min="1" max="31">
</form>
Notice that—per its documentation—input
elements have many possible attributes, among them name
, placeholder
, and type
. Via the name
attribute can you set a name to refer to a particular input
in a form: the name, month, or day input, for instance. The placeholder
attribute adds text to the input box while it doesn’t (yet) have a value, and the type
attribute specifies the type of input (number
, text
, date
, password
etc.) the user can enter.
Forms, as you may recall, need to be submitted. It’s common practice to have an input
with a type of submit
. When that input is clicked, the form will be submitted along with the data the user has entered in the form’s input
elements.
<form>
<input name="name" placeholder="Name" type="text">
<input name="month" placeholder="Month" type="number" min="1" max="12">
<input name="day" placeholder="Day" type="number" min="1" max="31">
<input type="submit" value="Add Birthday">
</form>
Forms commonly need some route to which to send their data. Try using the action
and method
attributes of a form
element, which—upon form submission—specify to the browser which route it should request and which request method to use. The action
attribute specifies the route, while the method
attribute specifies the request method.
<form action="/" method="post">
<input name="name" placeholder="Name" type="text">
<input name="month" placeholder="Month" type="number" min="1" max="12">
<input name="day" placeholder="Day" type="number" min="1" max="31">
<input type="submit" value="Add Birthday">
</form>
Upon submission, this form makes an HTTP POST
request to the /
route. It includes the data the user has added to its input
elements in the request body.
Use Flask and SQL to add birthdays to birthdays.db
on POST
requests to the /
route
You’ve added a form
element that submits to the /
route via the POST
request method. Now, you can use Flask to write Python code that will control what happens when your application receives a POST
request to the /
route.
You’ll want to break this problem down into smaller problems:
- Access the form data submitted.
- Add that form data to your database,
birthdays.db
. - Redirect the user back to the
/
route (this time with aGET
request), so they can see the list of all birthdays. The distribution code implements this step for you.
Write some pseudocode to remind yourself to do the above.
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
# Access form data
# Add entry to database
return redirect("/")
else:
# TODO: Display the entries in the database on index.html
return render_template("index.html")
First, try accessing the form data submitted via POST
. Recall that Flask gives you a function, request.form.get
, which can do just that. For any form submitted via POST
, it can take the name
of a form
element’s input
and return that input’s value. For instance, request.form.get("day")
will return the value of the input
whose name
attribute is equal to “day.”
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
# Access form data
name = request.form.get("name")
month = request.form.get("month")
day = request.form.get("day")
# Add entry to database
return redirect("/")
else:
# TODO: Display the entries in the database on index.html
return render_template("index.html")
To debug, you might try using print
to print the values of the form’s inputs. You should see them in your terminal each time you submit the form.
Next, you’ll want to add these form values to your database, birthdays.db
. Notice that the distribution code already establishes a connection to the database using db = SQL("sqlite:///birthdays.db")
. From here forward, then, you can use db.execute
to run any SQL query on birthdays.db
. Per its documentation, the execute
method can use ?
in a query as a placeholder, the values of which can provided as additional arguments.
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
# Access form data
name = request.form.get("name")
month = request.form.get("month")
day = request.form.get("day")
# Add entry to database
db.execute("INSERT INTO birthdays (name, month, day) VALUES(?, ?, ?)", name, month, day)
return redirect("/")
else:
# TODO: Display the entries in the database on index.html
return render_template("index.html")
Use Flask, SQL, and Jinja to display birthdays on GET
requests to the /
route
Now that the user can add entries to your database, you’ll want to ensure they can view those entries on the web page itself. Try breaking this problem down into smaller problems:
- Query for all birthdays
- Render the birthdays in
index.html
Write some pseudocode to remind yourself to do the above.
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
# Access form data
name = request.form.get("name")
month = request.form.get("month")
day = request.form.get("day")
# Add entry to database
db.execute("INSERT INTO birthdays (name, month, day) VALUES(?, ?, ?)", name, month, day)
return redirect("/")
else:
# Query for all birthdays
# Render birthdays in index.html
return render_template("index.html")
First, consider how to query for all birthdays in birthdays.db
. Recall that you can use db.execute
to execute any SQL query on birthdays.db
. It’s quite the hint, but the below query should return all rows and columns in birthdays.db
, storing the result in a variable called birthdays
. Per its documentation, the execute
method returns a list of dictionaries. Each dictionary represents one row in the result set, whose values you can access using column names as keys.
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
# Access form data
name = request.form.get("name")
month = request.form.get("month")
day = request.form.get("day")
# Add entry to database
db.execute("INSERT INTO birthdays (name, month, day) VALUES(?, ?, ?)", name, month, day)
return redirect("/")
else:
# Query for all birthdays
birthdays = db.execute("SELECT * FROM birthdays")
# Render birthdays in index.html
return render_template("index.html")
Next, consider how to render this list of birthdays in index.html
. Recall that the render_template
function can take additional keyword arguments of variables you’d like to render in the resulting HTML. For example, the below will take the birthdays
variable and allow you to render it in your HTML under the name birthdays
.
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
# Access form data
name = request.form.get("name")
month = request.form.get("month")
day = request.form.get("day")
# Add entry to database
db.execute("INSERT INTO birthdays (name, month, day) VALUES(?, ?, ?)", name, month, day)
return redirect("/")
else:
# Query for all birthdays
birthdays = db.execute("SELECT * FROM birthdays")
# Render birthdays in index.html
return render_template("index.html", birthdays=birthdays)
Finally, you’ll need to render the birthdays in index.html
. Recall that the tr
tag can be used to create a table row and the td
tag can be used to create a table data cell. And recall that, with Jinja, you can create a for
loop inside your index.html
file.
<tbody>
{% for birthday in birthdays %}
<tr>
<td>{{ birthday.name }}</td>
<td>{{ birthday.month }}/{{ birthday.day }}</td>
</tr>
{% endfor %}
</tbody>
Walkthrough
This video was recorded when the course was using another environment for writing code. Though the interface may look different from your codespace, the behavior of the two environments should be largely similar!
Not sure how to solve?
How to Test
No check50
for this problem! But be sure to test your web application by adding some birthdays and ensuring that the data appears in your table as expected.
How to Submit
Per Step 4 below, after you submit, be sure to check your autograder results. If you see SUBMISSION ERROR: missing files (0.0/1.0)
, it means your file was not named exactly as prescribed (or you uploaded it to the wrong problem).
Correctness in submissions entails everything from reading the specification, writing code that is compliant with it, and submitting files with the correct name. If you see this error, you should resubmit right away, making sure your submission is fully compliant with the specification. The staff will not adjust your filenames for you after the fact!
- While in your birthdays directory, create a ZIP file of your Flask application by executing:
zip -r birthdays.zip *
- Download your
birthdays.zip
file by control-clicking or right-clicking on the file in your codespace’s file browser and choosing Download. - Go to CS50’s Gradescope page.
- Click Problem Set 9: Birthdays.
- Drag and drop your
birthdays.zip
file to the area that says Drag & Drop. Be sure it has that exact filename! If you upload a file with a different name, the autograder likely will fail when trying to run it, and ensuring you have uploaded files with the correct filename is your responsibility! - Click Upload.
You should see a message that says “Problem Set 9: Birthdays” submitted successfully!”