天天看点

Flask修炼-模板-02!

内容概述:

过滤器,

自定义过滤器,

控制代码块,

模板代码复用,

特有变量和函数,

Flask-WTF 表单,

CSRF

过滤器

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

{{ 变量名 | 过滤器 }}

如果没有任何参数传给过滤器,则可以把括号省略掉
在 jinja2 中,过滤器是支持链式调用的
常见内建过滤器

字符串操作

  • **safe:**禁用转义
<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>
           

列表操作

  • **first:**取第一个元素
<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>
           

自定义过滤器

过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。自定义过滤器有两种实现方式:
  • 一种是通过Flask应用对象的 add_template_filter 方法
  • 通过装饰器来实现自定义过滤器
重要:自定义的过滤器名称如果和内置的过滤器重名,会覆盖内置的过滤器
# 自定义过滤器
# 方式1: 装饰器的形式
# @app.template_filter('lireverse')
def do_lireverse(li):
    temp = list(li)
    temp.reverse()
    return temp


# 方式2: 直接添加过滤器
app.add_template_filter(do_lireverse, 'lireverse')

           

控制代码块

控制代码块主要包含两个:
- if/else if /else / endif
- for / endfor
           
{% 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: gray">{{ item.value }}</li>
    {% else %}
    <li style="background-color: red">{{ item.value }}</li>
    {% endif %}
{% endfor %}
           

模板代码复用

  • 宏(Macro)、继承(Block)、包含(include)均能实现代码的复用。
  • **继承(Block)**的本质是代码替换,一般用来实现多个页面中重复不变的区域。
  • **宏(Macro)**的功能类似函数,可以传入参数,需要定义、调用。
  • **包含(include)**是直接将目标模板文件整个渲染出来。
对宏(macro)的理解:
  • 把它看作 Jinja2 中的一个函数,它会返回一个模板或者 HTML 字符串
  • 为了避免反复地编写同样的模板代码,出现代码冗余,可以把他们写成函数以进行重用
  • 需要在多处重复使用的模板代码片段可以写入单独的文件,再包含在所有模板中,以避免重复
  • 定义宏
{% macro input(name,value='',type='text') %}
    <input type="{{type}}" name="{{name}}"
        value="{{value}}" class="form-control">
{% endmacro %}
           
  • 调用宏
{{ input('name' value='zs')}}
           
  • 这会输出
<input type="text" name="name"
    value="zs" class="form-control">
           
  • 把宏单独抽取出来,封装成html文件,其它模板中导入使用,文件名可以自定义macro.html
{% macro function(type='text', name='', value='') %}
<input type="{{type}}" name="{{name}}"
value="{{value}}" class="form-control">

{% endmacro %}
           
  • 在其它模板文件中先导入,再调用
{% import 'macro.html' as func %}
{% func.function() %}
           
模板继承
模板继承是为了重用模板中的公共内容。一般 Web 开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。
  • 标签定义的内容
{% block top %} {% endblock %}
           
  • 相当于在父模板中挖个坑,当子模板继承父模板时,可以进行填充。
  • 子模板使用 extends 指令声明这个模板继承自哪个模板
  • 父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()
在子模板中使用

extends

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

{% block contentBlock %}
    {{ super() }}
我是子类的内容<br/>
{% endblock %}
           
包含

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

包含在使用时,如果包含的模板文件不存在时,程序会抛出TemplateNotFound异常,可以加上

ignore missing

关键字。如果包含的模板文件不存在,会忽略这条include语句。
{% include 'includeaaa.html' ignore missing %}<br/>
{% include 'include.html' %}<br/>
           

特有变量和函数

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

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

session: 为 flask 的 session 对象

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

url_for():url_for会根据传入的路由器函数名,返回该路由对应的URL,在模板中始终使用url_for()就可以安全的修改路由绑定的URL,则不比担心模板中渲染出错的链接;如果我们定义的路由URL是带有参数的,则可以把它们作为关键字参数传入url_for(),Flask会把他们填充进最终生成的URL中

get_flashed_messages():这个函数会返回之前在flask中通过flask()传入的消息的列表,flash函数的作用很简单,可以把由Python字符串表示的消息加入一个消息队列中,再使用get_flashed_message()函数取出它们并消费掉:

config: {{ config.DEBUG }}<br/> {# 可以从模板中直接访问Flask当前的config对象 #}
<hr/>

请求上下文中两个变量:<br/>
当前路由: {{ request.url }}<br/>
session 取值: {{ session.name }}<br/>
<hr/>

应用上下文中 1 个变量<br/>
g 变量: {{ g.name }}<br/>
<hr/>

两个可以直接使用的函数:<br/>
<a href="{{ url_for('index') }}" target="_blank" rel="external nofollow" >回到首页</a><br/>
<hr/>
获取闪现消息:<br/>

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

Flask-WTF 表单

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

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

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

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。

CSRF_ENABLED是为了CSRF(跨站请求伪造)保护。 SECRET_KEY用来生成加密令牌,当CSRF激活的时候,该设置会根据设置的密匙生成加密令牌。

服务器代码
from flask import Flask, render_template, flash, request
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import InputRequired, EqualTo, DataRequired

app = Flask(__name__)
# 关闭 csrf 验证
app.config['WTF_CSRF_ENABLED'] = True
app.secret_key = 'asdfasdf'


# 自定义注册表单
class RegisterForm(FlaskForm):
    username = StringField('用户名:', validators=[InputRequired('请输入用户名')], render_kw={'placeholder': '我是占位文字'})
    password = PasswordField('密码:', validators=[InputRequired('请输入密码')])
    password2 = PasswordField('确认密码:', validators=[InputRequired('请输入确认密码'), EqualTo('password', '两次密码要一致')], )
    submit = SubmitField('注册:')


@app.route('/')
def index():
    return 'Hello World!'


@app.route('/register_wtf', methods=['GET', 'POST'])
def register_wtf():
    register_form = RegisterForm()

    # 使用 wtf 表单帮我们做验证
    if register_form.validate_on_submit():
        # 执行注册逻辑
        # 取到表单中提交上来的三个参数
        username = request.form.get("username")
        password = request.form.get("password")
        password2 = request.form.get("password2")

        # 取值方式 2
        # username = register_form.username.data

        # 假装做注册操作
        print(username, password, password2)
        return "success"
    else:
        if request.method == 'POST':
            flash('参数错误')

    return render_template('temp5_wtf.html', form=register_form)


@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('temp5_wtf.html')


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

           
模板代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<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>

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

<hr/><br/>
以下是使用 Flask_wtf 实现 <br/><br/><br/><br/>

<form method="post">
    {{ form.csrf_token() }}
    {{ form.username.label }}{{ form.username }}<br/>
    {{ form.password.label }}{{ form.password }}<br/>
    {{ form.password2.label }}{{ form.password2 }}<br/>
    {{ form.submit }}<br/>
</form>
</body>
</html>
           

CSRF

CSRF

全拼为

Cross Site Request Forgery

,译为跨站请求伪造。

CSRF

指攻击者盗用了你的身份,以你的名义发送恶意请求。

造成的问题:个人隐私泄露以及财产安全。

CSRF

流程:
  1. 客户端浏览并登录信任网站 A
  2. 验证通过,在用户处产生 A 的 cookie
  3. 用户在没有登出的情况下访问攻击网站 B
  4. B 会用一个诱惑性的按钮来让用户点击,但是这个点击会要求访问第三方网站 A ,发出一个请求
  5. 根据 B 的请求,浏览器要去访问 A 网站,访问 A 网站会默认带上之前保存的 cookie
  6. A 网站不知道这个请求是用户发出的还是 B 攻击网站发出的,但是浏览器过去的时候带上了 cookie,所以 A 会根据用户的权限处理这个请求,B 攻击网站的请求里一般会有请求,这样 B 就达到了模拟用户操作进行攻击的目的

防止 CSRF 攻击

  1. 在客户端向后端请求界面数据的时候,后端会往响应中的 cookie 中设置 csrf_token 的值
  2. 在 Form 表单中添加一个隐藏的的字段,值也是 csrf_token
  3. 在用户点击提交的时候,会带上这两个值向后台发起请求
  4. 后端接受到请求,以会以下几件事件:
    • 从 cookie中取出 csrf_token
    • 从 表单数据中取出来隐藏的 csrf_token 的值
    • 进行对比
  5. 如果比较之后两值一样,那么代表是正常的请求,如果没取到或者比较不一样,代表不是正常的请求,不执行下一步操作

继续阅读