| 关于   英文版   «  10.1. 第 8 周:HTML、CSS、JavaScript   ::   目录   ::   12.1. 第 10 周:结束  »

11.1. 第 9 周:Flask

11.1.1. 欢迎!

  • 在前几周中,你已经学习了许多编程语言、技术和策略。

  • 确实,这门课与其说是一门 C 语言课 或 Python 课 ,不如说是一门 编程课 ,以便你能跟上未来的趋势。

  • 在过去的这几周里,你已经学会了 如何学习 编程。

  • 今天,我们将从 HTML 和 CSS 过渡到结合 HTML、CSS、SQL、Python 和 JavaScript,从而让你能够创建自己的 Web 应用程序。

  • 你可以考虑利用本周所学的技能来完成你的期末项目。

11.1.2. http-服务器

  • 到目前为止,你所见的所有 HTML 都是预先编写且静态的。

  • 过去,当你访问一个页面时,浏览器会下载一个 HTML 页面,你便能查看它。这些被称为 静态 页面,因为 HTML 中编程的内容正是用户在互联网浏览器 客户端 看到并下载的内容。

  • 动态页面指的是 Python 及类似语言能够即时创建 HTML 的能力。因此,你可以拥有由代码根据用户的输入或行为在 服务器端 生成的网页。

  • 你过去曾使用 http-server 来提供你的网页。今天,我们将利用一种新的服务器,它可以解析网址并根据提供的 URL 执行操作。

  • 此外,上周你看到的 URL 如下:

    https://www.example.com/folder/file.html
    

    注意 file.html 是位于 example.com 处名为 folder 的文件夹中的一个 HTML 文件。

11.1.3. Flask

  • 本周,我们将介绍与 路由 (如 https://www.example.com/route?key=value )交互的能力,其中可以通过 URL 中提供的关键码和值在服务器上生成特定功能。

  • Flask 是一个第三方库,允许你在 Python 中使用 Flask 框架或微框架来托管 Web 应用程序。

  • 你可以通过在 cs50.dev 的终端窗口中执行 flask run 来运行 Flask。

  • 为此,你需要一个名为 app.py 的文件和另一个名为 requirements.txt 的文件。 app.py 包含告诉 Flask 如何运行你的 Web 应用程序的代码。 requirements.txt 包含你的 Flask 应用程序运行所需的库列表。

  • 以下是 requirements.txt 的示例:

    Flask
    

    注意只有 Flask 出现在此文件中。这是因为运行 Flask 应用程序需要 Flask。

  • src9/hello0/app.py

    以下是 app.py 中一个非常简单的 Flask 应用程序:

    # Says hello to world by returning a string of text
    
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        return "hello, world"
    

    注意, / 路由仅返回文本 hello, world 。

  • src9/hello1/app.py

    我们还可以创建实现 HTML 的代码:

    # Says hello to world by returning a string of HTML
    
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        return '<!DOCTYPE html><html lang="en"><head><title>hello</title></head><body>hello, world</body></html>'
    

    请注意,这里返回的不是纯文本,而是 HTML。

  • src9/hello2/templates/index.html

    改进我们的应用时,还可以通过创建一个名为 templates 的文件夹,并在该文件夹中创建一个名为 index.html 的文件(包含以下代码),来基于模板提供 HTML:

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <title>hello</title>
        </head>
    
        <body>
            hello, world
        </body>
    
    </html>
    
  • src9/hello5/app.py

    然后,在 templates 文件夹所在的同一文件夹中,创建一个名为 app.py 的文件,并添加以下代码:

    # Uses request.args.get
    
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        name = request.args.get("name", "world")
        return render_template("index.html", name=name)
    

    请注意,此代码将 app 定义为 Flask 应用程序。接着,它将 app 的 / 路由定义为返回 index.html 的内容,并传入 name 作为参数。默认情况下, request.args.get 函数会查找用户提供的 name 。若未提供名称,则默认为 world 。 @app.route 也被称为装饰器。

  • 你可以在终端窗口中输入 flask run 来运行此 Web 应用程序。如果 Flask 无法运行,请确保上述每个文件中的语法正确。此外,如果 Flask 仍无法运行,请确保你的文件组织如下:

    /templates
        index.html
    app.py
    requirements.txt
    
  • 一旦运行成功,系统会提示你点击一个链接。当你导航到该网页后,尝试在浏览器的地址栏中向基础 URL 添加 ?name=[Your Name] 。

11.1.4. 表单

  • src9/hello6/templates/index.html

    改进我们的程序,我们知道大多数用户不会在地址栏中输入参数。相反,程序员依赖用户在网页上填写表单。因此,我们可以按如下方式修改 index.html :

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <title>hello</title>
        </head>
    
        <body>
            <form action="/greet" method="get">
                <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
                <button type="submit">Greet</button>
            </form>
        </body>
    
    </html>
    

    请注意,现在创建了一个表单,用于获取用户的姓名,然后将其传递给名为 /greet 的路由。 autocomplete 已关闭。此外,还包含一个文本为 name 的 placeholder 。

  • src9/hello6/app.py

    此外,我们可以按如下方式更改 app.py :

    # Adds a form, second route
    
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        return render_template("index.html")
    
    @app.route("/greet")
    def greet():
        return render_template("greet.html", name=request.args.get("name", "world"))
    

    注意,默认路径将显示一个表单供用户输入其姓名。 /greet 路由会将 name 传递给该网页。

  • src9/hello6/templates/greet.html

    要完成此实现,你需要在 templates 文件夹中为 greet.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>
    

    请注意,此路由现在会向用户呈现问候语,后跟其姓名。

11.1.5. 模板

  • 我们的两个网页 index.html 和 greet.html 拥有大量相同的数据。如果能让主体部分独一无二,同时在页面之间复用相同的布局,那该多好?

  • src9/hello7/templates/layout.html

    首先,创建一个名为 layout.html 的新模板,并编写如下代码:

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

    请注意, {% block body %}{% endblock %} 允许从其他 HTML 文件中插入其他代码。

  • src9/hello7/templates/index.html

    然后,按如下方式修改你的 index.html :

    {% extends "layout.html" %}
    
    {% block body %}
    
        <form action="/greet" method="get">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            <button type="submit">Greet</button>
        </form>
    
    {% endblock %}
    

    请注意,行 {% extends "layout.html" %} 告诉服务器从哪里获取此页面的布局。然后, {% block body %}{% endblock %} 告诉要将什么代码插入到 layout.html 中。

  • src9/hello7/templates/greet.html

    最后,按如下方式修改 greet.html :

    {% extends "layout.html" %}
    
    {% block body %}
        hello, {{ name }}
    {% endblock %}
    

    请注意,此代码更短且更紧凑。

11.1.6. 请求方法

  • 你可以想象某些场景下使用 get 并不安全,因为用户名和密码会出现在 URL 中。

  • src9/hello8/app.py

    我们可以利用 post 方法,通过如下修改 app.py 来帮助解决此问题:

    # Switches to POST
    
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        return render_template("index.html")
    
    @app.route("/greet", methods=["POST"])
    def greet():
        return render_template("greet.html", name=request.form.get("name", "world"))
    

    注意 POST 被添加到了 /greet 路由,并且我们使用 request.form.get 而非 request.args.get 。

  • 这指示服务器 更深 地查看虚拟信封,而不在 post 的 URL 中暴露项目。

  • src9/hello9/app.py

    尽管如此,通过为 get 和 post 使用单一路由,此代码仍可进一步优化。为此,请按以下方式修改 app.py :

    # Uses a single route
    
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/", methods=["GET", "POST"])
    def index():
        if request.method == "POST":
            return render_template("greet.html", name=request.form.get("name", "world"))
        return render_template("index.html")
    

    请注意, get 和 post 均在单次路由中完成。然而, request.method 用于根据用户请求的路由类型进行正确路由。

  • src9/hello9/templates/index.html

    因此,你可以按如下方式修改你的 index.html :

    {% extends "layout.html" %}
    
    {% block body %}
    
        <form action="/" method="post">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            <button type="submit">Greet</button>
        </form>
    
    {% endblock %}
    

    注意表单 action 已被修改。

  • src9/hello10/app.py

    尽管如此,这段代码仍存在一个缺陷。使用我们的新实现后,当有人在表单中不输入姓名时, Hello, 会在没有姓名的情况下显示。我们可以通过按如下方式编辑 app.py 来改进代码:

    # Moves default value to template
    
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/", methods=["GET", "POST"])
    def index():
        if request.method == "POST":
            return render_template("greet.html", name=request.form.get("name"))
        return render_template("index.html")
    

    注意 name=request.form.get("name")) 已被修改。

  • src9/hello10/templates/greet.html

    最后,按如下方式修改 greet.html :

    {% extends "layout.html" %}
    
    {% block body %}
    
        hello,
        {% if name %}
            {{ name }}
        {% else %}
            world
        {% endif %}
    
    {% endblock %}
    

    注意 hello, {{ name }} 是如何被修改的,以便在未识别到名称时提供默认输出。

  • 由于我们修改了许多文件,你可能希望将最终代码与 our final code 进行比较。

11.1.7. 新生校内体育

  • Frosh IMs 或 froshims 是一个允许学生注册校内体育活动的 Web 应用程序。

  • 关闭所有与 hello 相关的窗口,并在终端窗口中输入 mkdir froshims 以创建一个文件夹。然后,输入 cd froshims 以浏览到此文件夹。在其中,通过输入 mkdir templates 创建一个名为 templates 的目录。

  • 接下来,在 froshims 文件夹中,输入 code requirements.txt 并按如下方式编写代码:

    Flask
    

    与之前一样,运行 Flask 应用程序需要 Flask。

  • src9/froshims1/app.py

    最后,输入 code app.py 并按如下方式编写代码:

    # Implements a registration form using a select menu, validating sport server-side
    
    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)
    
    @app.route("/register", methods=["POST"])
    def register():
    
        # Validate submission
        if not request.form.get("name") or request.form.get("sport") not in SPORTS:
            return render_template("failure.html")
    
        # Confirm registration
        return render_template("success.html")
    

    请注意,系统提供了 failure 选项,若 name 或 sport 字段未正确填写,则会向用户显示失败消息。

  • src9/froshims1/templates/index.html

    接下来,在 templates 文件夹中创建一个名为 index.html 的文件,方法是输入 code templates/index.html ,然后编写如下代码:

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Register</h1>
        <form action="/register" method="post">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            <select name="sport">
                <option selected value="">Sport</option>
                {% for sport in sports %}
                    <option value="{{ sport }}">{{ sport }}</option>
                {% endfor %}
            </select>
            <button type="submit">Register</button>
        </form>
    {% endblock %}
    
  • src9/froshims1/templates/layout.html

    接下来,创建一个名为 layout.html 的文件,方法是输入 code templates/layout.html 并编写如下代码:

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>froshims</title>
        </head>
    
        <body>
            {% block body %}{% endblock %}
        </body>
    
    </html>
    
  • src9/froshims1/templates/success.html

    第四,在 templates 中创建一个名为 success.html 的文件,内容如下:

    {% extends "layout.html" %}
    
    {% block body %}
        You are registered!
    {% endblock %}
    
  • src9/froshims1/templates/failure.html

    最后,在 templates 中创建一个名为 failure.html 的文件,内容如下:

    {% extends "layout.html" %}
    
    {% block body %}
        You are not registered!
    {% endblock %}
    
  • 执行 flask run 并查看此阶段的应用程序。

  • src9/froshims2/templates/index.html

    你可以想象,我们可能希望使用单选按钮来查看各种注册选项。我们可以按如下方式改进 index.html :

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Register</h1>
        <form action="/register" method="post">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            {% for sport in sports %}
                <input name="sport" type="radio" value="{{ sport }}"> {{ sport }}
            {% endfor %}
            <button type="submit">Register</button>
        </form>
    {% endblock %}
    

    注意 type 如何被更改为 radio 。

  • 再次执行 flask run ,你可以看到界面现在已发生变化。

  • src9/froshims4/app.py

    你可以想象,我们可能希望接受许多不同注册者的注册。我们可以按如下方式改进 app.py :

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

    请注意,一个名为 REGISTRANTS 的字典用于记录由 REGISTRANTS[name] 选择的 sport 。此外,请注意 registrants=REGISTRANTS 将该字典传递给了此模板。

  • src9/froshims4/templates/error.html

    此外,我们可以实现 error.html :

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Error</h1>
        <p>{{ message }}</p>
        <img alt="Grumpy Cat" src="/static/cat.jpg">
    {% endblock %}
    
  • src9/froshims4/templates/registrants.html

    此外,创建一个名为 registrants.html 的新模板,如下所示:

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Registrants</h1>
        <table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Sport</th>
                </tr>
            </thead>
            <tbody>
                {% for name in registrants %}
                    <tr>
                        <td>{{ name }}</td>
                        <td>{{ registrants[name] }}</td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
    {% endblock %}
    

    请注意, {% for name in registrants %}...{% endfor %} 将遍历每位注册者。能够在动态网页上进行迭代非常强大!

  • 最后,在与 app.py 相同的文件夹中创建一个名为 static 的文件夹。在那里,上传一个 cat 的以下文件。

  • 执行 flask run 并试用该应用程序。

  • 你现在拥有了一个 Web 应用程序!然而,存在一些安全漏洞!由于所有内容都保存在内存中,攻击者可以修改 HTML 并 入侵 网站。此外,如果服务器关闭,这些数据将不会持久化。是否有一种方法可以让我们的数据即使在服务器重启后也能持久化?

11.1.8. Flask 和 SQL

  • 正如我们已经看到 Python 如何与 SQL 数据库交互,我们可以结合 Flask、Python 和 SQL 的力量来创建一个数据能够持久化的 Web 应用程序!

  • 要实现这一点,你需要采取若干步骤。

  • 首先,将以下 SQL database 下载到你的 froshims 文件夹中。

  • 在终端中执行 sqlite3 froshims.db 并输入 .schema 以查看数据库文件的内容。接着输入 SELECT * FROM registrants; 以了解具体内容。你会注意到文件中目前没有任何注册信息。

  • 接下来,按如下方式修改 requirements.txt :

    cs50
    Flask
    
  • src9/froshims7/templates/index.html

    按如下方式修改 index.html :

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Register</h1>
        <form action="/register" method="post">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            {% for sport in sports %}
                <input name="sport" type="checkbox" value="{{ sport }}"> {{ sport }}
            {% endfor %}
            <button type="submit">Register</button>
        </form>
    {% endblock %}
    
  • src9/froshims7/templates/layout.html

    按如下方式修改 layout.html :

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>froshims</title>
        </head>
    
        <body>
            {% block body %}{% endblock %}
        </body>
    
    </html>
    
  • src9/froshims7/templates/error.html

    确保 error.html 显示如下:

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Error</h1>
        <p>{{ message }}</p>
        <img alt="Grumpy Cat" src="/static/cat.jpg">
    {% endblock %}
    
  • src9/froshims7/templates/registrants.html

    修改 registrants.html 使其显示如下:

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Registrants</h1>
        <table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Sport</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                {% for registrant in registrants %}
                    <tr>
                        <td>{{ registrant.name }}</td>
                        <td>{{ registrant.sport }}</td>
                        <td>
                            <form action="/deregister" method="post">
                                <input name="id" type="hidden" value="{{ registrant.id }}">
                                <button type="submit">Deregister</button>
                            </form>
                        </td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
    {% endblock %}
    

    请注意,其中包含一个隐藏值 registrant.id ,以便稍后在 app.py 中使用此 id 。

  • src9/froshims7/app.py

    最后,按如下方式修改 app.py :

    # Implements a registration form, storing registrants in a SQLite database, with support for multiple sports and deregistration
    
    from cs50 import SQL
    from flask import Flask, redirect, render_template, request
    
    app = Flask(__name__)
    
    db = SQL("sqlite:///froshims.db")
    
    SPORTS = [
        "Basketball",
        "Soccer",
        "Ultimate Frisbee"
    ]
    
    @app.route("/")
    def index():
        return render_template("index.html", sports=SPORTS)
    
    @app.route("/deregister", methods=["POST"])
    def deregister():
    
        # Forget registrant
        id = request.form.get("id")
        if id:
            db.execute("DELETE FROM registrants WHERE id = ?", id)
        return redirect("/registrants")
    
    @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 sports
        sports = request.form.getlist("sport")
        if not sports:
            return render_template("error.html", message="Missing sport")
        for sport in sports:
            if sport not in SPORTS:
                return render_template("error.html", message="Invalid sport")
    
        # Remember registrant
        for sport in sports:
            db.execute("INSERT INTO registrants (name, sport) VALUES(?, ?)", name, sport)
    
        # Confirm registration
        return redirect("/registrants")
    
    @app.route("/registrants")
    def registrants():
        registrants = db.execute("SELECT * FROM registrants")
        return render_template("registrants.html", registrants=registrants)
    

    请注意, cs50 库已被使用。为 post 方法包含了一个用于 register 的路由。该路由将从注册表单中获取姓名和运动项目,并执行 SQL 查询以将 name 和 sport 添加到 registrants 表中。 deregister 路由指向一个 SQL 查询,该查询将获取用户的 id 并利用该信息注销此用户。

  • 你可以执行 flask run 并检查结果。

  • 如果你想下载我们对 froshims 的实现,可以在此处 here 进行。

  • 你可以在 Flask documentation 中阅读更多关于 Flask 的内容。

11.1.10. 购物车

  • 接下来是最后一个利用 Flask 启用会话能力的示例。

  • src9/store/app.py

    我们检查了 app.py 中 store 的以下代码。展示了以下代码:

    from cs50 import SQL
    from flask import Flask, redirect, render_template, request, session
    from flask_session import Session
    
    # Configure app
    app = Flask(__name__)
    
    # Connect to database
    db = SQL("sqlite:///store.db")
    
    # Configure session
    app.config["SESSION_PERMANENT"] = False
    app.config["SESSION_TYPE"] = "filesystem"
    Session(app)
    
    @app.route("/")
    def index():
        books = db.execute("SELECT * FROM books")
        return render_template("books.html", books=books)
    
    @app.route("/cart", methods=["GET", "POST"])
    def cart():
    
        # Ensure cart exists
        if "cart" not in session:
            session["cart"] = []
    
        # POST
        if request.method == "POST":
            book_id = request.form.get("id")
            if book_id:
                session["cart"].append(book_id)
            return redirect("/cart")
    
        # GET
        books = db.execute("SELECT * FROM books WHERE id IN (?)", session["cart"])
        return render_template("cart.html", books=books)
    

    注意 cart 是使用列表实现的。可以使用 books.html 中的 Add to Cart 按钮将商品添加到此列表中。点击此类按钮时,会调用 post 方法,其中商品的 id 会被追加到 cart 。查看购物车时,调用 get 方法会执行 SQL 以显示购物车中的书籍列表。

  • src9/store/templates/books.html

    我们也看到了 books.html 的内容:

    {% extends "layout.html" %}
    
    {% block body %}
    
        <h1>Books</h1>
        {% for book in books %}
            <h2>{{ book["title"] }}</h2>
            <form action="/cart" method="post">
                <input name="id" type="hidden" value="{{ book['id'] }}">
                <button type="submit">Add to Cart</button>
            </form>
        {% endfor %}
    
    {% endblock %}
    

    注意这如何使用 for book in books 为每本书创建 Add to Cart 的能力。

  • 你可以在 source code 中查看驱动此 flask 实现的其他文件。

11.1.11. 显示

  • src9/shows1/app.py

    我们查看了一个名为 shows 的预设计程序,位于 app.py :

    # Searches for shows using LIKE
    
    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)
    

    注意 search 路由提供了一种查找 show 的方法。此查找会检索标题 LIKE 用户提供的内容。

  • src9/shows1/templates/index.html

    我们还检查了 index.html :

    {% extends "layout.html" %}
    
    {% block body %}
    
        <form action="/search" method="get">
            <input autocomplete="off" autofocus name="q" placeholder="Query" type="search">
            <button type="submit">Search</button>
        </form>
    
    {% endblock %}
    
  • 你可以在 source code 中查看此实现的其他文件。

11.1.12. API

  • 应用程序接口或 API 是一系列规范,允许你与另一项服务进行交互。例如,我们可以利用 IMDB 的 API 与其数据库进行交互。我们甚至可以集成用于处理可从服务器下载的特定类型数据的 API。

  • src9/shows2/app.py

    在改进 shows 并考察 app.py 的改进时,我们看到了以下内容:

    # Searches for shows using Ajax
    
    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():
        q = request.args.get("q")
        if q:
            shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", "%" + q + "%")
        else:
            shows = []
        return render_template("search.html", shows=shows)
    

    注意, search 路由会执行一条 SQL 查询。

  • src9/shows2/templates/查找.html

    查看 search.html ,你会注意到它非常简单:

    {% for show in shows %}
        <li>{{ show["title"] }}</li>
    {% endfor %}
    

    请注意,它提供了一个项目符号列表。

  • src9/shows2/templates/index.html

    最后,查看 index.html ,请注意 AJAX 代码被用于驱动查找功能:

    <!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="search">
    
            <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.text();
                    document.querySelector('ul').innerHTML = shows;
                });
            </script>
    
        </body>
    
    </html>
    

    请注意,这里使用了一个事件监听器来动态查询服务器,以提供与所给标题匹配的列表。这将定位 HTML 中的 ul 标签,并相应地修改网页以包含匹配项列表。

  • 你可以在 AJAX documentation 中阅读更多内容。

11.1.13. JSON

  • JavaScript 对象表示法 或 JSON 是包含关键码和值的字典文本文件。这是一种原始且对计算机友好的获取大量数据的方式。

  • JSON 是从服务器返回数据的一种非常实用的方式。

  • src9/shows3/templates/index.html

    你可以在我们共同研究的 index.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 i in shows) {
                        let title = shows[i].title.replace('<', '&lt;').replace('&', '&amp;');
                        html += '<li>' + title + '</li>';
                    }
                    document.querySelector('ul').innerHTML = html;
                });
            </script>
    
        </body>
    
    </html>
    

    尽管上述内容可能有些晦涩,但它为你自行研究 JSON 提供了一个起点,以便了解如何在你自己的 Web 应用程序中实现它。

  • src9/shows3/app.py

    此外,我们检查了 app.py 以了解如何获取 JSON 响应:

    # Searches for shows using Ajax with JSON
    
    from cs50 import SQL
    from flask import Flask, jsonify, 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():
        q = request.args.get("q")
        if q:
            shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", "%" + q + "%")
        else:
            shows = []
        return jsonify(shows)
    

    注意 jsonify 如何将结果转换为当代 Web 应用程序可接受的易读格式。

  • 你可以在 JSON documentation 中阅读更多内容。

  • 总之,你现在已能够使用 Python、Flask、HTML 和 SQL 完成自己的 Web 应用程序。

11.1.14. 总结

在本课中,你学习了如何利用 Python、SQL 和 Flask 创建 Web 应用程序。具体而言,我们讨论了…

  • Flask

  • 表单

  • 模板

  • 请求方法

  • Flask 与 SQL

  • Cookie 与会话

  • API

  • JSON

   «  10.1. 第 8 周:HTML、CSS、JavaScript   ::   目录   ::   12.1. 第 10 周:结束  »

关闭窗口