05.Flask之Jinja2模板(二)

控制代码块

控制代码块主要包含两个:

- if/else if /else / endif
- for / endfor

if语句

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

{% if my_int > 18 %}
    传入数据大于18
{% elif my_int < 18 %}
    传入数据小于18
{% else %}
    传入数据等于18
{% endif %}

过滤器可以被用在 if 语句中:

{% if my_array | length > 3 %}
    列表中有{{ my_array | length }}个数据
{% else %}
    列表元素过少
{% endif %}

循环

  • 我们可以在 Jinja2 中使用循环来迭代任何列表或者生成器函数
{% for i in my_array %}
        <p>{{ i }}</p>
{% 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循环的一些信息

{% for i in my_array %}
        <p>{{ loop.index }},{{ i }}</p>
        <p>{{ loop.length }}</p>
{% endfor %}

示例程序:

准备数据:

# 只显示4行数据,背景颜色依次为:黄,绿,红,紫
my_list = [
    {
        "id": 1,
        "value": "我爱工作"
    },
    {
        "id": 2,
        "value": "工作使人快乐"
    },
    {
        "id": 3,
        "value": "沉迷于工作无法自拔"
    },
    {
        "id": 4,
        "value": "日渐消瘦"
    },
    {
        "id": 5,
        "value": "以梦为马,越骑越傻"
    }
]
View Code

模板代码:

{% for item in my_list if item.id != 5 %}
    {% if loop.index == 1 %}
        <li style="background-color: orange">{{ item.value }}</li>
    {% elif loop.index == 2 %}
        <li style="background-color: green">{{ item.value }}</li>
    {% elif loop.index == 3 %}
        <li style="background-color: red">{{ item.value }}</li>
    {% else %}
        <li style="background-color: purple">{{ item.value }}</li>
    {% endif %}
{% endfor %}
View Code

模板代码复用

对宏(macro)的理解:

  • 把它看作 Jinja2 中的一个函数,它会返回一个模板或者 HTML 字符串
  • 为了避免反复地编写同样的模板代码,出现代码冗余,可以把他们写成函数以进行重用
  • 需要在多处重复使用的模板代码片段可以写入单独的文件,再包含在所有模板中,以避免重复

代码示例:

第一步

视图相关配置

@app.route('/')
def hello_world():
    return render_template("login.html")

 

第二步

在templates中新建login.html文件

templates -》 login.html

<form>
    <label>用户名:</label><input type="text" name="username"><br/>
    <label>身份证号:</label><input type="text" name="idcard"><br/>
    <label>密码:</label><input type="password" name="password"><br/>
    <label>确认密码:</label><input type="password" name="password2"><br/>
    <input type="submit" value="注册">
</form>

 

第三步

内容抽取,从前面的代码,我们可以看出啦,这些内容很多地方都是一样的,只有前面的文字,name或者type是不一样的。发现了共同点,我们就可以抽取代码了。

我们根据抽取内容来定义宏

templates -》 login.html

{#定义宏,相当于定义一个函数,在使用的时候直接调用该宏,传入不同的参数就可以了#}
{% macro input(label="", type="text", name="", value="") %}
<label>{{ label }}</label><input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}

 

第四步

根据抽取的宏,重写构造表单

templates -》 login.html

{#定义宏,相当于定义一个函数,在使用的时候直接调用该宏,传入不同的参数就可以了#}
{% macro input(label="", type="text", name="", value="") %}
<label>{{ label }}</label><input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}
<form> {{ input("用户名:", name="username") }}<br/> {{ input("身份证号:", name="idcard") }}<br/> {{ input("密码:", type="password", name="password") }}<br/> {{ input("确认密码:", type="password", name="password2") }}<br/> {{ input(type="submit", value="注册") }} </form>

 

第五步

用文件来存储宏文件,然后导入宏

在templates中新建一个macro.html的文件,专门用来存放宏文件。

templates -》macro.html

{#定义宏,相当于定义一个函数,在使用的时候直接调用该宏,传入不同的参数就可以了#}
{% macro input(label="", type="text", name="", value="") %}
<label>{{ label }}</label><input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}

 

第六步

使用宏文件来定制表单

templates -》 login.html

{% import "macro.html" as func %}
<form>
    {{ func.input("用户名:", name="username") }}<br/>
    {{ func.input("身份证号:", name="idcard") }}<br/>
    {{ func.input("密码:", type="password", name="password") }}<br/>
    {{ func.input("确认密码:", type="password", name="password2") }}<br/>
    {{ func.input(type="submit", value="注册") }}
</form>

 

模板继承

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

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

父模板

  • base.html
{% block top %}
  顶部菜单
{% endblock top %}

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

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

子模板

  • extends指令声明这个模板继承自哪
{% extends 'base.html' %}
{% block content %}
 需要填充的内容
{% endblock content %}
  • 模板继承使用时注意点:
    • 不支持多继承
    • 为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
    • 不能在一个模板文件中定义多个相同名字的block标签。
    • 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。

包含

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

  • include的使用
{% include 'hello.html' %}

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

  • include 的使用加上关键字ignore missing
{% include 'hello.html' ignore missing %}

小结

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

 

模板中特有的变量和函数

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

请求上下文中的相关内容获取

request

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

{{ request.url }}<br>
{{ request.method }}

 

session

为Flask的session对象

{{ session.username }}

想要获取session,首先,要先设置secret_key,然后再给session设置相关键值对。

from flask import Flask, render_template, session

app = Flask(__name__)
app.secret_key = "dkjflasdflasdjflkwej"

@app.route('/')
def hello_world():
    session["username"] = "laowang"
    return render_template("login.html")


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

 

应用上下文中的相关内容获取

config

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

{{ config["DEBUG"] }}
{{ config.DEBUG }}

 

g变量

在视图函数中设置g变量的 name 属性的值

@app.route('/')
def hello_world():
    g.name = "xiaohua"
    return render_template("login.html")

然后在模板中直接可以取出

{{ g.name }} 

两个可以直接使用的函数

url_for()

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

示例:

定义两个视图函数

@app.route('/')
def hello_world():
    return render_template("login.html")

@app.route("/demo1")
def demo1():
    return "demo1"

然后再templates文件夹中,新建login.html,并放入下面代码:

{{ url_for("demo1") }}
<a href="{{ url_for("demo1") }}">回到首页</a>

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

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

 

get_flashed_messages()

这个函数会返回之前在flask视图函数中通过flask()传入的消息的列表,比如:在视图函数中写入:flask("我是闪现消息"),然后在前端页面中写入下面的模板语法来接收这个消息。

{%for message in get_flashed_messages()%}
    {{message}}
{%endfor%}

flash函数的作用很简单,可以把由Python字符串表示的消息加入一个消息队列中,再使用get_flashed_message()函数取出它们并消费掉。

 

Web表单

Web 表单是 Web 应用程序的基本功能。

它是HTML页面中负责数据采集的部件。表单有三个部分组成:表单标签、表单域、表单按钮。表单允许用户输入数据,负责HTML页面数据采集,通过表单将用户输入的数据提交给服务器。

在Flask中,为了处理web表单,我们可以使用 Flask-WTF 扩展,它封装了 WTForms,并且它有验证表单数据的功能。

 

那么为什么Flask要用专门的WTF拓展来完成表单内容,而不使用原生的表单呢?

为啥会有WTF表单

我们通过一个示例来看

第一步

构造测试前端页面和视图

test.py

@app.route("/register")
def register():
    return render_template("register.html")

templates - 》register.html

<body>
注册界面
</body>

测试目前代码是否正确

 

第二步

将原生的form表单放入到前端页面当中

templates - 》register.html

<form method="post">
    <label>用户名:</label><input type="text" name="username" placeholder="请输入用户名"><br/>
    <label>密码:</label><input type="password" name="password" placeholder="请输入密码"><br/>
    <label>确认密码:</label><input type="password" name="password2" placeholder="请输入确认密码"><br/>
    <input type="submit" value="注册">
</form>

我们将修改过的代码重写运行,会发现出现错误,这是因为我们在form表单中没有写入action,所以,当我们提交数据之后,这个表单内容就会被提交的当前的路径下。而我们后端的路由中并没有设置可以接收post方式的请求,这样才导致了错误发发生,

所以,这里我们对视图函数的请求方式进行完善,并完成一些简单的数据校验:

@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")

        if not all([username, password, password2]):
            # 向前端界面弹出一条提示(闪现消息)
            flash("参数不足")
        elif password != password2:
            flash("两次密码不一致")
        else:
            # 假装做注册操作
            print(username, password, password2)
            return "success"
    return render_template("register.html")

当我们运行之后,在表单中填入相关数据,点击注册,当参数不足或者两次密码不正确就会出现下面的报错。

 

而这个报错和session有关,我们看一下flash的底层代码:

 会发现flash在底层使用session的相关技术,可以看出来,我们闪现的消息是通过session传递的。使用session,我们需要设置secret_key

app.secret_key = "djflajflajsf"

当我们再次点击注册,发现好像是成功了但是并没有任何消息提示,这是因为虽然用flash闪送了消息,但是,需要在前端页面中进行接收,所以我们在register.html中加入对闪送消息的接收。

{% for message in get_flashed_messages() %}
    {{ message }}
{% endfor %}

 

实验总结:

目前为止,我们实现了使用原生表单来完成简单的注册操作。但是,目前有些问题,第一,表单需要我们进行填写,第二,这些表单内容仍需要我们进行校验。而WTF拓展可以帮助我们完成这两部分内容。

Flask-WTF表单的使用

第一步

Flask-WTF库下载

pip install flask-wtf -i https://pypi.tuna.tsinghua.edu.cn/simple

 

第二步

根据flask-wtf创建表单

#导入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']='SECRET_KEY'

#自定义表单类,文本字段、密码字段、提交按钮
class RegisterForm(FlaskForm):
    username = StringField("用户名:", validators=[DataRequired("请输入用户名")], render_kw={"placeholder": "请输入用户名"})
    password = PasswordField("密码:", validators=[DataRequired("请输入密码")])
    password2 = PasswordField("确认密码:", validators=[DataRequired("请输入确认密码"), EqualTo("password", "两次密码不一致")])
    submit = SubmitField("注册")

@app.route("/register_wtf", methods=["GET", "POST"])
def  register_wtf():
    register_form = RegisterForm()
    return render_template("register_wtf.html", form=register_form)

WTF中首先要建立表单类,然后在需要的时候函数中实例化这个类,再将类传到前端当中进行展示。

 

第三步

在前端接收表单内容并用jinja2模板语法完成渲染。

在templates文件夹中新建register_wtf.html文件,并写入下面内容:

 

<form method="post">
    {{ form.username.label }} {{ form.username }}<br/>
    {{ form.password.label }} {{ form.password }}<br/>
    {{ form.password2.label }} {{ form.password2 }}<br/>
    {{ form.submit }}
</form>

label其实就是设置表单时的第一个参数。

 

第四步

利用WTF类中写入的相关验证,完成注册

test.py

@app.route("/register_wtf", methods=["GET", "POST"])
def  register_wtf():
    register_form = RegisterForm()
    if register_form.validate_on_submit():
        # 如果代码能走到这个地方,那么就代码表单中所有的数据都能验证成功
        username = request.form.get("username")
        password = request.form.get("password")
        password2 = request.form.get("password2")
        # 假装做注册操作
        print(username, password, password2)
        return "success"
    else:
        if request.method == "POST":
            flash("参数有误或者不完整")
    return render_template("register_wtf.html", form=register_form)

当我们运行之后,会发现当输入内容正确之后,还是没有达到我们想要的效果,所以,我们在两个判断中加入断点来看相关错误信息:

 我们发现,当输入正确信息,在错误一栏中,给出的问题是缺失csrf。

为了求证,在FlaskForm的源码中,我们可以看到,里面有关于csrf的内容:

 

 为了让测试通过,我们先对它进行一些设置

app.config['WTF_CSRF_ENABLED'] = False

  

WTForms支持的HTML标准字段

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

WTForms常用验证函数

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

使用 Flask-WTF 需要配置参数 SECRET_KEY。

posted @ 2019-10-30 09:25  苦行僧95  阅读(169)  评论(0)    收藏  举报