Flask之Jinja2模板引擎


Flask内置的模板语言,它的设计思想来源于 Django 的模板引擎DTP(Django Template),并扩展了其语法和一系列强大的功能。

渲染模版函数

  • Flask提供的 render_template 函数封装了该模板引擎
  • render_template 函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值。

模板的基本使用

  1. 在flask应用对象创建的时候,设置或者保留template_folder参数,创建模板目录

    app = Flask(__name__,template_folder='templates')
    
  2. 在项目下创建 templates 文件夹,用于存放所有的模板文件,并在目录下创建一个模板html文件 templates/index.html

    
    
    
        
        {{ Title }}
    
    
        

    {{ msg }}

  3. 在视图函数设置渲染模板并设置模板数据

    run.py

    from flask import Flask, render_template, render_template_string
    from flask_script import Manager
    
    
    app = Flask(import_name=__name__, template_folder='templates')
    manager = Manager(app)
    
    
    # 编写路由视图
    @app.route(rule='/')
    def index():
        data = {}
        data["title"] = "我的flask项目"
        data["msg"] = "hello jinja2"
        return render_template("index.html", **data)
        # return render_template_string(open("templates/index.html"), **data)
    
    
    if __name__ == '__main__':
        # 运行flask
        manager.run()
    

输出变量

{{ }} 来表示变量名,这种 {{ }} 语法叫做 变量代码块

# 使用 {# #} 进行注释,注释的内容不会在html中被渲染出来
{# {{ name }} #}

run.py

from flask import Flask, render_template, render_template_string

app = Flask(__name__)


@app.route("/")
def index():
    """基本加载模板"""
    data = {}
    data["msg"] = "hello jinja2"
    data["title"] = "我的网页标题"
    # 根据模板路径来生成html ---> render_template("模板路径",**data)
    # ret = render_template("index.html", **data)

    content = """
    
    
    
        
        {{ Title }}
    
    
        

{{ msg }}

""" # 根据模板内容来生成html ---> render_template_string("html模板代码",**data) ret = render_template_string(content, **data) print(ret) """打印效果:

hello jinja2

""" print(type(ret)) # return ret if __name__ == '__main__': app.run(debug=True)

template\index.html




    
    {{ Title }}


    

{{ msg }}

访问127.0.0.1:500

Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 __str__ 方法或者str()转换为一个字符串就可以,比如,可以通过下面的方式显示一个字典或者列表中的某个元素:

index.html




    
    {{title}}


    
{{title}}
{{data_list}}
{{data_list[0]}}
{{data_list.0}}
{{data_list[-1]}}
{{data_dict}}
{{data_dict['name']}}
{{data_dict.name}}

settings\dev.py

# 声明一个配置类
class Config(object):
    SECRET_KEY = "DD434O7HQ2131!@#edn#hu!@!g@uWO1NS"

run.py

from flask import Flask, render_template


app = Flask(__name__, template_folder='templates')


@app.route("/")
def index():
    data = {}
    data["title"] = "我的项目"
    data["data_list"] = ["a", "b", "c"]
    data["data_dict"] = {
        "name": "pure",
        "id": 3417,
    }
    # return render_template("index.html",
    #                        title="我的flask项目",
    #                        data_list=data_list,
    #                        data_dict=data_dict
    #                        )
    return render_template("index.html", **data)


if __name__ == '__main__':
    manage.run()

访问127.0.0.1:500

模板中特有的变量和函数

你可以在自己的模板中访问一些 Flask 默认内置的函数和对象

config

你可以从模板中直接访问Flask当前的config对象:

{{config.SQLALCHEMY_DATABASE_URI}}
sqlite:///database.db

request

就是flask中代表当前请求的request对象:

{{request.url}}
http://127.0.0.1

session

为Flask的session对象,显示session数据

{{session.new}}
True

g变量

在视图函数中设置g变量的 name 属性的值,然后在模板中直接可以取出

{{ g.name }}

url_for()

url_for会根据传入的路由器函数名,返回该路由对应的URL,在模板中始终使用url_for()就可以安全的修改路由绑定的URL,则不比担心模板中渲染出错的链接:

{{url_for('home')}}

如果我们定义的路由URL是带有参数的,则可以把它们作为关键字参数传入url_for(),Flask会把他们填充进最终生成的URL中:

{{ url_for('index', post_id=1)}}
/1

新增一个接口代码如下:

@app.route("/index2")
def index02():
    data = {}
    data["book_list"] = [
        {"id": 1, "title": "python从入门到精通", "price": 80},
        {"id": 2, "title": "php从入门到精通", "price": 90},
        {"id": 3, "title": "go从入门到精通", "price": 100},
    ]
    data["title"] = "网页标题"
    return render_template("index2.html", **data)

新增一个html文件代码如下templates\index2.html




    
    {{ title }}


输出变量:

{{ title }}

{# 模板注释 #} {# 中括号的方式也可以,但是性能会降低,不推荐使用 #}

输出列表或字典的成员或键值对:

{{ book_list[1]["id"] }}--------{{ book_list[1]["title"] }}------{{ book_list[1]["price"] }}

{{ book_list.0.id }}--------{{ book_list.0.title }}------{{ book_list.0.price }}

输出项目的配置信息:

{{ config.DEBUG }}

获取请求对象中的数据:

{{request.url}}

uname = {{ request.args.get("uname") }}

输出session对象:

{{session.new}}

输出g变量:

{{ g.name }}

访问127.0.0.1:5000/index2

流程控制

{% %} 定义的控制代码块,可以实现一些语言层次的功能,比如:

  • if / elif /else / endif if语句
  • for / endfor 循环

if语句

Jinja2 语法中的if语句跟 Python 中的 if 语句相似,后面的布尔值或返回布尔值的表达式将决定代码中的哪个流程会被执行。

index03.html代码




    
    Title


    

判断

{# if #} {% if request.args.name %}

欢迎回来,{{request.args.name}}

{% endif %} {# if/else #} {% if request.args.name %}

欢迎回来,{{request.args.name}}

{% else %}

对不起,您尚未登录

{% endif %} {# if/elif/else #} {% if request.args.name=="root" %}

欢迎回来,您是当前网站的超级管理员~

{% elif request.args.name %}

尊敬的用户{{request.args.name}},欢迎回来

{% else %}

对不起,您尚未登录

{% endif %}

run.py代码

from flask import Flask,render_template,request


app = Flask(__name__,template_folder='templates')


@app.route("/")
def index():
    return "hello"


if __name__ == '__main__':
    manage.run()

for循环

  • 我们可以在 Jinja2 中使用循环来迭代任何列表或者生成器函数
{% for post in posts %}
    

{{ post.title }}

{{ post.text | safe }}

{% endfor %}
  • 循环和if语句可以组合使用,以模拟 Python 循环中的 continue 功能,下面这个循环将只会渲染post.text不为None的那些post:
{% for post in posts if post.text %}
    

{{ post.title }}

{{ post.text | safe }}

{% endfor %}
  • 在一个 for 循环块中你可以访问这些特殊的变量:
变量 描述
loop.index 当前循环迭代的次数(从 1 开始)
loop.index0 当前循环迭代的次数(从 0 开始)
loop.revindex 到循环结束需要迭代的次数(从 1 开始)
loop.revindex0 到循环结束需要迭代的次数(从 0 开始)
loop.first 如果是第一次迭代,为 True 。
loop.last 如果是最后一次迭代,为 True 。
loop.length 序列中的项目数。
loop.cycle 在一串序列间期取值的辅助函数。见下面示例程序。
  • 在循环内部,你可以使用一个叫做loop的特殊变量来获得关于for循环的一些信息
    • 比如:要是我们想知道当前被迭代的元素序号,并模拟Python中的enumerate函数做的事情,则可以使用loop变量的index属性,例如:
{% for post in posts%}
{{loop.index}}, {{post.title}}
{% endfor %}

会输出这样的结果

1, Post title
2, Second Post
  • cycle函数会在每次循环的时候,返回其参数中的下一个元素,可以拿上面的例子来说明:
{% for post in posts%}
	{{loop.cycle('odd','even')}} {{post.title}}
{% endfor %}

会输出这样的结果:

odd Post Title
even Second Post

案例:

index04.html




    
    {{ title }}



    

循环

{% for book in book_list %} {% if loop.index %2 == 0 %} {% else %} {% endif %} {% endfor %}
序号 ID price title
{{ loop.index }} {{ book.id }} {{ book.price }} {{ book.title }}

run.py

from flask import Flask, render_template, render_template_string, g, request

app = Flask(__name__)


@app.route("/index04")
def index04():
    data = {}
    data["title"] = "我的项目"
    data["book_list"] = [
        {"id": 10, "price": 78.50, "title": "javascript入门"},
        {"id": 21, "price": 78.50, "title": "python入门"},
        {"id": 33, "price": 78.50, "title": "django项目实战"},
        {"id": 34, "price": 78.50, "title": "django项目实战"},
        {"id": 33, "price": 78.50, "title": "django项目实战"},
    ]
    return render_template("index03.html", **data)


if __name__ == '__main__':
    app.run(debug=True)

访问http://127.0.0.1:5000/index04

过滤器

过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。

  • 过滤器的使用方式为:
{{ 变量 | 过滤器函数名(参数1,参数2,....)}}
{{ 变量 | 过滤器函数名(参数1,参数2,....) | 过滤器函数名(参数1,参数2,....) | ....}}
  • 如果没有任何参数传给过滤器,则可以把括号省略掉
{{ 变量 | 过滤器函数名 }}
  • 如:upper,这个过滤器的作用:把变量variable 的值的首字母转换为大写,其他字母转换为小写

在 jinja2 中,过滤器是可以支持链式调用的,示例如下:

{{ "hello world" | reverse | upper }}

常见的内建过滤器

字符串操作

  • safe:禁用转义

{{ 'hello' | safe }}

  • capitalize:把变量值的首字母转成大写,其余字母转小写

{{ 'hello' | capitalize }}

  • lower:把值转成小写

{{ 'HELLO' | lower }}

  • upper:把值转成大写

{{ 'hello' | upper }}

  • title:把值中的每个单词的首字母都转成大写

{{ 'hello' | title }}

  • reverse:字符串反转

{{ 'olleh' | reverse }}

  • format:格式化输出

{{ '%s is %d' | format('name',17) }}

  • striptags:渲染之前把值中所有的HTML标签都删掉

    如果内容中,存在大小于号的情况,则不要使用这个过滤器,容易误删内容。

{{ 'hello' | striptags }}

{{ "如果xx,那么x和z之间是否相等?" | striptags }}

  • truncate: 字符串截断

{{ 'hello every one' | truncate(9)}}

列表操作

  • first:取第一个元素

{{ [1,2,3,4,5,6] | first }}

  • last:取最后一个元素

{{ [1,2,3,4,5,6] | last }}

  • length:获取列表长度

{{ [1,2,3,4,5,6] | length }}

  • sum:列表求和

{{ [1,2,3,4,5,6] | sum }}

  • sort:列表排序

{{ [6,2,3,1,5,4] | sort }}

语句块过滤

{% filter upper %}
	

hello world

hello world

hello world

hello world

{% endfilter %}

自定义过滤器

过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。自定义过滤器有两种实现方式:

  • 一种是通过Flask应用对象的 add_template_filter 方法
  • 通过装饰器来实现自定义过滤器

重要:自定义的过滤器名称如果和内置的过滤器重名,会覆盖内置的过滤器。

需求:添加列表反转的过滤器

方式一

通过调用应用程序实例的 add_template_filter 方法实现自定义过滤器。该方法第一个参数是函数名,第二个参数是自定义的过滤器名称:

# 自定义过滤器
def list_reverse(data):
    # 过滤器必须有返回值,否则模板中没有内容输出!!!
    return data[::-1]

app.add_template_filter(list_reverse, "list_reverse")

方式二

用装饰器来实现自定义过滤器。装饰器传入的参数是自定义的过滤器名称。

@app.add_template_filter
def list_reverse(data):
    return data[::-1]
  • 主程序中创建和注册过滤器

run.py

from flask import Flask, render_template


app = Flask(import_name=__name__,template_folder='templates')

# 自定义过滤器
def do_list_reverse(data):
    return data[::-1]

# 注册过滤器
app.add_template_filter(do_list_reverse, "list_reverse")

@app.route(rule='/')
def index():
    data={}
    data["user_list"] = ["xiaoming","小黑白","小红"]
    return render_template("index.html",**data)


if __name__ == '__main__':
    manager.run()

index05.html




    
    title


    

{{ user_list }}

{{ user_list | list_reverse }}

{{ user_list }}

访问http://127.0.0.1:5000/

案例:给手机进行部分屏蔽

13812345678 ---> 138****5678

run.py

from flask import Flask,render_template,request


app = Flask(__name__,template_folder='templates')


@app.template_filter("mobile")
def do_mobile(data,string):
    return data[:3]+string+data[7:]


@app.route("/")
def index():
    data = {}
    data["user_list"] = [
        {"id":1,"name":"张三","mobile":"13112345678"},
        {"id":2,"name":"张三","mobile":"13112345678"},
        {"id":3,"name":"张三","mobile":"13112345678"},
        {"id":4,"name":"张三","mobile":"13112345678"},
    ]
    return render_template("index06.html",**data)

if __name__ == '__main__':
    manage.run()

index06.html,模板代码:




    
    Title



    {% for user in user_list %}
    
    {% endfor %}

ID 姓名 手机
{{ user.id }} {{ user.name }} {{ user.mobile | mobile(string="****") }}

访问http://127.0.0.1:5000/

模板继承

在模板中,可能会遇到以下情况:

  • 多个模板具有完全相同的顶部和底部内容
  • 多个模板中具有相同的模板代码内容,但是内容中部分值不一样
  • 多个模板中具有完全相同的 html 代码块内容

像遇到这种情况,可以使用 JinJa2 模板中的 继承 来进行实现

模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。

  • 标签定义的内容
{% block top %} {% endblock %}
  • 相当于在父模板中挖个坑,当子模板继承父模板时,可以进行填充。
  • 子模板使用 extends 指令声明这个模板继承自哪个模板
  • 父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()

base.html父模板代码

{% block top %}
    顶部
{% endblock top %}

{% block content %}
{% endblock content %}

{% block bottom %}
    底部
{% endblock bottom %}

son.html子模板代码:

{# extends指令声明这个模板继承自哪#}
{% extends 'base.html' %}
{% block content %}
    需要填充的内容
{% endblock content %}

run.py

from flask import Flask, render_template, request

"""创建flask应用"""
app = Flask(__name__, template_folder='templates')


# 在项目当中,过滤作用的函数,会被放到专门的过滤器文件中
@app.route("/base")
def base():
    data = {}
    return render_template("base.html", **data)


@app.route("/son")
def son():
    data = {}
    data["user_list"] = [
        {"id": 1, "name": "张三", "mobile": "13112345678"},
    ]
    return render_template("son.html", **data)


if __name__ == '__main__':
    app.run()

访问http://127.0.0.1:5000/sonhttp://127.0.0.1:5000/son

模板继承使用时注意点:

  1. 不支持多继承

  2. 为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行,而且extends必须在最上方

  3. 不能在一个模板文件中定义多个相同名字的block标签。否则报错

  4. 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。