步骤 3:通过 Flask 应用提供静态文件、添加页面和使用模板继承

上一步:使用视图和页面模板创建 Flask 应用

在本教程的前几个步骤中,你已了解如何通过单页自包含 HTML 创建最小的 Flask 应用。 现代 Web 应用通常由许多页面组成,并使用 CSS、JavaScript 文件等共享资源来提供一致的样式和行为。

在此步骤中,你将了解如何:

  • 使用 Visual Studio 项模板快速添加具有方便样本代码的不同类型的新文件(步骤 3-1)
  • 通过代码提供静态文件(步骤 3-2,可选)
  • 向应用添加其他页(步骤 3-3)
  • 使用模板继承创建跨页使用的标头和导航栏(步骤 3-4)

步骤 3-1:熟悉项模板

开发 Flask 应用时,通常会添加多个 Python、HTML、CSS 和 JavaScript 文件。 对于每个文件类型(诸如 web.config 等其他需要部署的文件),Visual Studio 提供了方便的项模板来帮助入门。

若要查看可用模板,请转到“解决方案资源管理器”,右键单击要在其中创建项的文件夹,选择“添加”>“新项”:

Add new item dialog in Visual Studio

若要使用模板,选择所需的模板,为该文件指定一个名称,然后选择“确定”。 以这种方式添加项会自动将文件添加到 Visual Studio 项目中,并标记对源代码管理所做的更改。

问:Visual Studio 如何知道要提供哪些项模板?

答:Visual Studio 项目文件 (.pyproj) 包含一个项目类型标识符,它将其标记为 Python 项目。 Visual Studio 使用此类型标识符来仅显示那些适用于项目类型的项模板。 通过这种方式,Visual Studio 可以为许多项目类型提供一组丰富的项模板,而不必每次都要求你对它们进行排序。

步骤 3-2:从应用中提供静态文件

在使用 Python(借助任何框架)生成的 Web 应用中,Python 文件始终在 Web 主机的服务器上运行,并且永远不会传输到用户计算机。 其他文件(如 CSS 和 JavaScript)仅由浏览器使用,因此,主机服务器会在需要时按原样传递它们。 此类文件称为“静态”文件,可通过 Flask 自动交付,而无需编写任何代码。 例如,在 HTML 文件中,可以使用项目中的相对路径来引用静态文件。 本步骤的第一个部分将 CSS 文件添加到现有页面模板。

当需要通过代码(例如通过 API 终结点实现)提供静态文件时,Flask 提供一种轻松方法,可使用名为 static 的文件夹(位于项目根目录)中的相对路径来引用文件 。 本步骤的第二个部分使用简单的静态数据文件演示该方法。

在任一情况下,都可按照个人喜好整理 static 中的文件 。

使用模板中的静态文件

  1. 在解决方案资源管理器中,右键单击 Visual Studio 项目中的“HelloFlask”文件夹,选择“添加”>“新文件夹”,然后命名 static 文件夹。

  2. 右键单击 static 文件夹,然后选择“添加”>“新项”。 在出现的对话框中,选择“样式表”模板,将文件命名为 site.css,然后选择“确定” 。 site.css 文件出现在项目中,并在编辑器中打开。 文件夹结构应类似于下图:

    Static file structure as shown in Solution Explorer

  3. 将 site.css 的内容替换为以下代码并保存文件 :

    .message {
        font-weight: 600;
        color: blue;
    }
    
  4. 将应用的 templates/index.html 文件的内容替换为以下代码,此操作会将步骤 2 中使用的 <span> 元素替换为引用 message 样式类的 <strong>。 通过此方式使用样式类为设计元素样式提供了更多灵活性。

    <html>
        <head>
            <title>{{ title }}</title>
            <link rel="stylesheet" type="text/css" href="/static/site.css" />
        </head>
        <body>
            <span class="message">{{ message }}</span>{{ content }}
        </body>
    </html>
    
  5. 运行项目以观察结果。 完成后停止应用,然后可以将更改提交到源代码管理(如步骤 2 中所述)。

通过代码提供静态文件

Flask 提供一个名为 serve_static_file 的函数,可通过代码调用该函数来引用项目的 static 文件夹中的任何文件 。 以下过程创建一个返回静态数据文件的简单 API 终结点。

  1. 如果尚未进行此操作,请创建 static 文件夹:在解决方案资源管理器中,右键单击 Visual Studio 项目中的 HelloFlask 文件夹,选择“添加”>“新文件夹”,然后命名 static 文件夹。

  2. 在 static 文件夹中,创建名为 data.json 的静态 JSON 数据文件,该文件包含以下内容(这些是无意义的示例数据) :

    {
        "01": {
            "note" : "Data is very simple because we're demonstrating only the mechanism."
        }
    }
    
  3. 在 views.py 中,添加含有 /api/data 路由的函数,该路由使用 send_static_file 方法返回静态数据文件:

    @app.route('/api/data')
    def get_data():
      return app.send_static_file('data.json')
    
  4. 运行该应用并导航到 /api/data 终结点来查看是否返回该静态文件。 完成后,请停止应用。

问:是否有组织静态文件的任何约定?

答:可以在 static 文件夹中以所需的方式添加其他 CSS、JavaScript 和 HTML 文件。 组织静态文件的一种典型方法是创建名为 fonts、scripts 和 content 的子文件夹(用于样式表和任何其他文件) 。

问:如何在 API 中处理 URL 变量和查询参数?

答:有关问:Flask 如何使用变量 URL 路由和查询参数?,请参阅步骤 1-4 中的答案:

步骤 3-3:向应用添加一个页面

将其他页添加到应用意味着:

  • 添加用于定义视图的 Python 函数。
  • 添加用于页面标记的模板。
  • 将必需的路由添加到 Flask 项目的 urls.py 文件中 。

以下步骤将“关于”页添加到“HelloFlask”项目,并从主页链接到该页面:

  1. 在解决方案资源管理器中,右键单击 templates 文件夹,选择“添加”>“新项”,选择“HTML 页”项模板,将文件命名为 about.html,然后选择“确定”。

    提示

    如果“新项” 命令未显示在“添加” 菜单上,请确保已停止应用,使 Visual Studio 退出调试模式。

  2. 用下面的标记替换 about.html 的内容(可以在步骤 3-4 中使用简单的导航栏替换到主页的显式链接) :

    <html>
        <head>
            <title>{{ title }}</title>
            <link rel="stylesheet" type="text/css" href="/static/site.css" />
        </head>
        <body>
            <div><a href="home">Home</a></div>
            {{ content }}
        </body>
    </html>
    
  3. 打开应用的 views.py 文件,并添加使用该模板的名为 about 的函数:

    @app.route('/about')
    def about():
        return render_template(
            "about.html",
            title = "About HelloFlask",
            content = "Example app page for Flask.")
    
  4. 打开 templates/index.html 文件,立即在 <body> 元素中添加以下行以链接到“关于”页(同样,可以使用步骤 3-4 中的导航栏替换此链接):

    <div><a href="about">About</a></div>
    
  5. 若要保存所有文件,请使用“文件”>“全部保存”菜单命令,或按 Ctrl+Shift+S。 (在 Visual Studio 中运行项目会自动保存文件,因此不需要此步骤。不过,这是一个很好的命令,应该了解一下!)

  6. 运行项目并观察结果,并检查页面之间的导航。 完成后停止应用。

问:页面函数的名称对 Flask 有影响吗?

答:否,因为这是 @app.route 装饰器,它决定 Flask 调用以生成响应的函数 URL。 开发人员通常将函数名称与路由相匹配,但这种匹配不是必需的。

步骤 3-4:使用模板继承创建标头和导航栏

与在每个页面上都有显式导航链接不同,现代 Web 应用通常使用品牌标头和提供最重要的页面链接、弹出菜单等内容的导航栏。 若要确保应用保持一致,所有页面中的标头和导航栏都应相同,同时不会在每个页面模板中重复出现相同的代码。 相反,你希望在一个位置定义所有页面的公共部分。

Flask 模板系统(默认 Jinja)为实现跨多个模板重用特定元素提供了两种方法:包含和继承。

  • 包含 是可以使用语法 {% include <template_path> %} 在引用模板的特定位置插入的另一个页面模板。 如果想要在代码中动态更改路径,也可以使用一个变量。 包含通常用于页面主体,在页面特定位置拉入共享模板。

  • 继承使用页面模板开头的 {% extends <template_path> %} 来指定共享基本模板,然后会在此模板上生成引用模板。 继承通常用于为应用页面定义共享布局、导航栏和其他结构,这样一来,引用模板只需要添加或修改称为“块”的基本模板的特定区域。

在这两种情况下,<template_path> 对应于应用的 templates 文件夹(还允许 .././)。

基本模板使用 {% block <block_name> %}{% endblock %} 标记来标示块。 如果引用模板随后使用具有相同块名称的标记,那么它的块内容将替代基本模板的内容。

以下步骤演示继承:

  1. 在应用的 templates 文件夹中,创建一个名为 layout.html 的新 HTML 文件(使用“添加”>“新项”上下文菜单或“添加”>“HTML 页”),并将它的内容替换为以下标记。 可以看到此模板包含一个名为“content”的块,这是引用页面需要替换的全部内容:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>{{ title }}</title>
        <link rel="stylesheet" type="text/css" href="/static/site.css" />
    </head>
    
    <body>
        <div class="navbar">
            <a href="/" class="navbar-brand">Hello Flask</a>
            <a href="{{ url_for('home') }}" class="navbar-item">Home</a>
            <a href="{{ url_for('about') }}" class="navbar-item">About</a>
        </div>
    
        <div class="body-content">
            {% block content %}
            {% endblock %}
            <hr/>
            <footer>
                <p>&copy; 2018</p>
            </footer>
        </div>
    </body>
    </html>
    
  2. 向应用的 static/site.css 文件添加以下样式(本教程不在此处演示响应式设计。但是,这些样式仅仅是为了生成一个有趣的结果):

    .navbar {
        background-color: lightslategray;
        font-size: 1em;
        font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
        color: white;
        padding: 8px 5px 8px 5px;
    }
    
    .navbar a {
        text-decoration: none;
        color: inherit;
    }
    
    .navbar-brand {
        font-size: 1.2em;
        font-weight: 600;
    }
    
    .navbar-item {
        font-variant: small-caps;
        margin-left: 30px;
    }
    
    .body-content {
        padding: 5px;
        font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    }
    
  3. 修改 templates/index.html 以引用基本模板并替换内容块 。 可以看到,通过使用继承,此模板变得很简单:

    {% extends "layout.html" %}
    {% block content %}
    <span class="message">{{ message }}</span>{{ content }}
    {% endblock %}
    
  4. 修改 templates/about.html 也可以引用基本模板并替换内容块 :

    {% extends "layout.html" %}
    {% block content %}
    {{ content }}
    {% endblock %}
    
  5. 运行服务器并观察结果。 完成后,请关闭浏览器。

    Running app showing the nav bar

  6. 因为已对应用进行大幅更改,所以现在是将所做的更改提交到源代码管理的绝佳时机。

后续步骤

可以利用以下资源进行深入了解: