Flask: Jinja2 模板的使用方式:

Web 开发中, 在前后端不分离的模式状态下, 模板是最为重要的一部分, 我们可以将前端的页面放在 templates 目录下, 然后再视图函数中, 调用render_templates 函数, 将后端数据通过 key=value 的形式, 传递模板, 更能方便的处理数据。

模板基本使用:

在templates目录下创建一个watchlist.html作为模板文件


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>{{ user.username }}'s Watchlist</title>
</head>
<body>
<a href="{{ url_for('index') }}">&larr; Return</a>
<h2>{{ user.username }}</h2>
{% if user.bio %}
    <i>{{ user.bio }}</i>
{% else %}
    <i>This user has not provided a bio.</i>
{% endif %}
{# 下面是电影清单(这是注释) #}
<h5>{{ user.username }}'s Watchlist ({{ movies|length }}):</h5>
<ul>
    {% for movie in movies %}
        <li>{{ movie.name }} - {{ movie.year }}</li>
    {% endfor %}
</ul>
</body>
</html>

有了模板, 自然要有一个视图函数去渲染它:

@app.route('/')
def movie_list():
    user = {
        'username': 'Grey Li',
        'bio': 'A boy who loves movies and music.',
    }
    movies = [
        {'name': 'My Neighbor Totoro', 'year': '1988'},
        {'name': 'Three Colours trilogy', 'year': '1993'},
        {'name': 'Forrest Gump', 'year': '1994'},
        {'name': 'Perfect Blue', 'year': '1997'},
        {'name': 'The Matrix', 'year': '1999'},
        {'name': 'Memento', 'year': '2000'},
        {'name': 'The Bucket list', 'year': '2007'},
        {'name': 'Black Swan', 'year': '2010'},
        {'name': 'Gone Girl', 'year': '2014'},
        {'name': 'CoCo', 'year': '2017'},
    ]

    return render_template('watchlist.html', user=user, movies=movies)

假如 user, movies 的数据是从数据库中查询出来的。

可以看到这里调用了 flask 提供的 render_template 方法, 然后将数据以 key=value 的形式传递到模板中, 就可以在模板中方便的处理渲染了

上下文:

模板上下文包含了很多变量,其中包括我们调用render_template()函数时手动传入的变量以及Flask默认传入的变量。

除了从视图函数中传递来的变量, 我们也可以在末班中定义变量, 使用 set 标签
 

{% set navigation = [('/', 'Home'), ('/about', 'About')] %}

 也可以将一部分模板数据定义为变量,使用set和endset标签声明开始和结束:


{% set navigation %}
    <li><a href="/">Home</a>
    <li><a href="/about">About</a>
{% endset %}

 内置上下文变量:

flask 模板中提供了一些内置变量, 这些变量可以直接在模板中使用,而无需传入

 自定义上下文:

除了内置的变量,我们也可以通过flask 提供的 context_processor 装饰器, 来自定义一些变量供模板使用:


@app.context_processor
def inject_foo():
    data = 'Hello Flask!!!'
    return {'data': data}

 然后, 可以直接在模板中调用这个 data 变量:

当然, 我们也可以不用定义一个函数, 来自定义变量传入模板, 我们也可以 使用全局变量, 这个视情况而定, 这时候, 我们需要使用 lamda 匿名函数

内置全局函数:

在jinja2 中, 同样提供了一些内置函数

 这里只列出了部分常用的全局函数,完整的全局函数列表请访问 http://jinja.pocoo.org/docs/2.10/templates/#list-of-global-functions 查看。

除了Jinja2内置的全局函数,Flask也在模板中内置了两个全局函数

 Flask除了把g、session、config、request对象注册为上下文变量,也将它们设为全局变量,因此可以全局使用。
url_for()用来获取URL,用法和在Python脚本中相同。在前面给出的watchlist.html模板中,用来返回主页的链接直接写出。在实际的代码中,这个URL使用url_for()生成,传入index视图的名称:


<a href="{{ url_for('index') }}">hello flask</a>

自定义全局函数:

除了使用app.context_processor注册模板上下文处理函数来传入函数,我们也可以使用app.template_global装饰器直接将函数注册为模板全局函数。

@app.template_global()
def test_func():
    return 'i am flask test'

默认使用函数的原名称传入模板,在app.template_global()装饰器中使用name参数可以指定一个自定义名称。app.template_global()仅能用于注册全局函数。

你可以直接使用app.add_template_global()方法注册自定义全局函数,传入函数对象和可选的自定义名称(name),比如app.add_template_global(your_global_function)。

过滤器:

在模板中, 使用过滤器是可以方便的修饰和过滤变量的特殊函数。过滤器和变量之间用 | 隔开, 例如, 把 flask 这个单词的 f 转换为大写:


{{ name|title }}

{{ movies|length }}

这会将name变量的值标题化,相当于在Python里调用name.title()。再比如,我们在本章开始的示例模板watchlist.html中使用length获取movies列表的长度,类似于在Python中调用len(movies)

 另一种用法是将过滤器作用于一部分模板数据,使用filter标签和endfilter标签声明开始和结束。比如,下面使用upper过滤器将一段文字转换为大写:


{% filter upper %}
    This text becomes uppercase.
{% endfilter %}

内置过滤器:

这里只列出了一部分常用的过滤器,完整的列表请访问 

http://jinja.pocoo.org/docs/2.10/templates/#builtin-filters

在使用过滤器时,列表中过滤器函数的第一个参数表示被过滤的变量值(value)或字符串(s),即竖线符号左侧的值,其他的参数可以通过添加括号传入。
另外,过滤器可以叠加使用,下面的示例为name变量设置默认值,并将其标题化:


<h1>Hello, {{ name|default('flask')|title }}!</h1>

使用 safe 过滤器可以将 html 文本内容转变成浏览器可渲染的内容, 但是需要注意的是, 在完成一些列的判断处理之后,才可以调用safe, 以免用户传递恶意代码。


{{ sanitized_text|safe }}

另一种将文本标记为安全的方法是在渲染前将变量转换为Markup对象:

注:高版本的flask 好像已经弃用了 Markup, 这里只做一个了解即可


from flask import Markup
@app.route('/hello')
def hello():
    text = Markup('<h1>Hello, Flask!</h1>')
    return render_template('index.html', text=text)

自定义过滤器:

如果提供的内置过滤器满足不了我们的需求, 那么我们就需要自定义过滤器了.

使用app.template_filter()装饰器可以注册自定义过滤器.

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():

    data = 1

    return render_template('index.html', data=data)


@app.template_filter()
def my_filter(data):

    if data >=5:
        return 'hello! everyone'

    else:
        return 'hello! flask'


if __name__ == '__main__':

    app.run(debug=True)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

{{ data|my_filter }}

</body>
</html>

和注册全局函数类似,你可以在app.template_filter()中使用name关键字设置过滤器的名称,默认会使用函数名称。过滤器函数需要接收被处理的值作为输入,返回处理后的值。

你可以直接使用app.add_template_filter()方法注册自定义过滤器,传入函数对象和可选的自定义名称(name),比如app.add_template_filter(your_filter_function)但是更推荐使用装饰器的方式, 简单方便高效

测试器:

在Jinja2中,测试器(Test)是一些用来测试变量或表达式,返回布尔值(True或False)的特殊函数。比如,number测试器用来判断一个变量或表达式是否是数字,我们使用is连接变量和测试器:


{% if age is number %}
  {{ age * 365 }}
{% else %}
  无效的数字。
{% endif %}

内置测试器:

这里只列出了一部分常用的测试器,完整的内置测试器列表请访问

http://jinja.pocoo.org/docs/2.10/templates/#list-of-builtin-tests

在使用测试器时,is的左侧是测试器函数的第一个参数(value),其他参数可以添加括号传入,也可以在右侧使用空格连接,以sameas为例:

 

 自定义测试器:

和过滤器类似,我们可以使用Flask提供的app.template_test()装饰器来注册一个自定义测试器


@app.template_test()
def baz(n):
    if n == 'baz':
        return True
    return False

 测试器的名称默认为函数名称,你可以在app.template_test()中使用name关键字指定自定义名称。测试器函数需要接收被测试的值作为输入,返回布尔值。

宏:

宏(macro)是Jinja2提供的一个非常有用的特性,它类似Python中的函数。使用宏可以把一部分模板代码封装到宏里,使用传递的参数来构建内容,最后返回构建后的内容。在功能上,它和局部模板类似,都是为了方便代码块的重用。


{% macro qux(amount=1) %}
    {% if amount == 1 %}
        I am qux.
    {% elif amount > 1 %}
        We are quxs.
    {% endif %}
{% endmacro %}

使用时,需要像从Python模块中导入函数一样使用import语句导入它,然后作为函数调用,传入必要的参数,如下所示:


{% from 'macros.html' import qux %}
...
{{ qux(amount=5) }}

另外,在使用宏时我们需要注意上下文问题。在Jinja2中,出于性能的考虑,并且为了让这一切保持显式,默认情况下包含(include)一个局部模板会传递当前上下文到局部模板中,但导入(import)却不会。具体来说,当我们使用render_template()函数渲染一个foo.html模板时,这个foo.html的模板上下文中包含下列对象:


·Flask使用内置的模板上下文处理函数提供的g、session、config、request。
·扩展使用内置的模板上下文处理函数提供的变量。
·自定义模板上下文处理器传入的变量。
·使用render_template()函数传入的变量。

Jinja2和Flask内置及自定义全局对象。
·Jinja2内置及自定义过滤器。
·Jinja2内置及自定义测试器。

使用include标签插入的局部模板(比如_banner.html)同样可以使用上述上下文中的变量和函数。而导入另一个并非被直接渲染的模板(比如macros.html)时,这个模板仅包含下列这些对象:
·Jinja2和Flask内置的全局函数和自定义全局函数。
·Jinja2内置及自定义过滤器。
·Jinja2内置及自定义测试器。

因此,如果我们想在导入的宏中使用前一个列表中的2、3、4项,就需要在导入时显式地使用with context声明传入当前模板的上下文:


{% from "macros.html" import foo with context %}

模板继承:

Jinja2的模板继承允许你定义一个基模板,把网页上的导航栏、页脚等通用内容放在基模板中,而每一个继承基模板的子模板在被渲染时都会自动包含这些部分。使用这种方式可以避免在多个模板中编写重复的代码。 

父模板:

基模板存储了程序页面的固定部分,通常被命名为base.html或layout.html。示例程序中的基模板base.html中包含了一个基本的HTML结构,我们还添加了一个简单的导航条和页脚。


<!DOCTYPE html>
<html>
<head>
    {% block head %}
        <meta charset="utf-8">
        <title>{% block title %}Template - HelloFlask{% endblock %}</title>
    {% block styles %}{% endblock %}
    {% endblock %}
</head>
<body>
<nav>
    <ul><li><a href="{{ url_for('index') }}">Home</a></li></ul>
</nav>
<main>
    {% block content %}{% endblock %}
</main>
<footer>
    {% block footer %}
        ...
    {% endblock %}
</footer>
{% block scripts %}{% endblock %}
</body>
</html>

当子模板继承基模板后,子模板会自动包含基模板的内容和结构。为了能够让子模板方便地覆盖或插入内容到基模板中,我们需要在基模板中定义块(block),在子模板中可以通过定义同名的块来执行继承操作。
块的开始和结束分别使用block和endblock标签声明,而且块之间可以嵌套。在这个基模板中,我们创建了六个块:head、title、styles、content、footer和scripts,分别用来划分不同的代码。其中,head块表示<head>标签的内容,title表示<title>标签的内容,content块表示页面主体内容,footer表示页脚部分,styles块和scripts块,则分别用来包含CSS文件和JavaScript文件引用链接或页内的CSS和JavaScript代码。

子模板:

因为基模板中定义了HTML的基本结构,而且包含了页脚等固定信息,在子模板中我们不再需要定义这些内容,只需要对特定的块进行修改。这时我们可以修改前面创建的电影清单模板watchlist.html和主页模板index.html,将这些子模板的通用部分合并到基模板中,并在子模板中定义块来组织内容,以便在渲染时将块中的内容插入到基模板的对应位置。

我们使用extends标签声明扩展基模板,它告诉模板引擎当前模板派生自base.html。


{% extends 'base.html' %}
{% from 'macros.html' import qux %}
{% block content %}
{% set name='baz' %}
<h1>Template</h1>
<ul>
    <li><a href="{{ url_for('watchlist') }}">Watchlist</a></li>
    <li>Filter: {{ foo|musical }}</li>
    <li>Global: {{ bar() }}</li>
    <li>Test: {% if name is baz %}I am baz.{% endif %}</li>
    <li>Macro: {{ qux(amount=5) }}</li>
</ul>
{% endblock %}

 我们使用extends标签声明扩展基模板,它告诉模板引擎当前模板派生自base.html。注意 extends必须是子模板的第一个标签。
我们在基模板中定义了四个块,在子模板中,我们可以对父模板中的块执行两种操作:
(1)覆盖内容
当在子模板里创建同名的块时,会使用子块的内容覆盖父块的内容。比如我们在子模板index.html中定义了title块,内容为Home,这会把块中的内容填充到基模板里的title块的位置,最终渲染为<title>Home</title>,content块的效果同理。

(2)追加内容
如果想要向基模板中的块追加内容,需要使用Jinja2提供的super()函数进行声明,这会向父块添加内容。比如,下面的示例向基模板中的styles块追加了一行<style>样式定义: 

当子模板被渲染时,它会继承基模板的所有内容,然后根据我们定义的块进行覆盖或追加操作


{% block styles %}
{{ super() }}
<style>
    .foo {
        color: red;
    }
</style>
{% endblock %}

 

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_文书先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值