Birthdays
Create a web application to keep track of friendsâ birthdays.
Getting Started
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/2022/fall/labs/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!
Walkthrough
This video was recorded when the course was still using CS50 IDE for writing code. Though the interface may look different from your codespace, the behavior of the two environments should be largely similar!
Hints
- Recall that you can call
db.execute
to execute SQL queries withinapp.py
.- If you call
db.execute
to run aSELECT
query, recall that the function will return to you a list of dictionaries, where each dictionary represents one row returned by your query.
- If you call
- Youâll likely find it helpful to pass in additional data to
render_template()
in yourindex
function so that access birthday data inside of yourindex.html
template. - Recall that the
tr
tag can be used to create a table row and thetd
tag can be used to create a table data cell. - Recall that, with Jinja, you can create a
for
loop inside yourindex.html
file. - In
app.py
, you can obtain the dataPOST
ed by the userâs form submission viarequest.form.get(field)
wherefield
is a string representing thename
attribute of aninput
from your form.- For example, if in
index.html
, you had an<input name="foo" type="text">
, you could userequest.form.get("foo")
inapp.py
to extract the userâs input.
- For example, if in
Not sure how to solve?
Testing
No check50
for this lab! But be sure to test your web application by adding some birthdays and ensuring that the data appears in your table as expected.
Run flask run
in your terminal while in your birthdays
directory to start a web server that serves your Flask application.
How to Submit
- 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 âLab 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 âLab 9: Birthdaysâ submitted successfully!â
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!
Want to see the staffâs solution?
app.py
import os
from cs50 import SQL
from flask import Flask, flash, jsonify, redirect, render_template, request, session
app = Flask(__name__)
db = SQL("sqlite:///birthdays.db")
@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")
# Insert data into database
db.execute("INSERT INTO birthdays (name, month, day) VALUES(?, ?, ?)", name, month, day)
# Go back to homepage
return redirect("/")
else:
# Query for all birthdays
birthdays = db.execute("SELECT * FROM birthdays")
# Render birthdays page
return render_template("index.html", birthdays=birthdays)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@500&display=swap" rel="stylesheet">
<link href="/static/styles.css" rel="stylesheet">
<title>Birthdays</title>
</head>
<body>
<div class="jumbotron">
<h1>Birthdays</h1>
</div>
<div class="container">
<div class="section">
<h2>Add a birthday.</h2>
<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>
</div>
<div class="section">
<table>
<thead>
<tr>
<th>Name</th>
<th>Birthday</th>
</tr>
</thead>
<tbody>
{% for birthday in birthdays %}
<tr>
<td>{{ birthday.name }}</td>
<td>{{ birthday.month }}/{{ birthday.day }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>
styles.css
body {
background-color: #fff;
color: #212529;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
margin: 0;
text-align: left;
}
.container {
margin-left: auto;
margin-right: auto;
padding-left: 15px;
padding-right: 15px;
text-align: center;
width: 90%;
}
.jumbotron {
background-color: #477bff;
color: #fff;
margin-bottom: 2rem;
padding: 2rem 1rem;
text-align: center;
}
.section {
padding-bottom: 1rem;
padding-left: 2rem;
padding-right: 2rem;
padding-top: 0.5rem;
}
.section:hover {
background-color: #f5f5f5;
transition: color 2s ease-in-out, background-color 0.15s ease-in-out;
}
h1 {
font-family: 'Montserrat', sans-serif;
font-size: 48px;
}
button, input[type="submit"] {
background-color: #d9edff;
border: 1px solid transparent;
border-radius: 0.25rem;
font-size: 0.95rem;
font-weight: 400;
line-height: 1.5;
padding: 0.375rem 0.75rem;
text-align: center;
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
vertical-align: middle;
}
input[type="text"], input[type="number"] {
line-height: 1.8;
width: 25%;
}
input[type="text"]:hover, input[type="number"]:hover {
background-color: #f5f5f5;
transition: color 2s ease-in-out, background-color 0.15s ease-in-out;
}
table {
background-color: transparent;
margin-bottom: 1rem;
width: 100%;
}
table th,
table td {
padding: 0.75rem;
vertical-align: middle;
}
tbody tr:nth-of-type(odd) {
background-color: rgb(179, 208, 255, 0.3)
}