模板

Flask默认使用的模板引擎是Jinja2,它是一个功能齐全的Python模板引擎,除了设置变量,还允许我们在模板中添加if判断,执行for迭代,调用函数等,以各种方式控制模板的输出。

模板基本用法

创建模板

template/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 %}
{# Below is the movie list (this is comment) #}
<h5>{{ user.username }}'s Watchlist ({{ movies|length }}):</h5>
<ul>
    {% for movie in movies %}
    <li>{{ movie.name }} - {{ movie.year }}</li>
    {% endfor %}
</ul>
</body>
</html>

语句:比如if判断、for循环等

{% ... %}

表达式比如字符串、变量、函数调用等

{{ ... }}

注释:

{# ... #}

user字典中的username键值通过“.”获取,即user.username,在效果上等同于user['username']

模板语法

{% if user.bio %}
    <i>{{ user.bio }}</i>
{% else %}
    <i>This user has not provided a bio.</i>
{% endif %}

末尾的{% endif %}用来声明if语句的结束,这一行不能省略。

for语句用来迭代一个序列:

<ul>
    {% for movie in movies %}
    <li>{{ movie.name }} - {{ movie.year }}</li>
    {% endfor %}
</ul>

渲染模板

使用Flask提供的渲染函数render_template()
template/app.py:渲染HTML模板

from flask import Flask,render_template

app = Flask(__name__)

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'},
]
@app.route('/')
def index():
    return 'hello'

@app.route('/watchlist')
def watchlist():
    return render_template('watchlist.html',user=user,movies=movies)

Flask会在程序根目录下的templates文件夹里寻找模板文件,所以这里传入的文件路径是相对于templates根目录的。左边的user表示传入模板的变量名称,右边的user则是要传入的对象。

模板辅助工具

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

上下文

可以在模板中定义变量,使用set标签:

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

内置上下文变量

自定义上下文

Flask提供了一个app.context_processor装饰器,可以用来注册模板上下文处理函数,它可以帮我们完成统一传入变量的工作。

注册模板上下文处理函数

@app.context_processor
def inject_foo():
    foo = 'I am foo.'
    #等同于 return {'foo':foo}
    return dict(foo=foo)

当我们调用render_template()函数渲染任意一个模板时,所有使用app.context_processor装饰器注册的模板上下文处理函数(包括Flask内置的上下文处理函数)都会被执行,这些函数的返回值会被添加到模板中,因此我们可以在模板中直接使用foo变量。

<h1>{{foo}}</h1>

除了使用app.context_processor装饰器,也可以直接将其作为方法调用,传入模板上下文处理函数:

def inject_foo2():
    foo = 'I am foo2.'
    #等同于 return {'foo':foo}
    return dict(foo2=foo)

app.context_processor(inject_foo2)

使用lambda可以简化为:

app.context_processor(lambda :dict(foo='I am foo.'))

全局对象

全局对象是指在所有的模板中都可以直接使用的对象,包括在模板中导入的模板。

内置全局函数

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

自定义全局函数

可以使用app. template_global装饰器直接将函数注册为模板全局函数。

bar()函数注册为模板全局函数。

template/app.py:注册模板全局函数

@app.template_global
def bar():
    return 'I am bar.'

过滤器

在Jinja2中,过滤器(filter)是一些可以用来修改和过滤变量值的特殊函数,过滤器和变量用一个竖线(管道符号)隔开,需要参数的过滤器可以像函数一样使用括号传递。

{{ name | title }}

相当于在Python里调用name.title()

使用length获取movies列表的长度,类似于在Python中调用len(movies)

{{ movies | length }}

内置过滤器

过滤器可以叠加使用,比如:

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

自定义过滤器

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

from flask import Markup
@app.template_filter()
def musical(s):
    return s + Markup(' &#9835;')

过滤器函数需要接收被处理的值作为输入,返回处理后的值。过滤器函数接收s作为被过滤的变量值,返回处理后的值。我们创建的musical过滤器会在被过滤的变量字符后面添加一个音符(single bar note)图标,因为音符通过HTML实体♫表示,我们使用Markup类将它标记为安全字符。

测试器

在Jinja2中,测试器(Test)是一些用来测试变量或表达式,返回布尔值(True或False)的特殊函数。

使用is连接变量和测试器:

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

内置测试器

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

{% if foo is sames(bar)%}

等同于:

{% if foo is sames bar %}

自定义测试器

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

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

传入函数对象和可选的自定义名称(name),比如app.add_template_test(your_test_function)

模板环境对象

在程序中,我们可以使用app.jinja_env更改Jinja2设置。比如,你可以自定义所有的定界符。下面使用variable_start_stringvariable_end_string分别自定义变量定界符的开始和结束符号:

app = Flask(__name__)
app.jinja_env.variable_start_string = '[['
app.jinja_env.variable_end_string = ']]'

添加自定义全局对象

下面的代码使用app.jinja_env.globals分别向模板中添加全局函数bar和全局变量foo

def bar():
    return 'I am bar.'
foo = 'I am foo.'
app.jinja_env.globals['bar'] = bar
app.jinja_env.globals['foo'] = foo

添加自定义过滤器

下面的代码使用app.jinja_env.filters向模板中添加自定义过滤器smiling

def smiling(s):
    return s + ':)'

app.jinja_env.filters['smiling'] = smiling

添加自定义测试器

使用app.jinja_env.tests向模板中添加自定义测试器baz

def baz(n):
    if n == 'baz':
        return True
    return False

app.jinja_env.tests['baz'] = baz

模板结构组织

局部模板

我们使用include标签来插入一个局部模板,这会把局部模板的全部内容插在使用include标签的位置。

{% include '_banner.html' %}

可以把宏存储在单独的文件中,这个文件通常命名为macros.html_macors.html。在创建宏时,我们使用macroendmacro标签声明宏的开始和结束。

{% 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的模板继承允许你定义一个基模板,把网页上的导航栏、页脚等通用内容放在基模板中。

编写基模板

template/templates/base.html:定义基模板

<!DOCTYPE html>
<html>
<head>
    {% block head %}
        <meta charset="utf-8">
        <title>{% block title %}Template - HelloFlask{% endblock %}</title>
        <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
        {% block styles %}
            <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css' ) }}">
        {% endblock %}
    {% endblock %}
</head>
<body>
<nav>
    <ul><li><a href="{{ url_for('index') }}">Home</a></li></ul>
</nav>

<main>
    {% for message in get_flashed_messages() %}
        <div class="alert">{{ message }}</div>
    {% endfor %}
    {% block content %}{% endblock %}
</main>
<footer>
    {% block footer %}
        <small> &copy; 2018 <a href="http://greyli.com" title="Written by Grey Li">Grey Li</a> /
            <a href="https://github.com/greyli/helloflask" title="Fork me on GitHub">GitHub</a> /
            <a href="http://helloflask.com" title="A HelloFlask project">HelloFlask</a>
        </small>
    {% endblock %}
</footer>
{% block scripts %}{% endblock %}
</body>
</html>

我们需要在基模板中定义块(block),在子模板中可以通过定义同名的块来执行继承操作。

块的开始和结束分别使用blockendblock标签声明,而且块之间可以嵌套。
比如:

{% block styles %}

{% endblock %}

编写子模板

template/templates/index.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=1) }}</li>
    <li><a href="{{ url_for('watchlist_with_static') }}">Watchlist with image and styles.</a></li>
    <li><a href="{{ url_for('just_flash') }}">Flash something</a></li>
</ul>
{% endblock %}

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

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

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

模板进阶实践

空白控制

如果你想在渲染时自动去掉这些空行,可以在定界符内侧添加减号。比如,{%- endfor %}会移除该语句前的空白,同理,在右边的定界符内侧添加减号将移除该语句后的空白:

{% if user.bio -%}
    <i>{{ user.bio }}</i>
{% else -%}
    <i>This user has not provided a bio.</i>
{%- endif %}

加载静态文件

可以通过url_for('static', filename='avatar.jpg')获取这个文件的URL,这个函数调用生成的URL为/static/avatar.jpg,在浏览器中输入http://localhost:5000/static/avatar.jpg即可访问这个图片。

<img src="{{ url_for('static', filename='avatar.jpg') }}" width="50"> {{ user.username }}

创建了一个存储CSS规则的styles.css文件,我们使用下面的方式在模板中加载这个文件:

<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css' ) }}

添加Favicon

<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">

使用CSS框架

{% block styles %}
 <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/bootstrap.min.css' ) }}
{% endblock %}

使用宏加载静态资源

template/templates/macros.html:用于加载静态资源的宏

{% macro static_file(type, filename_or_url, local=True) %}
    {% if local -%}
        {% set filename_or_url = url_for('static', filename=filename_or_url) %}
    {%- endif %}
    {% if type == 'css' -%}
        <link rel="stylesheet" href="{{ filename_or_url }}" type="text/css">
    {%- elif type == 'js' -%}
        <script type="text/javascript" src="{{ filename_or_url }}"></script>
    {%- elif type == 'icon' -%}
        <link rel="icon" href="{{ filename_or_url }}">
    {%- endif %}
{% endmacro %}

在模板中导入宏后,只需在调用时传入静态资源的类别和文件路径就会获得完整的资源加载语句。使用它加载CSS文件的示例如下:

static_file('css', 'css/bootstrap.min.css')

消息闪现

使用功能flash()函数发送的消息会存储在session中,我们需要在模板中使用全局函数get_flashed_messages()获取消息并将其显示出来。

from flask import Flask,render_template,flash,redirect,url_for

app = Flask(__name__)

app.secret_key = 'hello,kevin'

@app.route('/flash')
def just_flash():
    flash('I am flash, who is looking for me?')

templates/base.html:渲染flash消息

<main>
    {% for message in get_flashed_messages() %}
        <div class="alert">{{ message }}</div>
    {% endfor %}
    {% block content %}{% endblock %}
</main>

同一个页面可能包含多条要显示的消息,所以这里使用for循环迭代get_flashed_message()返回的消息列表。

get_flashed_message()函数被调用时,session中存储的所有消息都会被移除。

自定义错误页面

template/templates/errors/404.html:404页面模板

{% extends 'base.html' %}

{% block title %}404 - Page Not Found{% endblock %}

{% block content %}
<h1>Page Not Found</h1>
<p>You are lost...</p>
{% endblock %}

错误处理函数需要附加app.errorhandler()装饰器,并传入错误状态码作为参数。错误处理函数本身则需要接收异常类作为参数,并在返回值中注明对应的HTTP状态码。当发生错误时,对应的错误处理函数会被调用,它的返回值会作为错误响应的主体。

template/app.py:404错误处理器

@app.errorhandler(404)
def page_not_found(e):
    return render_template('errors/404.html'), 404

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!