CS50 Week 9 Flask¶
在 Python 中使用 Flask 搭建后端。
Flask¶
Flask 是一个 Python 库,它也是一个框架。
在 HTML 中动态地显示文本:
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get("name")
return render_template("index.html", name=name)
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
<title>hello</title>
</head>
<body>
hello, {{ name }}
</body>
</html>
以上的代码,用flask run
生成 HTML 后,当我们的 URL 后面接着?name=David
时,屏幕会打印hello, David
。
表单 Form¶
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
<title>hello</title>
</head>
<body>
<form action="/greet" method="get">
<input
autocomplete="off"
autofocus
name="name"
placeholder="Name"
type="text"
/>
<input type="submit" />
</form>
</body>
</html>
上面的表单有一个action
,它意味着,当用户点击提交表单时,网页会转到/greet?name=name
中。
空表单¶
如果允许用户提交空表单,那么可以设置默认值。
@app.route("/greet")
def greet():
name = request.args.get("name", "world") # 设置默认值。
return render_template("greet.html", name=name)
不允许提交空表单¶
可以在表单中加入属性required
。但这样做并不安全,因为用户可以在浏览器中修改 HTML 代码,绕过required
的限制。
样式¶
可以写一个样式模板,让其他界面继承这个模板的样式。
layout.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>hello</title>
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
index.html
:
{% extends "layout.html" %} {% block body %}
<form action="/greet" method="post">
<input
autocomplete="off"
autofocus
name="name"
placeholder="Name"
type="text"
/>
<input type="submit" />
</form>
{% endblock %}
模板继承的语法是Jinja
的语法。Jinja
是一个 Python 的模板引擎。
用 Flask 运行,就能自动生成继承layout.html
模板后的index.html
,也就是能自动生成完整的 HTML 文件。
POST¶
POST 提交的表单内容不会显示在 URL 上,因此可以保护隐私。但浏览器的历史记录也不能记录表单内容,因此,下次再提交相同的内容时,用户需要再次手动输入,而不能靠浏览器提示。
GET¶
- GET 请求可被缓存
- GET 请求保留在浏览器历史记录中
- GET 请求可被收藏为书签
- GET 请求不应在处理敏感数据时使用
- GET 请求有长度限制
- GET 请求只应当用于取回数据
- 对应 request.args
POST¶
- POST 请求不会被缓存
- POST 请求不会保留在浏览器历史记录中
- POST 不能被收藏为书签
- POST 请求对数据长度没有要求
- 对应 request.form
MVC¶
用循环减少编写重复的 HTML 代码¶
from flask import Flask, render_template, request
app = Flask(__name__)
SPORTS = ["Basketball" "Soccer", "Ultimate Frisbee"]
@app.route("/")
def index():
return render_template("index.html", sports=SPORTS)
...
在index.html
中,用循环展示重复的元素:
...
<select name="sport">
<option disabled selected value="">Sport</option>
{% for sport in sports %}
<option value="{{ sport }}">{{ sport }}</option>
{% endfor %}
</select>
...
储存数据¶
可以将用户提交的数据储存到 Python 字典中。
# Implements a registration form, storing registrants in a dictionary, with error messages
from flask import Flask, redirect, render_template, request
app = Flask(__name__)
REGISTRANTS = {}
SPORTS = ["Basketball", "Soccer", "Ultimate Frisbee"]
@app.route("/")
def index():
return render_template("index.html", sports=SPORTS)
@app.route("/register", methods=["POST"])
def register():
# Validate name
name = request.form.get("name")
if not name:
return render_template("error.html", message="Missing name")
# Validate sport
sport = request.form.get("sport")
if not sport:
return render_template("error.html", message="Missing sport")
if sport not in SPORTS:
return render_template("error.html", message="Invalid sport")
# Remember registrant
REGISTRANTS[name] = sport
# Confirm registration
return redirect("/registrants")
@app.route("/registrants")
def registrants():
return render_template("registrants.html", registrants=REGISTRANTS)
这些数据暂时存在计算机或服务器的 RAM 中,所以数据并不稳定。当计算机或服务器重启时,这些数据都会丢失。
自动发送邮件¶
flask_mail 中有 Mail 和 Message 模块,可以实现发送邮件的功能。
# Implements a registration form, confirming registration via email
import os
import re
from flask import Flask, render_template, request
from flask_mail import Mail, Message
app = Flask(__name__)
# Requires that "Less secure app access" be on
# https://support.google.com/accounts/answer/6010255
app.config["MAIL_DEFAULT_SENDER"] = os.environ["MAIL_DEFAULT_SENDER"]
app.config["MAIL_PASSWORD"] = os.environ["MAIL_PASSWORD"]
app.config["MAIL_PORT"] = 587
app.config["MAIL_SERVER"] = "smtp.gmail.com"
app.config["MAIL_USE_TLS"] = True
app.config["MAIL_USERNAME"] = os.environ["MAIL_USERNAME"]
mail = Mail(app)
...
值得注意的是,这里的邮箱用户名和密码并没有在源代码中显示地展示出来,而是通过引入变量os.environ
来实现,这可以保护隐私数据。
Session 和 Cookie¶
服务器通过 Session 可以记录用户的状态。
Cookie 是浏览器中的一小块数据(随机的数字),它用于跟踪用户的访问。有了 Cookie,浏览器就能告诉服务器:我已经访问过了,已经输如果用户名和密码了,我的访问被记录在这个唯一的 Cookie 中。然后服务器就能为这个客户提供特定的数据了。
搜索并动态展示数据¶
主要技术:
- 和 SQLite 连接,实现数据库的查询功能。
- JavaScript 动态请求数据并展示。
# Searches for shows
from cs50 import SQL
from flask import Flask, render_template, request
app = Flask(__name__)
db = SQL("sqlite:///shows.db")
@app.route("/")
def index():
return render_template("index.html")
@app.route("/search")
def search():
shows = db.execute(
"SELECT * FROM shows WHERE title LIKE ?", "%" + request.args.get("q") + "%"
)
return render_template("search.html", shows=shows)
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
<title>shows</title>
</head>
<body>
<input autocomplete="off" autofocus placeholder="Query" type="text" />
<ul></ul>
<script>
let input = document.querySelector("input");
input.addEventListener("input", async function () {
let response = await fetch("/search?q=" + input.value);
let shows = await response.json();
let html = "";
for (let id in shows) {
let title = shows[id].title
.replace("<", "<")
.replace("&", "&");
html += "<li>" + title + "</li>";
}
document.querySelector("ul").innerHTML = html;
});
</script>
</body>
</html>