Lab 9: Birthdays

Create a web application to keep track of friends’ birthdays.

screenshot of birthdays website

Accepting this Lab

  1. Accept this lab via GitHub Classroom.
  2. After about a minute, refresh the page and ensure you see “You’re ready to go!”.

Getting Started

Log into code.cs50.io, click on your terminal window, and execute cd by itself. You should find that your terminal window’s prompt resembles the below:

$

Next, execute

update50

After your codespace reloads, use

get50 birthdays

in order to download a directory called birthdays into your codespace.

Then execute

cd birthdays

in order to change into that directory. Your prompt should now resemble the below:

birthdays/ $

Execute ls by itself, 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 via GET, 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 your GET request handling to query the birthdays.db database for all birthdays. Pass all of that data to your index.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.
  • When the / route is requested via POST, 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 of post.
    • Then, in app.py, add logic in your POST request handling to INSERT a new row into the birthdays table based on the data supplied by the user.

Optionally, you may also:

  • Add the ability to delete and/or edit birthday entries.
  • Add any additional features of your choosing!

Hints

  • Recall that you can call db.execute to execute SQL queries within app.py.
    • If you call db.execute to run a SELECT query, recall that the function will return to you a list of dictionaries, where each dictionary represents one row returned by your query.
  • You’ll likely find it helpful to pass in additional data to render_template() in your index function so that access birthday data inside of your index.html template.
  • 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.
  • Recall that, with Jinja, you can create a for loop inside your index.html file.
  • In app.py, you can obtain the data POSTed by the user’s form submission via request.form.get(field) where field is a string representing the name attribute of an input from your form.
    • For example, if in index.html, you had an <input name="foo" type="text">, you could use request.form.get("foo") in app.py to extract the user’s input.

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

In your terminal, execute the below to submit your work.

submit50 birthdays

To confirm your submission, go to github.com/classroom50/birthdays-USERNAME where USERNAME is your GitHub username. You should see the code from your latest submission.

Labs are assessed only on whether you’ve submitted an honest attempt.

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)
}