Flask_Jinja2模板(九)

在前面的示例中,视图函数的主要作用是生成请求的响应,这是最简单的请求。实际上,视图函数有两个作用:处理业务逻辑和返回响应内容。在大型应用中,把业务逻辑和表现内容放在一起,会增加代码的复杂度和维护成本。本节学到的模板,它的作用即是承担视图函数的另一个作用,即返回响应内容。 模板其实是一个包含响应文本的文件,其中用占位符(变量)表示动态部分,告诉模板引擎其具体值需要从使用的数据中获取。使用真实值替换变量,再返回最终得到的字符串,这个过程称为“渲染”。Flask使用Jinja2这个模板引擎来渲染模板。Jinja2能识别所有类型的变量,包括{}。 Jinja2模板引擎,Flask提供的render_template函数封装了该模板引擎,render_template函数的第一个参数是要渲染的模板的文件名,后面的参数可以是键值对,也可以是**kwargs,均能将变量对应的真实值传递给模板。。

我们先来认识下模板的基本语法:

{% if user %}
    {{ user }}
{% else %}
    hello!
<ul>
    {% for index in indexs %}
    <li> {{ index }} </li>
    {% endfor %}
</ul>

一、变量

Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 str() 方法转换为一个字符串就可以。

视图代码

from flask import Flask, render_template


app = Flask(__name__)


@app.route("/")
def index():
    """
    "index.html"表示要渲染的模板
    两种传递参数值的方式:
        1、键值对
        2、不定长字段参数**kwargs
    """
    data = {
        "name2": "李四",
        "age2": 19
    }
    my_dict = {
        "name": "王五",
        "age": 21
    }
    my_list = [1, 2, 3, 4, 5, 6, 7]
    list_index = 1
    return render_template("index.html",
                           # 键值对
                           name1="张三", age1=18,
                           # 不定长参数
                           **data,
                           # 参数值除了可以是string和int类型外,还可以是dict,list等类型,只要这个类型能被 str() 方法转换为一个字符串就可以。
                           # dict类型
                           mydict=my_dict,
                           # list类型
                           mylist=my_list,
                           index=list_index
                           )


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

模板代码

模板放在templates目录下,名字为index.html。使用 {{ 参数名 }} 的方式进行插值操作。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <!--  string 和 int取值方式  -->
    <div>{{ name1 }}:{{ age1 }}</div>
    <div>{{ name2 }}:{{ age2 }}</div>

    <!--  dict取值方式  -->
    <div>{{ mydict }}</div>
    <div>{{ mydict["name"]}}:{{ mydict.age }}</div>

    <!--  list取值方式  -->
    <div>{{ mylist }}</div>
    <div>{{ mylist[0] }}</div>
    <div>{{ mylist[index] }}</div>

</body>
</html>

渲染效果

二、过滤器

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

过滤器的使用方式为:变量名 | 过滤器。 过滤器名写在变量名后面,中间用 | 分隔。如:{{variable | capitalize}},这个过滤器的作用:把变量variable的值的首字母转换为大写,其他字母转换为小写。

字符串过滤器

safe:禁用转义html标签
<p>{{ '<em>hello</em>' | safe }}</p>

capitalize:把变量值的首字母转成大写,其余字母转小写
<p>{{ 'hello' | capitalize }}</p>

lower:把值转成小写
<p>{{ 'HELLO' | lower }}</p>

upper:把值转成大写
<p>{{ 'hello' | upper }}</p>

title:把值中的每个单词的首字母都转成大写
<p>{{ 'hello' | title }}</p>

reverse:字符串反转
<p>{{ 'olleh' | reverse }}</p>

format:格式化输出
<p>{{ '%s is %d' | format('name',17) }}</p>

striptags:渲染之前把值中所有的HTML标签都删掉
<p>{{ '<em>hello</em>' | striptags }}</p>

truncate: 字符串截断
<p>{{ 'hello every one' | truncate(9)}}</p>

列表过滤器

irst:取第一个元素
<p>{{ [1,2,3,4,5,6] | first }}</p>

last:取最后一个元素
<p>{{ [1,2,3,4,5,6] | last }}</p>

length:获取列表长度
<p>{{ [1,2,3,4,5,6] | length }}</p>

sum:列表求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>

sort:列表排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>

语句块过滤(不常用)

{% filter upper %}
    this is a Flask Jinja2 introduction
{% endfilter %}

自定义过滤器

过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。

自定义过滤器有两种实现方式:

  • 使用Flask应用对象的add_template_filter方法
  • 使用Flask应用对象的template_filter装饰器

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

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

# 间隔截取列表
def filter_double_sort(ls):
    return ls[::2]


# 第一个参数为过滤器函数, 
# 第二个参数为模板中使用的过滤器名字)
app.add_template_filter(filter_double_sort, 'double_2')

实现方式二:使用Flask应用对象的template_filter装饰器实现自定义过滤器。装饰器传入的参数是自定义的过滤器名称。

# 装饰器中的参数为模板中使用的过滤器名字
@app.template_filter('db2')
def filter_double_sort(ls):
    return ls[::2]

使用自定义过滤器

<p>add_template_filter函数:{{ mylist | double_2 }}</p>
<p>template_filter装饰器:{{ mylist | db2}}</p>

三、Flask-WTF表单扩展

在介绍Flask-WTF表单扩展前,我们先不使用Flask-WTF简单实现一个注册表单

模板文件如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- form标签不使用action属性时,发送的请求被当前视图函数接收 -->
<form method='post'>
    <div><input type="text" name="username" placeholder='用户名'></div>
    <div><input type="password" name="password" placeholder='密码'></div>
    <div><input type="password" name="password2" placeholder='确认密码'></div>
    <input type="submit">
</form>
</body>
</html>

视图函数如下:

from flask import Flask, render_template, request, session, redirect, url_for


app = Flask(__name__)
app.config["SECRET_KEY"] = "asd"


@app.route("/login/")
def login():
    return F"{session.get('user')} 注册成功"


@app.route("/register/", methods=["get", "post"])
def register():
    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")
        password2 = request.form.get("password2")
        session["user"] = username
        return redirect(url_for("login"))
    else:
        return render_template("register.html")


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

测试效果:

上图中,即使密码与确认密码不一致,但仍能注册成功,这是因为我们没有在视图中验证表单的数据,为了保证参数的合规性,我们需要对每个使用if来进行校验,这是很繁琐的。

而Flask-WTF扩展不仅可以帮助我们在视图中验证表单的数据,还可以使用该扩展进行CSRF验证,帮助我们快速定义表单模板。

使用Flask-WTF表单扩展,需要自行安装,安装命令如下:

pip install flask-wtf

并且还需要配置参数SECRET_KEY,当CSRF(跨站请求伪造)保护激活的时候,CSRF_ENABLED设置会根据设置的密匙生成加密令牌。

接下来,我们使用Flask-WTF实现表单。

模板文件如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post">
    <!--  设置csrf_token  -->
    {{ form.csrf_token }}

        {{ form.user_name.label }}
        <p>{{form.user_name}}</p>
        {% for msg in form.user_name.errors %}
        <p>{{msg}}</p>
        {% endfor %}

        {{ form.password.label }}
        <p>{{form.password}}</p>
        {% for msg in form.password.errors %}
        <p>{{msg}}</p>
        {% endfor %}

        {{ form.password2.label }}
        <p>{{form.password2}}</p>
        {% for msg in form.password2.errors %}
        <p>{{msg}}</p>
        {% endfor %}

        {{form.submit}}
 </form>
</body>
</html>

视图函数如下:

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

# 导入wtf扩展的表单类
from flask_wtf import FlaskForm
# 导入自定义表单需要的字段
from wtforms import SubmitField, StringField, PasswordField
# 导入wtf扩展提供的表单验证器的验证函数
from wtforms.validators import DataRequired, EqualTo

app = Flask(__name__)
app.config['SECRET_KEY'] = 'asd'


@app.route("/login/")
def login():
    return F"{session.get('user')} 注册成功"


# 定义注册表单类,该类继承FlaskForm
class RegisterForm(FlaskForm):
    # 字段类型:
        # StringField 对应 type="text"
        # PasswordField 对应 type="password"
        # SubmitField 对应 type="submit"
    # 字段参数说明:
        # label:字段的名称
        # validators:验证器
            # DataRequired(message):验证数据不能为空。message为校验不通过的提示
            # EqualTo(fieldname, message):验证与指定字段fieldname的值相等,message为校验不通过的提示。
    user_name = StringField(label=u"用户名", validators=[DataRequired(u"用户名不能为空")])
    password = PasswordField(label=u"密码", validators=[DataRequired(u"密码不能为空")])
    password2 = PasswordField(label=u"确认密码", validators=[DataRequired(u"确认密码不能为空"),
                                                         EqualTo("password", u"两次密码不一致")])
    submit = SubmitField(label=u"提交")


# 定义根路由视图函数,生成表单对象,获取表单数据,进行表单数据验证
@app.route('/register/', methods=['GET', 'POST'])
def register():
    # 创建表单对象, 如果是post请求,前端发送了数据,flask会把数据在构造form对象的时候,存放到对象中
    form = RegisterForm()

    # 判断form中的数据是否合理
    # 如果form中的数据完全满足所有的验证器,则返回True,否则返回False
    if form.validate_on_submit():
        # 表示验证合格
        # 提取数据
        uname = form.user_name.data
        pwd = form.password.data
        pwd2 = form.password2.data
        print(uname, pwd, pwd2)
        session["user"] = uname
        return redirect(url_for("login"))
    # 如果校验为False,则返回模板数据
    return render_template("register.html", form=form)


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

测试效果:

WTForms支持的HTML标准字段

字段对象说明
StringField 文本字段
TextAreaField 多行文本字段
PasswordField 密码文本字段
HiddenField 隐藏文本字段
DateField 文本字段,值为datetime.date格式
DateTimeField 文本字段,值为datetime.datetime格式
IntegerField 文本字段,值为整数
DecimalField 文本字段,值为decimal.Decimal
FloatField 文本字段,值为浮点数
BooleanField 复选框,值为True和False
RadioField 一组单选框
SelectField 下拉列表
SelectMultipleField 下拉列表,可选择多个值
FileField 文本上传字段
SubmitField 表单提交按钮
FormField 把表单作为字段嵌入另一个表单
FieldList 一组指定类型的字段

WTForms常用验证函数

验证函数说明
DataRequired 确保字段中有数据
EqualTo 比较两个字段的值,常用于比较两次密码输入
Length 验证输入的字符串长度
NumberRange 验证输入的值在数字范围内
URL 验证URL
AnyOf 验证输入值在可选列表中
NoneOf 验证输入值不在可选列表中

四、控制语句

模板中的if控制语句

@app.route('/user')
def user():
    user = 'flsk'
    return render_template('user.html',user=user)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
     {% if user %}
        <h1> hello {{user}} </h1>
    {% else %}
         <h1> welcome to flask </h1>        
    {% endif %}
</body>
</html>

模板中的for循环语句

 @app.route('/loop')
 def loop():
    fruit = ['apple','orange','pear','grape']
    return render_template('loop.html',fruit=fruit)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        {% for index in fruit %}
            <li>{{ index }}</li>
        {% endfor %}
    </ul>
</body>
</html>

五、宏、继承、包含

5.1 宏

类似于python中的函数,宏的作用就是在模板中重复利用代码,避免代码冗余。

Jinja2支持宏,还可以导入宏,需要在多处重复使用的模板代码片段可以写入单独的文件,再包含在所有模板中,以避免重复。

定义宏

{% macro input() %}
  <input type="text"
         name="username"
         value=""
         size="30"/>
{% endmacro %}

调用宏

{{ input() }}

定义带参数的宏

{% macro input(name,value='',type='text',size=20) %}
    <input type="{{ type }}"
           name="{{ name }}"
           value="{{ value }}"
           size="{{ size }}"/>
{% endmacro %}

调用宏,并传递参数

{{ input(value='name',type='password',size=40)}}

把宏单独抽取出来,封装成html文件,其它模板中导入使用

文件名可以自定义,比如:定义macro.html,文件内代码如下

{% macro function() %}
    <input type="text" name="username" placeholde="Username">
    <input type="password" name="password" placeholde="Password">
    <input type="submit">
{% endmacro %}

在其它模板文件中先导入,再调用

{% import 'macro.html' as func %}
{% func.function() %}

5.2 继承

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

{% block top %}``{% endblock %}标签定义的内容,相当于在父模板中挖个坑,当子模板继承父模板时,可以进行填充。

子模板使用extends指令声明这个模板继承自哪?父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()。

父模板:base.html

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

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

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

子模板:

  {% extends 'base.html' %}
  {% block content %}
   需要填充的内容
  {% endblock content %}

模板继承使用时注意点:

  • 不支持多继承。
  • 为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
  • 不能在一个模板文件中定义多个相同名字的block标签。
  • 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。

5.3 包含(Include)

Jinja2模板中,除了宏和继承,还支持一种代码重用的功能,叫包含(Include)。它的功能是将另一个模板整个加载到当前模板中,并直接渲染。

示例:include的使用

{\% include 'hello.html' %}

包含在使用时,如果包含的模板文件不存在时,程序会抛出TemplateNotFound异常,可以加上ignore missing关键字。如果包含的模板文件不存在,会忽略这条include语句。

示例:include的使用加上关键字ignore missing

{\% include 'hello.html' ignore missing %}

5.4 宏、继承、包含

  • 宏(Macro)、继承(Block)、包含(include)均能实现代码的复用。
  • 继承(Block)的本质是代码替换,一般用来实现多个页面中重复不变的区域。
  • 宏(Macro)的功能类似函数,可以传入参数,需要定义、调用。
  • 包含(include)是直接将目标模板文件整个渲染出来。
原文地址:https://www.cnblogs.com/testlearn/p/14113160.html