跳转至

CS50 Week 9 Flask

在 Python 中使用 Flask 搭建后端。

Flask

Flask 是一个 Python 库,它也是一个框架。

在 HTML 中动态地显示文本:

Python
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)
HTML
<!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

HTML
<!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中。

空表单

如果允许用户提交空表单,那么可以设置默认值。

Python
@app.route("/greet")
def greet():
    name = request.args.get("name", "world")  # 设置默认值。
    return render_template("greet.html", name=name)

不允许提交空表单

可以在表单中加入属性required。但这样做并不安全,因为用户可以在浏览器中修改 HTML 代码,绕过required的限制。

样式

可以写一个样式模板,让其他界面继承这个模板的样式。

layout.html

HTML
<!DOCTYPE html>

<html lang="en">
  <head>
    <title>hello</title>
  </head>
  <body>
    {% block body %}{% endblock %}
  </body>
</html>

index.html

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

Model with arrow to View labeled Updates; View with arrow to User labeled Sees; User with arrow to Controlled labeled Uses; Controller with arrow to Model labeled Manipulates

用循环减少编写重复的 HTML 代码

Python
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中,用循环展示重复的元素:

HTML
...
<select name="sport">
  <option disabled selected value="">Sport</option>
  {% for sport in sports %}
  <option value="{{ sport }}">{{ sport }}</option>
  {% endfor %}
</select>
...

储存数据

可以将用户提交的数据储存到 Python 字典中。

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 模块,可以实现发送邮件的功能。

Python
# 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 是浏览器中的一小块数据(随机的数字),它用于跟踪用户的访问。有了 Cookie,浏览器就能告诉服务器:我已经访问过了,已经输如果用户名和密码了,我的访问被记录在这个唯一的 Cookie 中。然后服务器就能为这个客户提供特定的数据了。

搜索并动态展示数据

主要技术:

  • 和 SQLite 连接,实现数据库的查询功能。
  • JavaScript 动态请求数据并展示。
Python
# 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)
HTML
<!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("<", "&lt;")
            .replace("&", "&amp;");
          html += "<li>" + title + "</li>";
        }
        document.querySelector("ul").innerHTML = html;
      });
    </script>
  </body>
</html>

评论