作者选择了自由与开源基金会作为写作捐赠计划的一部分接受捐赠。
引言
Flask 是一个轻量级的Python网络框架,为使用Python语言创建网络应用提供了有用的工具和功能。
在开发网络应用时,将业务逻辑与展示逻辑分开至关重要。业务逻辑负责处理用户请求并与数据库通信以构建适当的响应。展示逻辑则是如何将数据呈现给用户,通常使用HTML文件构建响应网页的基本结构,并利用CSS样式来设计HTML组件。例如,在社交媒体应用中,当用户未登录时,可能只显示用户名和密码字段;若用户已登录,则显示登出按钮。这就是展示逻辑的体现。如果用户输入了用户名和密码,你可以利用Flask执行业务逻辑:从请求中提取数据(用户名和密码),验证凭据正确则登录用户,否则返回错误信息。错误信息的显示方式则由展示逻辑处理。
在 Flask 中,你可以使用 Jinja 模板语言来渲染 HTML 模板。模板是一个文件,可以包含固定内容和动态内容。当用户从你的应用程序请求某些内容(如首页或登录页)时,Jinja 允许你响应一个 HTML 模板,在其中可以使用标准 HTML 中不可用的许多功能,如变量、if
语句、for
循环、过滤器和模板继承。这些功能使你能够高效编写易于维护的 HTML 页面。Jinja 还会自动转义 HTML,以防止 跨站脚本(XSS) 攻击。
在本教程中,你将构建一个渲染多个 HTML 文件的小型 Web 应用程序。你将使用变量将数据从服务器传递到模板。模板继承将帮助你避免重复。你将在模板中使用条件和循环等逻辑,使用过滤器修改文本,并使用 Bootstrap 工具包 来美化你的应用程序。
前提条件
-
一个本地的 Python 3 编程环境。根据您的发行版,遵循如何安装和配置 Python 3 的本地编程环境系列教程。在本教程中,我们将项目目录命名为
flask_app
。 -
在您的编程环境中安装 Flask,具体步骤参见如何使用 Flask 和 Python 创建您的第一个 Web 应用程序的第 1 步。
-
了解基本的Flask概念,如路由和视图函数。如果你不熟悉Flask,可以查看如何使用Flask和Python创建你的第一个Web应用程序。
-
了解基本的HTML概念。你可以回顾我们的如何使用HTML构建网站教程系列以获取背景知识。
步骤1 — 渲染模板并使用变量
确保你已激活环境并安装了Flask,然后就可以开始构建你的应用了。第一步是在首页显示一条欢迎访客的消息。你将使用Flask的render_template()
辅助函数来提供一个HTML模板作为响应。你还将了解如何从应用端向模板传递变量。
首先,在你的flask_app
目录中,打开一个名为app.py
的文件进行编辑。使用nano
或你喜欢的文本编辑器:
在app.py
文件中添加以下代码:
保存并关闭文件。
在这段代码中,你从flask
包导入了Flask
类和render_template()
函数。你使用Flask
类创建了一个名为app
的Flask应用实例。然后,你通过app.route()
装饰器定义了一个视图函数(即返回HTTP响应的Python函数),名为hello()
。该视图函数使用render_template()
函数来渲染一个名为index.html
的模板文件。
接下来,你需要在flask_app
目录内的templates
目录中创建一个名为index.html
的模板文件。Flask会在名为templates
的模板目录中查找模板,因此这个名称很重要。确保你位于flask_app
目录中,并运行以下命令来创建templates
目录:
接下来,在templates
目录中打开一个名为index.html
的文件进行编辑。这里的index.html
并不是一个标准要求的名称;如果你愿意,可以将其命名为home.html
或homepage.html
或其他任何名称:
在index.html
文件中添加以下HTML代码:
在这里,你设置了一个标题,添加了一条Hello World!
消息作为H1
标题,并创建了一条Welcome to FlaskApp!
消息作为H2
标题。
保存并关闭文件。
在激活了虚拟环境的flask_app
目录中,使用FLASK_APP
环境变量告诉Flask应用程序的位置(在你的例子中是app.py
),并将FLASK_ENV
环境变量设置为development
,以开发模式运行应用程序并启用调试器。使用以下命令来完成这一操作(在Windows上,使用set
代替export
):
然后,使用flask run
命令运行应用程序:
在开发服务器运行的情况下,使用浏览器访问以下URL:
http://127.0.0.1:5000/
页面标题设置为 `FlaskApp
`,两个标题则渲染为 HTML。
在网页应用中,你经常需要将应用的 Python 文件中的数据传递到 HTML 模板。为了演示如何在此应用中实现这一点,你将传递一个包含当前 UTC 日期和时间的变量到索引模板,并在模板中显示该变量的值。
保持服务器运行,并在新终端中打开 `app.py
` 文件进行编辑:
从 Python 标准库中导入 `datetime
` 模块,并修改 `index()
` 函数,使文件内容如下:
import datetime
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def hello():
return render_template('index.html', utc_dt=datetime.datetime.utcnow())
保存并关闭文件。
这里你导入了 `datetime
` 模块,并向 `index.html
` 模板传递了一个名为 `utc_dt
` 的变量,其值为 `datetime.datetime.utcnow()
`,即当前的 UTC 日期和时间。
接下来,要在索引页上显示该变量的值,请打开 `index.html
` 文件进行编辑:
将文件修改如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FlaskApp</title>
</head>
<body>
<h1>Hello World!</h1>
<h2>Welcome to FlaskApp!</h2>
<h3>{{ utc_dt }}</h3>
</body>
</html>
保存并关闭文件。
你添加了一个 H3 标题,并使用特殊的 `{{ ... }}
` 分隔符来打印 `utc_dt
` 变量的值。
打开浏览器,访问索引页面:
http://127.0.0.1:5000/
你将看到类似下图的页面:
你现在已经创建了一个带有HTML模板的Flask应用程序索引页面,渲染了一个模板,并传递和显示了一个变量值。接下来,你将通过使用模板继承来避免代码重复。
步骤2 — 使用模板继承
在这一步中,你将创建一个包含可与其他模板共享内容的基模板。你将编辑你的索引模板以继承自基模板。然后,你将创建一个新页面,作为你的应用程序的“关于”页面,用户可以在那里找到更多关于你的应用程序的信息。
一个基模板包含通常在所有其他模板之间共享的HTML组件,例如应用程序的标题、导航栏和页脚。
首先,在你的模板目录中打开一个名为base.html
的新文件进行编辑:
在你的base.html
文件中编写以下代码:
保存并关闭文件。
这个文件中的大部分代码是标准的HTML,一个标题,一些用于导航链接的样式,一个带有两个链接的导航栏,一个用于索引页面,另一个用于尚未创建的“关于”页面,以及一个用于页面内容的<div>
。(这些链接目前还不起作用;下一步将展示如何链接页面)。
然而,以下高亮部分是特定于Jinja模板引擎的:
-
{% block title %} {% endblock %}
: 一个块,用作标题的占位符。稍后,您将在其他模板中使用它,为应用程序中的每个页面提供自定义标题,而无需每次都重写整个<head>
部分。 -
{% block content %} {% endblock %}
: 另一个块,将根据子模板(继承自base.html
的模板)的内容进行替换,该子模板将覆盖它。
现在您有了一个基础模板,可以通过继承来利用它。打开index.html
文件:
然后将其内容替换为以下内容:
在这里,您使用 `{% extends %}
` 标签来继承 `base.html
` 模板。然后通过替换基础模板中的 `content
` 块,扩展它,使其包含前述代码块中的 `content
` 块内容。
这个内容块包含一个 `<h1>
` 标签,其中文本为 `Index
`,它位于一个标题块内,该标题块替换了 `base.html
` 模板中原有的 `title
` 块,使其文本变为 `Index
`,从而完整标题变为 `Index - FlaskApp
`。这样,您可以避免重复相同的文本,因为它既作为页面标题,又作为出现在继承自基础模板的导航栏下方的标题。
接着,您还有几个标题:一个带有文本 `Hello World!
` 的 `<h1>
` 标题,一个 `<h2>
` 标题,以及一个包含 `utc_dt
` 变量值的 `<h3>
` 标题。
模板继承使您能够重用在其他模板(此处为 `base.html
`)中的HTML代码,而无需每次需要时都重复编写。
保存并关闭文件,然后在浏览器中刷新索引页面。页面将显示如下:
接下来,您将创建关于页面。打开 `app.py
` 文件以添加新路由:
在文件末尾添加以下路由:
在这里,您使用app.route()
装饰器来创建一个名为about()
的视图函数。在其中,您返回调用render_template()
函数的结果,并以about.html
模板文件名作为参数。
保存并关闭文件。
打开一个名为about.html
的模板文件进行编辑:
向文件中添加以下代码:
此处,您通过extends
标签继承自基础模板,将基础模板的content
块替换为一个<h1>
标签,该标签同时也作为页面的标题,并添加一个包含应用程序相关信息的<h3>
标签。
保存并关闭文件。
在开发服务器运行的情况下,使用浏览器访问以下URL:
http://127.0.0.1:5000/about
您将看到一个类似如下的页面:
注意导航栏和部分标题是如何从基础模板继承而来的。
现在,您已经创建了一个基础模板,并在索引页和关于页中使用它以避免代码重复。此时,导航栏中的链接还未实现任何功能。在下一步中,您将学习如何通过修复导航栏链接来在模板中链接不同的路由。
步骤3 — 页面间链接
在这一步中,你将学习如何使用url_for()
辅助函数在你的模板中进行页面间的链接。你将在基础模板中的导航栏添加两个链接,一个指向首页,另一个指向关于页面。
首先打开你的基础模板进行编辑:
编辑文件使其如下所示:
这里,你使用了特殊的url_for()
函数,它会返回你提供的视图函数的URL。第一个链接指向hello()
视图函数的路由(即首页)。第二个链接指向about()
视图函数的路由。注意,你传递的是视图函数的名称,而不是路由(如/
或/about
)。
使用url_for()
函数来构建URL有助于更好地管理URL。如果你硬编码URL,修改路由时链接将会失效。而使用url_for()
,你可以修改路由并确保链接仍能正常工作。url_for()
函数还会处理转义特殊字符等其他事项。
保存并关闭文件。
现在前往首页,尝试导航栏中的链接。你会发现它们如预期般工作。
你已经学会了如何在模板中使用 `url_for()` 函数链接到其他路由。接下来,你将添加一些条件语句,根据你设定的条件控制模板中显示的内容,并在模板中使用 `for` 循环来显示列表项。
步骤 4 — 使用条件语句和循环
在这一步中,你将在模板中使用 `if` 语句来根据特定条件控制显示内容。你还将使用 `for` 循环遍历 Python 列表,并在列表中显示每个项目。你将添加一个新页面,以列表形式显示评论。索引号为偶数的评论将具有蓝色背景,而索引号为奇数的评论将显示为灰色背景。
首先,你将为评论页面创建一个路由。打开你的 `app.py` 文件进行编辑:
在文件末尾添加以下路由:
在上面的路由中,你有一个名为 `comments` 的 Python 列表,其中包含四个项目。(在实际场景中,这些评论通常来自数据库,而不是像你在这里硬编码的那样。)你在最后一行返回一个名为 `comments.html` 的模板文件,并将包含列表的变量 `comments` 传递给模板文件。
保存并关闭文件。
接下来,在 `templates` 目录中打开一个新的 `comments.html` 文件进行编辑:
将以下代码添加到文件中:
在这里,你扩展了 `base.html` 模板并替换了 `content` 块的内容。首先,你使用一个 `
` 标题,它同时也作为页面的标题。
你在 `{% for comment in comments %}` 这一行使用了一个 Jinja 的 `for` 循环,来遍历 `comments` 列表中的每一个评论(这些评论会被存储在 `comment` 变量中)。你通过 `
{{ comment }}
` 标签来显示评论,这与在 Jinja 中正常显示变量的方式相同。你使用 `{% endfor %}` 关键字来标识 `for` 循环的结束。这与 Python 的 `for` 循环构造不同,因为在 Jinja 模板中没有特殊的缩进要求。
保存并关闭文件。
在开发服务器运行的情况下,打开浏览器并访问评论页面:
http://127.0.0.1:5000/comments
你将看到类似以下的页面:
现在,你将在模板中使用 `if` 条件语句,通过显示奇数索引号的评论带有灰色背景,偶数索引号的评论带有蓝色背景。
打开你的 `comments.html` 模板文件进行编辑:
编辑使其如下所示:
通过这次编辑,你在代码行 `{% if loop.index % 2 == 0 %}
` 中添加了一个 `if
` 语句。这里的 `loop
` 变量是 Jinja 的一个特殊变量,它提供了关于当前循环的信息。在这里,你使用 `loop.index
` 来获取当前项的索引,该索引从 `1
` 开始,不同于 Python 列表中的 `0
` 开始。
这个 `if
` 语句通过使用 `%
` 运算符来检查索引是否为偶数。它检查索引数除以 `2
` 的余数;如果余数为 `0
`,则意味着索引数是偶数,否则索引数是奇数。你使用 `{% set %}
` 标签声明了一个名为 `bg_color
` 的变量。如果索引数是偶数,你将其设置为一种偏蓝色的颜色,否则,如果索引数是奇数,你将 `bg_color
` 变量设置为灰色。然后,你使用 `bg_color
` 变量为包含评论的 `<div>
` 标签设置背景颜色。在评论文本上方,你使用 `loop.index
` 在 `<p>
` 标签中显示当前索引号。
保存并关闭文件。
打开浏览器并访问评论页面:
http://127.0.0.1:5000/comments
你将看到新的评论页面:
这是展示如何使用 `if
` 语句的一个示例。但通过使用 Jinja 的特殊辅助函数 `loop.cycle()
`,你也可以实现同样的效果。为了演示这一点,打开 `comments.html
` 文件:
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} Comments {% endblock %}</h1>
<div style="width: 50%; margin: auto">
{% for comment in comments %}
<div style="padding: 10px;
background-color: {{ loop.cycle('#EEE', '#e6f9ff') }};
margin: 20px">
<p>#{{ loop.index }}</p>
<p style="font-size: 24px">{{ comment }}</p>
</div>
{% endfor %}
</div>
{% endblock %}
在这里,你移除了 if/else
语句,并使用了 loop.cycle('#EEE', '#e6f9ff')
助手来在两种颜色之间循环。background-color
的值将会是 #EEE
一次,然后是 #e6f9ff
另一次。
保存并关闭文件。
在浏览器中打开评论页面,刷新它,你会发现这与使用 if
语句的效果相同。
你可以使用 if
语句实现多种功能,包括控制页面上显示的内容。例如,要显示除第二条评论之外的所有评论,你可以使用条件为 loop.index != 2
的 if
语句来过滤掉第二条评论。
打开评论模板:
并编辑成如下形式:
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} Comments {% endblock %}</h1>
<div style="width: 50%; margin: auto">
{% for comment in comments %}
{% if loop.index != 2 %}
<div style="padding: 10px;
background-color: #EEE;
margin: 20px">
<p>#{{ loop.index }}</p>
<p style="font-size: 24px">{{ comment }}</p>
</div>
{% endif %}
{% endfor %}
</div>
{% endblock %}
在这里,你使用 {% if loop.index != 2 %}
来显示索引不是 2
的评论,这意味着所有评论除了第二条。你还使用了一个硬编码的背景颜色值,而不是 loop.cycle()
助手,以简化操作,其余部分保持不变。你使用 {% endif %}
结束 if
语句。
保存并关闭文件。
刷新评论页面,你会发现第二条评论没有显示。
现在你需要在导航栏中添加一个链接,将用户带到评论页面。打开基础模板进行编辑:
编辑 `
在这里,你使用 `url_for()` 助手链接到 `comments()` 视图函数。
保存并关闭文件。
导航栏现在会有一个新的链接,指向评论页面。
你在模板中使用了 `if` 语句来根据特定条件控制显示内容。你使用了 `for` 循环遍历 Python 列表并显示每个项目,并且你了解了 Jinja 中的特殊 `loop` 变量。接下来,你将使用 Jinja 过滤器来控制变量数据的显示方式。
步骤 5 — 使用过滤器
在这一步中,你将学习如何在模板中使用 Jinja 过滤器。你将使用 `upper` 过滤器将上一步添加的评论转换为大写,使用 `join` 过滤器将一系列字符串连接成一个字符串,并学习如何使用 `safe` 过滤器在不转义的情况下渲染受信任的 HTML 代码。
首先,你将把评论页面中的评论转换为大写。打开 `comments.html` 模板进行编辑:
编辑它,使其如下所示:
在这里,您通过管道符号(|
)添加了upper
过滤器。这将修改comment
变量的值,使其变为大写。
保存并关闭文件。
在开发服务器运行的情况下,用浏览器打开评论页面:
http://127.0.0.1:5000/comments
应用过滤器后,您可以看到评论全部变成了大写。
过滤器还可以接受括号中的参数。为了演示这一点,您将使用join
过滤器将comments
列表中的所有评论连接起来。
打开评论模板:
编辑使其如下所示:
在这里,您添加了一个<hr>
标签和一个<div>
标签,其中使用join()
过滤器将comments
列表中的所有评论连接起来。
保存并关闭文件。
刷新评论页面,您将看到类似以下的页面:
如您所见,comments
列表显示为评论之间用管道符号分隔,这是您传递给join()
过滤器的参数。
另一个重要的过滤器是 `safe
` 过滤器,它允许你在浏览器中渲染受信任的HTML。为了说明这一点,你将在评论模板中使用 `{{ }}
` Jinja 分隔符添加一些包含HTML标签的文本。在实际应用中,这些内容通常来自服务器变量。然后,将 `join()
` 参数改为 `<hr>
` 标签,而不是管道符号。
打开评论模板:
编辑使其如下所示:
在这里,你添加了值 `"<h1>COMMENTS</h1>"
` 并将连接参数改为 `<hr>
` 标签。
保存并关闭文件。
刷新评论页面,你会看到类似以下的页面:
可以看到,HTML标签并未被渲染。这是Jinja的安全特性,因为某些HTML标签可能有害,可能导致 跨站脚本(XSS) 攻击。你应仅允许受信任的HTML在浏览器中渲染。
为了渲染上述HTML标签,打开评论模板文件:
通过添加 `safe
` 过滤器进行编辑:
你可以看到,也可以像在行 `<p>{{ comments | join(" <hr> ") | safe }}</p>
` 中那样链式使用过滤器。每个过滤器应用于前一个过滤的结果。
保存并关闭文件。
刷新评论页面,你会看到HTML标签现在按预期渲染了:
警告:在处理来自未知数据源的HTML时使用safe
过滤器可能会使您的应用程序面临XSS攻击的风险。除非渲染的HTML来自可信源,否则请勿使用它。
更多信息,请查看Jinja内置过滤器列表。
您现在已了解如何在Jinja模板中使用过滤器来修改变量值。接下来,您将集成Bootstrap工具包来美化您的应用程序。
步骤6 — 集成Bootstrap
在这一步中,您将学习如何使用Bootstrap工具包来美化您的应用程序。您将在基础模板中添加一个Bootstrap导航栏,该导航栏将出现在所有继承自基础模板的页面中。
Bootstrap工具包有助于您美化应用程序,使其更具视觉吸引力。它还能帮助您在Web应用程序中加入响应式网页,以便在移动浏览器上良好运行,而无需自行编写HTML、CSS和JavaScript代码来实现这些目标。
要使用Bootstrap,您需要将其添加到基础模板中,以便在所有其他模板中使用。
打开你的 `base.html
` 模板,进行编辑:
将其编辑成如下内容:
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
<title>{% block title %} {% endblock %} - FlaskApp</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('hello') }}">FlaskApp</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('comments') }}">Comments</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('about') }}">About</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
{% block content %} {% endblock %}
</div>
<!-- Optional JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script>
</body>
</html>
上述代码大部分是使用 Bootstrap 所需的样板代码。你有一些元标签,在 `<head>
` 部分链接到 Bootstrap 的 CSS 文件,底部则链接到可选的 JavaScript。代码中高亮的部分包含了在前几步中解释的 Jinja 代码。注意你如何使用特定的标签和 CSS 类来告诉 Bootstrap 如何显示每个元素。
在上面的 `<nav>
` 标签中,你有一个带有 `navbar-brand
` 类的 `<a>
` 标签,它决定了导航栏中的品牌链接。在 `<ul class="navbar-nav">
` 标签内部,你有一个 `<li>
` 标签内的 `<a>
` 标签,包含了常规的导航栏项目。
要了解更多关于这些标签和 CSS 类的信息,请参阅 Bootstrap 组件。
保存并关闭文件。
在开发服务器运行的情况下,用浏览器打开索引页面:
http://127.0.0.1:5000/
你会看到类似以下的页面:
现在你可以在所有模板中使用 Bootstrap 组件来为 Flask 应用程序中的项目进行样式设计。
结论
你现在了解了如何在 Flask 网络应用中使用 HTML 模板。你通过变量将数据从服务器传递到模板,利用模板继承来避免重复,引入了诸如 `if
` 条件语句和 `for
` 循环等元素,并实现了不同页面间的链接。你还学习了如何使用过滤器来修改文本和显示受信任的 HTML,并将 Bootstrap 集成到你的应用中。
如果你想进一步了解 Flask,可以查阅Flask 主题页面。
Source:
https://www.digitalocean.com/community/tutorials/how-to-use-templates-in-a-flask-application