第3章:模板-Flask Web 开发(第2版)

模板是一个包含响应文本的文件,其中包含只有在请求上下文中才会知道的动态部分的占位符变量。用实际值替换变量并返回最终响应字符串的过程称为渲染(rendering)。Flask 使用功能强大的模板引擎:Jinja2。

Jinja2 模板引擎

最简单的模板文件,是只包含响应文本内容的文件。示例3-1显示的是和示例2-1中,index() 视图函数返回值一样模板文件:

<!-- 示例3-1:templates/index.html :Jinja2 模板 -->
<h1>Hello World!</h1>

在示例2-2中,user() 视图函数包含一个名为 name 的变量。示例3-2演示了如何实现和该视图函数返回值一样的模板文件:

<!-- 示例3-2:templates/user.html :Jinja2 模板 -->
<h1>Hello, {{ name }}!</h1>

渲染模板

默认,Flask 从主入口文件所在目录的 templates 子目录里搜索模板文件。下一版本的 hello.py,你需要先创建 templates 子目录,并将示例3-1和示例3-2的代码分别保存为 templates/index.htmltemplates/user.html 文件。

# 示例 3-3 hello.py:渲染模板
from flask import Flask, render_template

# ...

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/user/<name>')
def user(name):
    return render_template('user.html', name=name)

如果你从 Github 克隆了示例代码,可以使用 git checkout 3a 签出本示例。

变量

示例3-2中的 {{ name }} 结构,用于在模板中显示变量值。Jinja2 模板引擎能正确识别并处理任何类型的变量:

<p>从字典中获取一个值: {{ mydict['key'] }}.</p>
<p>从列表中获取一个值: {{ mylist[3] }}.</p>
<p>通过一个变量索引,从列表中获取一个值: {{ mylist[myintvar] }}.</p>
<p>从对象的方法中获取一个值: {{ myobj.somemethod() }}.</p>

从本例开始,大部分示例代码本站都会进行适度修改。修改的主要内容是将原书示例代码中的描述性文字进行汉化。读者可以通过对照原书 Git 仓库里的示例代码和本站修改后的代码进行更好的理解。

变量的值可以通过 过滤器(filter) 进行修改。过滤器是在变量名后面加上竖线(|),然后跟上过滤器名来调用。下例使用 capitalize 过滤器,将 name 变量的值首字母大写:

你好, {{ name|capitalize }}!

下表列示了 Jinja2 常用的过滤器。

过滤器 说明
safe 不对 HTML 标签等内容进行转义,而显示变量原始的内容
capitalize 将变量值的首字母转成大写
lower 将变量值所有字母转成小写
upper 将变量值所有字母转成大写
title 将变量值只所有单词的首字母转成大写
trim 去除变量值两边的空白字符
striptags 渲染前,将变量值中的所有 HTML 标签去除

safe 过滤器最值得一提。出于安全考虑,默认情况下,Jinja2 会把所有变量的值进行转义。比如,如果变量的值为 <h1>Hello World!</h1>,Jinja2 会在渲染模板时,将其转义为:&lt;h1&gt;Hello World!&lt;/h1&gt;。完整的过滤器列表,请参见官方文档

控制结构

下例演示了 Jinja2 的条件控制:

{% if user %}
    你好, {{ user }}!
{% else %}
    你好,陌生人!
{% endif %}

下例演示了 Jinja2 的 for 循环:

<ul>
    {% for comment in comments %}
        <li>{{ comment }}</li>
    {% endfor %}
</ul>

Jinja2 还支持宏(macro),Jinja2 的宏类似 Python 里的函数:

{% macro render_comment(comment) %}
    <li>{{ comment }}</li>
{% endmacro %}

<ul>
    {% for comment in comments %}
        {{ render_comment(comment) }}
    {% endfor %}
</ul>

定义宏的目的主要是为了复用代码。可以将宏定义在单独的模板文件中,然后在其它模板文件里导入该文件:

{% import 'macros.html' as macros %}
<ul>
    {% for comment in comments %}
        {{ macros.render_comment(comment) }}
    {% endfor %}
</ul>

可能有一部分模板代码要在多处使用,此时可以将这部分需要多处使用的代码保存到单独的模板文件里,然后在其它模板中通过 include 来将其包含到当前模板里:

{% include 'common.html' %}

另一种复用代码的方式是通过模板继承。模板继承和 Python 里的类继承相似。首先,创建一个基础模板文件 templates/base.html

<!-- 基础模板,templates/base.html -->
<html>
<head>
    {% block head %}
    <title>{% block title %}{% endblock %} - 我的应用</title>
    {% endblock %}
</head>
<body>
    {% block body %}
    {% endblock %}
</body>
</html>

基础模板中通过 block 定义的块,可以在子模板中覆写。上例中定义了三个块:headtitlebody。下面的例子继承了 base.html 模板。

{% extends "base.html" %}
{% block title %}首页{% endblock %}
{% block head %}
    {{ super() }}
    <style>
    </style>
{% endblock %}
{% block body %}
<h1>你好,世界!</h1>
{% endblock %}

通过 Flask-Bootstrap 整合 Bootstrap

Flask-Bootstrap 是一个 Flask 扩展,通过它可以把 Bootstrap 整合到 Flask中。通过以下命令安装:

(venv) $ pip install flask-bootstrap

和大多数 Flask 扩展一样,在使用 Flask-Bootstrap 之前,需要对其进行初始化。

# 示例 3-4 hello.py:Flask-Bootstrap 初始化
from flask_bootstrap import Bootstrap

# ...

bootstrap = Bootstrap(app)

Flask 扩展通常都是通过 flask_<name> 的方式进行导入的。<name> 是 Flask 扩展的名称。Flask 扩展一般是通过将应用对象作为参数传递给扩展的构造函数进行初始化。在第7章中,你将学习到 Flask 扩展更高级的初始化方法。

Flask-Bootstrp 一旦初始化完成之后,生成一个包含所有 bootstrap 文件和一个通用结构的基本模板。应用中的其它模板只要继承这个基本模板即可。示例3-5展示了基于 bootstrap 基本模板的新版 user.html

<!-- 示例 3-5: templates/user.html:继承 bootstrap 基本模板 -->
{% extends "bootstrap/base.html" %}

{% block title %}Flasky{% endblock %}

{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle"
             data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">Flasky</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="/">首页</a></li>
            </ul>
        </div>
    </div>
</div>
{% endblock %}

{% block content %}
<div class="container">
    <div class="page-header">
        <h1>你好, {{ name }}!</h1>
    </div>
</div>
{% endblock %}

如果你从 Github 克隆了示例代码,可以使用 git checkout 3b 签出本示例。

Flask-Bootstrap 默认从海外 CDN 加载 bootstrap 所需的文件(CSS、JavaScript等)。由于网络的原因,Flask-Bootstrap 将显示的非常慢,甚至无法正常显示。Flask-Bootstrap 支持从本地加载 bootstrap 所需的文件,有关此操作,请关注本站推出的教学视频。

Flask-Bootstrap 的 base.html 基础模板定义了一些常用的块,如下所示:

说明
doc HTML 文档实体
html_attribs 内嵌在<html>标签的属性
html <html>标签的内容
head <head>标签的内容
title <title>标签的内容
metas <meta>标签列表
styles 定义CSS样式
body_attribs 内嵌在<body>标签的属性
body <body>标签的内容
navbar 用户定义的导航栏
content 用户定义的页面内容
scripts 定义在文档底部的 Javascript 代码

Flask-Bootstrap 定义的很多块(比如cssscripts块)都是给该扩展自己使用的。如果在子模板中直接覆盖这些块,将会出现问题。Jinja2 提供了 super() 函数,用于显式调用父模板定义的块。

{% block scripts %}
{{ super() }}
<script type="text/javascript" src="my-script.js"></script>
{% endblock %}

自定义错误页面

Flask 允许自定义404、500等错误页面。

# 示例 3-6. hello.py: 自定义错误页面
@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500

如果你从 Github 克隆了示例代码,可以使用 git checkout 3c 签出本示例。

链接

Flask 提供了 url_for() 函数,用于动态生成 URL。该函数通过查找应用的 URL 映射来生成正确的 URL。

最基本的使用方法是,将视图函数的名字传递给该函数:

url_for('index') # 返回 /

要生成动态路由的URL,只需要将路由所需要的参数依次传入即可:

url_for('user', name='john') # 返回 /user/john

如何传入的参数并不是动态路由定义的参数,那么将作为 URL 参数返回:

url_for('user', name='john', page=2, version=1) # 返回 /user/john?page=1&version=1

url_for 既可以在视图函数里使用,也可以在 Jinja2 模板中使用。

静态文件

使用 url_for('static', filename=<静态文件名>) 来生成静态文件的 URL。

url_for('static', filename='css/style.css') # 返回 /static/css/style.css

默认,Flask 从主程序目录的 static 子目录里查找静态文件。

<!--示例 3-10. templates/base.html: 定义favicon-->
{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}"
    type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}"
    type="image/x-icon">
{% endblock %}

如果你从 Github 克隆了示例代码,可以使用 git checkout 3d 签出本示例。

使用 Flask-Moment 本地化日期和时间

Flask-Moment 调用的是 moment.js(moment.js 依赖 jquery),在客户端对日期和时间进行渲染。它是 Flask 的一个扩展。使用它之前,需要安装:

(venv) $ pip install flask-moment

和 Flask-Bootstrap 类似,需要对 Flask-Moment 进行初始化:

# 示例 3-11. hello.py: 初始化 Flask-Moment
from flask_moment import Moment
moment = Moment(app)

初始化完成之后,即可在模板中使用了:

<!--示例 3-12. templates/base.html: 导入 Moment.js 库-->
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}

在视图函数中,将当前时间传给模板:

# 示例 3-13. hello.py: 添加一个 datetime 变量
from datetime import datetime

@app.route('/')
def index():
    return render_template('index.html',
                           current_time=datetime.utcnow())

然后,修改该视图对应的模板文件:

<!--示例 3-14. templates/index.html:  使用 Flask-Moment 渲染时间戳-->
<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>

如果你从 Github 克隆了示例代码,可以使用 git checkout 3e 签出本示例。

Flask-Moment 允许通过 locale() 来设定以本地化的格式显示时间:

{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{{ moment.locale('zh-cn') }}
{% endblock %}

原书代码里的是 {{ moment.locale('es') }},本站将其修改为 {{ moment.locale('zh-cn') }} 以便以中文的格式显示时间

本文并非《Flask Web Development(2nd edition)》的全文翻译。而只是在阅读该书时,对自认为重点的内容进行记录以及思考。由于水平有限,本文所述内容难免出现不足或错误。请通过购买正版图书来进行更系统地学习,原书地址。本站将依照本系列文章录制视频教程,届时本文未作记录的部分以及项目实际开发中所需要的知识都会在视频教程中进行补充。

flask Flask Web Development(2nd edition) 2018-03-25 21:10 2285045