Flask模板


在视图函数中会向客户端返回一行HTML代码,但是一个完整的HTML需要很多行代码,如果采用return的形式是非常不方便维护的。

在动态的web程序中,返回的HTML数据要根据相应的变量做出动态生成,这时我们就需要模板引擎(template engine),借助模板引擎我们就能在HTML中用特殊语法标记出变量,这类包含固定内容和动态内容的文件称为模板(template)。

模板引擎的作用是读取并执行模板中的特殊语法和标记,传入变量生成最终的HTML页面,这个过程被称为渲染(rendering)。

3.1 模板的基本用法

3.1.1创建模板

# 创建数据
user = {
    'username': "Hanjun Liu",
    'bio': "I do not love any music or movies."
}

movies = [
    {'name': "My Neighbor Totoro", 'year': '1988'},
    {'name': "CoCo", "year": '2017'},
]
<!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 provide 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>

jinja2中常见的三种定界符:

  • if,for判断
  • 字符串,变量,函数调用
  • 注释

jinja2支持使用”.”来获取变量属性,user.username相当于user[“username”]

3.1.2 模板语法

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

我们应该适度的使用模板,仅把和输出控制有关的逻辑操作放到模板中。在for循环中jinja2提供了多个特殊变量,常用的如下:

3.1.3 渲染模板

在视图函数渲染模板时并不直接使用Jinja2提供的函数而是使用Flask提供的render_template()

@app.route('/watchlist')
def watchlist():
    return render_template("watchlist.html", user=user, movies=movies)  # 传入模板+传入对象

Flask还提供了一个render_template_string()来渲染模板字符串。Jinja2中传入的变量可以使字符串,字典,列表,也可以是函数,类和类实例,传入函数时要传入函数对象本身。

3.2 辅助工具

除了基本语法,Jinja2还提供了很多方便的工具方便控制模板输出。

3.2.1 上下文

上下文中包含了很多变量,其中包含我们调用的render_template()时手动传入的变量以及Flask默认传入的变量,我们还可以在模板中自定义变量:

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

{% set navigation %}
  • Home
  • About
  • {% endset %}

    Flask在模板上下文中提供了一些内置变量,可以在模板中直接使用:

    如果很多模板都需要同一个变量,那么每次都把这个变量传入视图函数非常麻烦,最好的方法是设置一个模板全局变量,Flask提供了一个app.context_processor装饰器来注册模板上下文处理函数,这个函数需要返回一个包含变量键值对的字典。

    @app.context_processor
    def inject_foo():
        foo =  "I am foo."
        return dict(foo=foo)
    
    
    #也可以直接调用
    def inject_foo():
        foo =  "I am foo."
        return dict(foo=foo)
    app.context_processor(inject_foo)

    3.2.2 全局对象

    全局对象是指在所有模板中都可以直接使用的对象,PS上下文就是一种全局的变量,而全局对象就是全局的函数等等。

    Jinja2中内置的全局函数:

    Flask提供的全局函数:

    我们可以使用app.template_global()直接将函数注册为模板全局函数。

    @app.template_global() # name参数可以指定一个自定义的名称
    def bar():
        return "I am bar"
    
    app.template_global(bar) #同样适用

    3.2.3 过滤器

    在Jinja2中过滤器是用来修改和过滤变量值的特殊函数:

    {{ name | title }}
    {{ movies | length }}
    
    {% filter upper %}
     This text becomes uppercase.
    {% endfilter %}

    Jinja2内置的过滤器

    根据Flask的设置模板Jinjia2会自动对模板中的变量进行转义,所以我们不需要使用escape过滤器对变量进行转义。

    如果不想转义:

    {{ some_text | safe }} 

    另一种将文本标记为安全(不转义)的方法是使用Markup对象:

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

    当然我们可以自定义过滤器:

    from flask import Markup
    
    @app.template_filter()
    def musical(s):
        return s + Markup(" &#9835")
    
    app.template_filter(musical)  # 必然可以

    3.2.4 测试器

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

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

    Jinja2内置的测试器:

    自定义测试器:

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

    3.2.5 模板环境对象

    在Jinja2中渲染行为由jinja2.Environment类控制,所有的配置选项、上下文变量、全局函数、过滤器和测试器都存储在Environment上,当与Flask结合后我们并不单独创建Environment对象而是使用Flask创建的Environment对象,它存储在app.jinja_env上。

    模板中的全局函数、过滤器、测试器分别存储在Environment中的globals、filters、tests中,都是字典对象。

    我们可以直接使用这个字典对象来进行添加

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

    3.3 模板组织结构

    Jinja2还提供了一些工具在宏观上组织模板内容。

    3.3.1 局部模板

    我们不会在视图函数中直接渲染局部模板,而是插入到其他独立模板中。当多个独立模板都会使用同一块代码,我们就把这个代码抽离出来放入局部模板,比如都需要一个提示条,就把提示条抽出来放入_banner.html中, 局部模板的命名通常用下划线开始。

    {%include '_bannner.html '%}

    3.3.2 宏

    宏是Jinja2提供的特征,它类似于Python中的函数,使用宏可以把一部分代码封装到宏中,通过参数传递来构建内容和局部模板类似都是方便代码块重用。宏也要放在单独的文件中macros.html

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

    传入方法:

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

    注意import和inlcude不同,import中上下文以及传入的参数无效。虽然没办法用上下文了,但是Flask把上下文传入了全局变量app.jinja_env.globals中,可以直接在宏中使用。

    3.3.3 模板继承

    Jinja2允许定义一个及模板,把导航栏、页脚等内容放入其中,继承的字幕版在渲染的时候会自动包含这些部分。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        {% block head %}
        <meta charset="UTF-8">
    
        <title>{% block title %}Title{% endblock %}</title>
            {% block style %}{% endblock %}
    <link rel="stylesheet" href="/css/prism-tomorrow.css" type="text/css"></head>
        {% endblock %}
    <body>
    <nav>
        <ul><li><a href="{{ url_for("index") }}">Home</a></li></ul>
    </nav>
    <main>
        {% block content %}{% endblock %}
    </main>
    <fotter>
        {% block footer %}{% endblock %}
    </fotter>
    {% block scripts %}{% endblock %}
    </body>
    </html>
    ```
    
    我们需要在基模板中定义块(block), 在子模板中使用同名块来执行继承操作。
    
    定义子模板index.html
    
    ```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 %}

    在子模板中我们我们可以对父模板执行两种操作:

    • 覆盖内容,如上
    • 追加内容
    {% block style %}
    {{ super }}
        <style>
        .foo {
            color: red;
        }
        </style>
    {% endblock %}

    3.4 模板进阶实践

    3.4.1 空白控制

    Jinja2在生成HTML时会将对语句表达式,注释保留空行,不想保留空行:

    <div>
        {%if True -%}
        <p>Hello</p>
        {%- endif%}
    </div>

    或者

    app.jinja_env.trime_blocks = True
    app.jinja_env.lstrip_blocks = True

    3.4.2 加载静态文件

    <img src="{{ url_for("static", filename='avatarjpg')}}" width="50">

    css文件也是同样的调用方法

    3.4.3 消息闪现

    flash函数发送的消息会存储在session中,通过全局函数get_flashed_messages()获取消息。

        {% for message in get_flashed_messages() %}
            <div class="alert">{{ message }}</div>
        {% endfor %}
    @app.route('/flash')
    def just_flash():
        flash("I am flash, who is looking for me?")
        return redirect(url_for('index'))

    3.4.4 自定义错误页面

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

    3.4.5 JavaScript和CSS中的Jinja2

    如果把Jinja2代码写在单独的Javascript和css中,尽管你在HTML中引用了但是也不会生成HTML

    • 把css和Javascript写在<style>或是<script>中(不推进)

    文章作者: Hanjun Liu
    版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Hanjun Liu !
     上一篇
    Vander--Go并发服务器框架开发日志 Vander--Go并发服务器框架开发日志
    git flow init 基础Server首先创建在vface目录下创建iserver.go文件,存放Server的Interface package vface //定义服务器接口 type IServer interface {
    2020-02-06
    下一篇 
    Golang基础整理 Golang基础整理
    基础包每个程序都是由包构成的,程序从main包开始运行,规定包名与导入路径中最后一个元素相对应import math/rand包中的源码均以package rand开始。 在Go中如果名字是大写开头就是可以导出的,以小写字母开头是不可以导出
    2020-02-05
      目录