生日

screenshot of 生日 website

要解决的问题

创建一个 Web 应用程序来记录朋友们的生日。

入门

下载分发代码

打开 cs50.dev.

首先点击你的终端窗口内部, 然后执行 cd 本身. 你会发现其”提示符”类似于下面所示。

$

点击该终端窗口内部,然后执行

wget https://cdn.cs50.net/2026/x/psets/9/birthdays.zip

然后按 Enter 键,从而将一个名为 birthdays.zip 的 ZIP 文件下载到你的 codespace 中。请注意不要忽略 wget 和后续 URL 之间的空格,以及任何其他字符!

现在执行

unzip birthdays.zip

来创建一个名为 birthdays 的文件夹。你不再需要这个 ZIP 文件了,所以可以执行

rm birthdays.zip

and 在提示符下回复 “y” 然后按 Enter 键来删除你下载的 ZIP 文件。

现在输入

cd birthdays

然后按 Enter 键进入(即打开)该目录。你的提示符现在应该类似于下面这样。

birthdays/ $

如果一切顺利,你应该执行

ls

你应该会看到以下文件和文件夹:

app.py  birthdays.db  static/  templates/

如果你遇到任何问题,请再次按照这些相同的步骤操作,看看能否确定哪里出错了!

理解代码

app.py 中,你会找到一个 Flask Web 应用程序的起点。该应用程序有一个路由 (/),接受 POST 请求(在 if 之后)和 GET 请求(在 else 之后)。目前,当 / 路由通过 GET 请求时,会渲染 index.html 模板。当 / 路由通过 POST 请求时,用户会被重定向回到 /(通过 GET)。

birthdays.db 是一个 SQLite 数据库,其中有一个表 birthdays,包含四列:idnamemonthday。该表中已有一些行,不过最终你的 Web 应用程序将支持向该表中插入行的功能!

static 目录中有一个 styles.css 文件,其中包含此 Web 应用程序的 CSS 代码。不需要编辑此文件,但如果你想编辑也是可以的!

templates 目录中有一个 index.html 文件,当用户查看你的 Web 应用程序时会被渲染。

实现细节

完成 Web 应用程序的实现,让用户可以存储和记录生日。

  • / 路由通过 GET 请求时,你的 Web 应用程序应在一个表格中显示数据库中所有人的姓名及其生日。
    • 首先,在 app.py 中,在你的 GET 请求处理中添加逻辑,查询 birthdays.db 数据库中的所有生日。将所有这些数据传递给你的 index.html 模板。
    • 然后,在 index.html 中,添加逻辑将每个生日呈现为表格中的一行。每行应有两列:一列是姓名,另一列是生日日期。
  • / 路由通过 POST 请求时,你的 Web 应用程序应向数据库添加一个新的生日,然后重新渲染首页。
    • 首先,在 index.html 中添加一个 HTML 表单。该表单应允许用户输入姓名、生日月份和生日日期。确保表单的提交目标为 /(即其 “action”),方法为 post
    • 然后,在 app.py 中,在你的 POST 请求处理中添加逻辑,根据用户提供的数据将新行 INSERTbirthdays 表中。

可选地,你也可以:

  • 添加删除和/或编辑生日条目的功能。
  • 添加你选择的任何额外功能!

提示

创建一个表单供用户提交生日

index.html 中,注意以下 TODO:

<!-- TODO: Create a form for users to 提交一个 name, a month, and a day -->

回想一下,要创建一个表单,你可以使用 form HTML 元素。你可以用以下开始和结束标签创建一个 form HTML 元素:

<form>
</form>

当然,表单还需要输入字段(以及一个按钮,用户可以通过它提交表单!)。回想一下,HTML input 元素可以在表单内创建输入框以及其他内容。你可以指定它们的 type 属性,使其接受 textnumber。也给 input 元素一个 name 属性,这样你就可以区分它们。

<form>
    <input name="name" type="text">
    <input name="month" type="number">
    <input name="day" type="number">
</form>

你的表单可能会受益于一个按钮,用户点击后可以提交他们的数据。添加一个类型为 submitinput 元素,这将允许用户做到这一点。如果你希望按钮本身带有说明文字,可以尝试设置 value 属性。

<form>
    <input name="name" type="text">
    <input name="month" type="number">
    <input name="day" type="number">
    <input type="submit" value="Add Birthday">
</form>

用户的数据将被提交到哪里?目前,无处可去!回想一下,你可以指定表单的 action 属性来决定表单提交后应请求哪个路由。表单数据将随结果请求一起提交。method 属性指定了提交表单时使用的 HTTP 请求方法。

<form action="/" method="post">
    <input name="name" type="text">
    <input name="month" type="number">
    <input name="day" type="number">
    <input type="submit" value="Add Birthday">
</form>

这样,你的表单就应该完全可用了,不过还可以进一步改进!考虑添加 placeholder 值来让界面更加美观:

<form action="/" method="post">
    <input name="name" placeholder="Name" type="text">
    <input name="month" placeholder="Month" type="number">
    <input name="day" placeholder="Day" type="number">
    <input type="submit" value="Add Birthday">
</form>

还可以考虑添加一些客户端验证,以确保用户配合你的表单意图。例如,类型为 numberinput 字段还可以指定 minmax 属性,用于确定用户可以输入的最小和最大值。

<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>
将用户表单提交添加到数据库

app.py 中,注意以下 TODO:

# TODO: Add the user's entry into the database

回想一下,Flask 有一些便捷的方法来访问通过 POST 提交的表单数据!特别是:

# Access form data
request.form.get(NAME)

其中 NAME 指的是具有已提交数据的特定 input 元素的 name 属性。如果你的 input 元素分别命名为 namemonthday,你可以通过以下方式分别访问(并存储!)它们的值:

# Access form data
name = request.form.get("name")
month = request.form.get("month")
day = request.form.get("day")

现在,用户在 namemonthday 输入元素中提交的值可作为 Python 变量供你使用。

下一步是将这些值添加到你的数据库中!得益于这一行代码

db = SQL("sqlite:///birthdays.db")

app.py 已经建立了与 birthdays.db 的连接,名称为 db。现在可以通过调用 db.execute 并传入一个有效的 SQL 查询来执行 SQL 查询。如果你想添加 Carter 的生日为 1 月 1 日,你可以运行以下 SQL 语句:

INSERT INTO birthdays (name, month, day) VALUES('Carter', 1, 1);

配置 app.py 来运行相同的查询,但使用占位符来表示要插入的值,如下所示:

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

这样就完成了!尝试提交表单,打开 birthdays.db,然后使用 SELECT 查询来查看 birthdays 表的内容。你应该会看到提交的表单数据可供你使用。

随着你创建更高级的应用程序,你还需要添加服务器端验证:即一种在做其他任何事之前检查用户数据是否有效的方法!你可能做的第一个验证是用户是否提交了任何数据!如果你尝试在用户没有提交任何数据的情况下使用 request.form.get 获取表单数据,request.form.get 将返回一个空字符串。你可以在 Python 中如下检查此值:

# Access form data
name = request.form.get("name")
if not name:
    return redirect("/")

month = request.form.get("month")
if not month:
    return redirect("/")

day = request.form.get("day")
if not day:
    return redirect("/")

# Insert data into database
db.execute("INSERT INTO birthdays (name, month, day) VALUES(?, ?, ?)", name, month, day)

现在,在确认用户已提供你所需的所有数据之前,你不会插入行。

还有一些事情仍然可能出错!如果用户实际上没有为 monthday 提供数值怎么办?一种检查方法是尝试int 将该值转换为整数,如果转换失败,则将用户重定向回首页。

# Access form data
name = request.form.get("name")
if not name:
    return redirect("/")

month = request.form.get("month")
if not month:
    return redirect("/")
try:
    month = int(month)
except ValueError:
    return redirect("/")

day = request.form.get("day")
if not day:
    return redirect("/")
try:
    day = int(day)
except ValueError:
    return redirect("/")

# Insert data into database
db.execute("INSERT INTO birthdays (name, month, day) VALUES(?, ?, ?)", name, month, day)

即便用户输入了一个数字,最好也检查它是否在正确的范围内!

# Access form data
name = request.form.get("name")
if not name:
    return redirect("/")

month = request.form.get("month")
if not month:
    return redirect("/")
try:
    month = int(month)
except ValueError:
    return redirect("/")
if month < 1 or month > 12:
    return redirect("/")

day = request.form.get("day")
if not day:
    return redirect("/")
try:
    day = int(day)
except ValueError:
    return redirect("/")
if day < 1 or day > 31:
    return redirect("/")

# Insert data into database
db.execute("INSERT INTO birthdays (name, month, day) VALUES(?, ?, ?)", name, month, day)
birthdays.db 中渲染生日

一旦用户可以提交生日并将其存储在 birthdays.db 中,你的下一个任务就是确保这些生日在 index.html 中渲染出来。

首先,你需要从 birthdays.db 中检索所有生日。你可以用以下 SQL 查询来实现:

SELECT * FROM birthdays;

See the following TODO in app.py:

# TODO: Display the entries in the database on index.html

考虑配置 app.py,使得每次页面以 GET 请求加载时运行此 SQL 查询:

# Query for all birthdays
birthdays = db.execute("SELECT * FROM birthdays")

现在,birthdays.dbbirthdays 表里的所有生日都可以通过一个名为 birthdays 的 Python 变量供你使用。特别是,SQL 查询的结果存储为一个字典列表。每个字典代表查询返回的一行,字典中的每个键对应于 birthdays 表的一个列名(即 “name”、”month” 和 “day”)。

要在 index.html 中渲染这些生日,你可以借助 Flask 的 render_template 函数。你可以通过指定一个关键字参数(也叫 birthdays),并将其设置为你刚刚创建的 birthdays 变量,来指定使用 birthdays 变量来渲染 index.html

# Query for all birthdays
birthdays = db.execute("SELECT * FROM birthdays")

# Render birthdays page
return render_template("index.html", birthdays=birthdays)

需要明确的是,= 左侧的名称 birthdays 是你可以在 index.html 中用来访问生日数据的名称。

现在 index.html 正在渲染并可以访问生日数据,你可以使用 Jinja 来正确渲染数据。Jinja 和 Python 一样,可以遍历列表的元素。Jinja 和 Python 一样,可以通过键访问字典的元素。在这种情况下,实现此操作的 Jinja 语法是字典的名称,后面跟一个 .,然后是你要访问的键的名称。

{% for birthday in birthdays %}
    <tr>
        <td>{{ birthday.name }}</td>
        <td>{{ birthday.month }}/{{ birthday.day }}</td>
    </tr>
{% endfor %}

就这样!尝试重新加载页面以查看渲染的生日。

讲解视频

不确定如何解决?

测试

此习题集没有 check50!但请务必通过添加一些生日并确保数据按预期显示在表格中来测试你的 Web 应用程序。

在你的 birthdays 目录下的终端中运行 flask run 来启动一个为你的 Flask 应用程序提供服务的 Web 服务器。

如何提交

在终端中执行以下命令提交你的作业,并按提示完成操作。

submit50 cs50/problems/2026/x/birthdays