web表单是web应用程序的基本功能。
它是HTML页面中负责数据采集的部件。表单有三个部分组成:表单标签、表单域、表单按钮。表单允许用户输入数据,负责HTML页面数据采集,通过表单将用户输入的数据提交给服务器。
无论前端是否校验了表单数据的安全性合法性,但是后端也是需要有一套单独的校验逻辑,因为如果有人不是通过你前端页面给后端发数据,后端还没有校验就是很麻烦的事情,在Flask中有处理表单的插件,非常简便,就是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 |
下拉列表 |
SelectMultipleField |
下拉列表,可选择多个值 |
FileField |
文本上传字段 |
SubmitField |
表单提交按钮 |
FormField |
把表单作为字段嵌入另一个表单 |
FieldList |
一组指定类型的字段 |
WTForms常用验证函数
验证函数 |
说明 |
---|---|
DataRequired |
确保字段中有数据 |
EqualTo |
比较两个字段的值,常用于比较两次密码输入 |
Length |
验证输入的字符串长度 |
NumberRange |
验证输入的值在数字范围内 |
URL |
验证URL |
AnyOf |
验证输入值在可选列表中 |
NoneOf |
验证输入值不在可选列表中 |
注意:使用Flask-WTF需要配置参数SECRET_KEY。CSRF_ENABLED是为了CSRF(跨站请求伪造)保护。 SECRET_KEY用来生成加密令牌,当CSRF激活的时候,该设置会根据设置的密匙生成加密令牌。
接下来通过一个简单的案例,来使用这个WTF,创建一个wtf_demo.py的文件
使用流程:首先定义一个表单的类,继承自FlaskForm在这个类,在类中创建字段,分别导入需要的字段的类[比如用户名导入StringField,密码导入PasswordField...根据自己的需求导入就好],准备就基本完成,接下来定义一个视图,在视图中创建表单对象,也就是你刚才创建的类对象,完成后返回你指定的模板文件,和表单对象即可,这里我们创建的表单对象为form
1 # coding:utf-8 2 from flask import Flask, render_template, redirect, url_for, session 3 from flask_wtf import FlaskForm 4 from wtforms import StringField, PasswordField, SubmitField 5 from wtforms.validators import DataRequired, EqualTo 6 7 import sys 8 reload(sys) 9 sys.setdefaultencoding('utf8') 10 11 app = Flask(__name__) 12 app.config['SECRET_KEY'] = 'asd' 13 14 15 # 定义登陆表单 16 class RegisterForm(FlaskForm): 17 # 创建需要提交的字段 18 # validators 是验证器 Datarequired 是不能为空 EqualTo是和谁相等 19 username = StringField(label=u"用户名", validators=[DataRequired(u'用户名不能为空')]) 20 password = PasswordField(label=u'密码', validators=[DataRequired(u'密码不能为空')]) 21 password2 = PasswordField(label=u'确认密码', validators=[DataRequired(u'确认密码不能为空'), EqualTo('password', u'两次密码不一致')]) 22 23 submit = SubmitField(label='提交') 24 25 26 @app.route('/register', methods=['post', 'get']) 27 def register(): 28 # 创建表单对象, 如果是post请求,前端发送了数据,flask会把数据在构造form对象的时候,存放到对象中,所以不用验证是get请求,还是post请求 29 form = RegisterForm() 30 31 # 判断form中的数据是否合法 32 # 如果form中的数据完全满足所有的验证器,则返回真,否则返回假 33 if form.validate_on_submit(): 34 # 表示前端发送的数据合法 35 # 获取前端发送的数据 36 uname = form.username.data 37 pw = form.password.data 38 pw2 = form.password2.data 39 print(uname, pw, pw2) 40 # 添加到session中 41 session['username'] = uname 42 # 跳转到index页面 43 return redirect(url_for("index")) 44 return render_template('register.html', form=form) 45 46 47 @app.route('/index') 48 def index(): 49 # 将登陆时候设置的session取出 50 username = session.get('username') 51 return "%s" % username 52 53 54 if __name__ == '__main__': 55 app.run(debug=True)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form method="post"> 9 {{ form.csrf_token }} 10 11 {{ form.username.label }} 12 <p>{{ form.username }}</p> 13 <div> 14 {% for msg in form.username.errors %} 15 <p>{{ msg }}</p> 16 {% endfor %} 17 </div> 18 {{ form.password.label }} 19 <p>{{ form.password }}</p> 20 {% for msg in form.password.errors %} 21 <p>{{ msg }}</p> 22 {% endfor %} 23 24 {{ form.password2.label }} 25 <p>{{ form.password2 }}</p> 26 {% for msg in form.password2.errors %} 27 <p>{{ msg }}</p> 28 {% endfor %} 29 30 {{ form.submit }} 31 </form> 32 </body> 33 </html>
注意:这里一定要记得设置 SECRET_KEY, 后面的值自己定义或者使用Python生成都可以,也需要在HTML页面中,设置csrf_token
展示结果
宏、继承、包含:
类似于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)}}
简单的通过一个示例展示一下到底如何使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>title</title> </head> <body> {# 不带参数 #} {% macro input() %} <input type="text" value="" size="30"> {% endmacro %} <h1>input 1</h1> {{ input() }} <h1>input 2</h1> {{ input() }} {# 带参数 #} {% macro input2(type, value, size) %} <input type="{{ type }}" value="{{ value }}" size="{{ size }}"> {% endmacro %} <h1>input2 1</h1> {{ input2("password", "", 50) }} <h1>input2 2</h1> {{ input2("password", "", 10) }} </body> </html>
# coding:utf-8 from flask import Flask, render_template app = Flask(__name__) @app.route("/index") def index(): return render_template("index.html") if __name__ == '__main__': app.run(host='0.0.0.0', debug=True)
这样就可以非常方便的复用我们需要的代码。还有一点要说明,就是带默认参数也是可以的
{# 带默认参数 #} {% macro input3(type="text", value="", size="30") %} <input type="{{ type }}" value="{{ value }}" size="{{ size }}"> {% endmacro %} <h1>input3 1</h1> {{ input3() }} <h1>input3 2</h1> {{ input3(type='password') }}
当然在上面我们也看到了jinja2的说明中,是支持宏,还有一句话就是也可以导入宏,下面我们来做一下展示
首先我们需要定一个文件,来管理我们要定义的宏,这里创建一个macro_input.html
{% macro input4(type="text", value="", size="44") %} <input type="{{ type }}" value="{{ value }}" size="{{ size }}"> {% endmacro %}
在模板index.html 中使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>title</title> </head> <body> ... {# 导入宏 #} {% import "macro_input.html" as input_func %} <h1>input4 1</h1> {{ input_func.input4() }} </body> </html>
展示效果
模板继承:
模板继承是为了重用模板中的公共内容。一般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嵌套时,阅读性更好。
-
包含(Include)
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中的特殊变量和方法:在Flask中,有一些特殊的变量和方法是可以在模板文件中直接访问的。
config 对象:
{# config 对象就是Flask的config对象,也就是 app.config 对象。#}
{{ config.SQLALCHEMY_DATABASE_URI }}
request 对象:就是 Flask 中表示当前请求的 request 对象,request对象中保存了一次HTTP请求的一切信息。
request常用的属性如下:
属性 | 说明 | 类型 |
---|---|---|
data | 记录请求的数据,并转换为字符串 | * |
form | 记录请求中的表单数据 | MultiDict |
args | 记录请求中的查询参数 | MultiDict |
cookies | 记录请求中的cookie信息 | Dict |
headers | 记录请求中的报文头 | EnvironHeaders |
method | 记录请求使用的HTTP方法 | GET/POST |
url | 记录请求的URL地址 | string |
files | 记录请求上传的文件 | * |
使用
{{ request.url }}
url_for 方法:url_for() 会返回传入的路由函数对应的URL,所谓路由函数就是被 app.route() 路由装饰器装饰的函数。如果我们定义的路由函数是带有参数的,则可以将这些参数作为命名参数传入。
{{ url_for('index') }}
{{ url_for('post', post_id=1024) }}
get_flashed_messages方法:返回之前在Flask中通过 flash() 传入的信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出。
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}