几个编写习惯
表单属性名要和前端input框内变量名一直
尽量保持与db字段一致
flask_wtf
一个整合wtf的第三方库,其中的一些结构和设计与django相似。
使用之前
使用wtfomr组件需要设置一个秘钥,不设置就会出现下面这样的错误
RuntimeError: A secret key is required to use CSRF.
需要在 路由类中设置一个秘钥属性,
app.py下
app.config["SECRET_KEY"] = os.urandom(24) # 秘钥设置任意
使用方法
定义一个表单类,并需要继承FlaskForm
forms.py 文件内,单独写一个表单验证类提供表单验证。
from flask import session
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField,SelectMultipleField
from wtforms.validators import Regexp, DataRequired, Length, EqualTo, ValidationError
class RegisterForm(FlaskForm):
phone = StringField(render_kw={'class':"form-control"},label="手机号码", validators=[
Regexp(r'^1[3,5,7,8,9]\d{9}$', message="手机号码格式错误"),
Mobile(message="手机号已经存在"),
DataRequired("手机号码不能为空")]) #
pwd = PasswordField(label="密码", validators=[
Length(6, 32, message='长度不对'),
DataRequired("密码不能为空")])
confirm_pwd = PasswordField(validators=[
EqualTo('pwd',message="确认密码错误")])
路由处使用
@app.route('/register', methods=['GET', 'POST'], endpoint="register")
def register():
form = RegisterForm(request.form)
if form.validate():
return "success"
return f'error:{form.errors}'
# 调用的时候,从表单模块拿到验证类,然后将前端提交的form信息传入
# 然后调用生成form对象的validate方法进行验证,返回值为布尔型
# true 成功,false 失败
# 从请求对象request中取得form表单,然后调用表单对象的验证方法进行验证,
# 成功返回成功,失败则返回表单类中设置的错误信息
关于request传送请求信息的优先级
如果传送的请求信息不是通过form表单传送过来的,回默认去检索obj。
def __init__(self,
formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
# 先formdata,如果不是,则obj,如果不是,则data
# data 需要时一个字典格式的数据,比如json数据
# obj 可以时一个类,比如一些ajax请求返回的数据
表单验证类属性,方法
class RegisterForm(FlaskForm):
phone = StringField(
render_kw={'class':"form-control"},
label="手机号码",
validators=[
Regexp(r'^1[3,5,7,8,9]\d{9}$', message="手机号码格式错误"),
Mobile(message="手机号已经存在"),
DataRequired("手机号码不能为空")
]
) #
pwd = PasswordField(label="密码", validators=[
Length(6, 32, message='长度不对'),
DataRequired("密码不能为空")])
confirm_pwd = PasswordField(validators=[
EqualTo('pwd',message="确认密码错误")])
# webget可以通过form表单去渲染前端页面中
field
field wtform的第一个widget,一个filed对应一个输入框,或者需要校验的
FloatField
DecimalField 必须输入数值,显示时保留一位小数
DateField 必须是 年-月-日 格式的日期
PasswordField 密码输入框
RedioField 单选框,choices里面内容会在ul标签中,
gender = RadioField('Gender',choices=[('m','Male'),("f","female")])
# 每个项是 值,显示名 choices=[('m','Male')]
SelectField 下拉单选框,choices里面内容会在option,
job = SelectField('job',choices=[('IT','se'),("docor","aid")])
# 每个项是 值,显示名 choices=[('m','Male')]
Select 类型,多选框 choices里面的内容会在option里面,里面每个项同上面的一样,
# 值,显示名对
# hobby = SelectMultipleField('hobby',choices=[('value','showname')])
通过wideget来渲染前端页面/传统方式实现前端页面
传统手写代码实现表单
label 第一个参数
<label >手机号码</label><input name="phone" type="text">
<label >密码</label><input name="pwd" type="password">
<label >确认密码</label><input name="confirm_pwd" type="password">
使用wtforms渲染表单
{{ form.phone.label() }} {{ form.phone() }}{{ form.phone.errors }}
{{ form.pwd.label() }} {{ form.pwd() }}
{{ form.confirm_pwd.label() }} {{ form.confirm_pwd() }}
render_kw
添加一些额外的属性进取,# 模板引擎的编写方式是在前端直接通过form对象去渲染# 或者是使用原生html代码# render_kw={‘class’:”form-control”} 前端F12会发现属于一个类# 名为 form-control
# 默认值
filed内 default= 如果没有输入就默认为0,输入了,进行验证判断
validators
属于wtform下的一个组件,需要引用才可以使用。
from wtforms.validators import Regexp, DataRequired, Length, EqualTo, ValidationError
# validators属性本来是一个元祖,其中含有各种各样的验证逻辑以供使用。在实际使用的时候以列表的形式进行引用
pwd = PasswordField(label="密码",
validators=[
Length(6, 32, message='长度不对'),
DataRequired("密码不能为空")])
看几种常见的validators方法,实际需要使用验证类的时候可以直接点进原码进行寻找,如果没有的话就自行进行定义自定义的验证类即可
DataRequired 参数必须
Regexp 正则匹配
Email 格式校验
IPAddress ip格式校验
EqualTo 相等校验
// 使用实例
phone = StringField(render_kw={'class':"form-control"},label="手机号码", validators=[
Regexp(r'^1[3,5,7,8,9]\d{9}$', message="手机号码格式错误"),
Mobile(message="手机号已经存在"),
DataRequired("手机号码不能为空")]) #
pwd = PasswordField(label="密码", validators=[
Length(6, 32, message='长度不对'),
DataRequired("密码不能为空")])
confirm_pwd = PasswordField(validators=[
EqualTo('pwd',message="确认密码错误")])
# webget可以通过form表单去渲染前端页面中
自定义validator
不需要深入思考验证类的构成方式,只需要知道自定义验证类需要定义一个init方法,一个call方法,
call方法前面的文章有提到过,是为了类可以直接被调用,类直接被调用的时候是调用的call方法里面的语句块,下看实例
# init 主要设置各种各样的实例属性,在下面的call方法中使用
# call 主要包含各种验证方法的编写
class Mobile:
regex = re.compile(r"1[3,5,7,9]]\d{9}$")
def __init__(self,message=None):
if message is None:
self.message = "不是手机号码"
self.message = message
def __call__(self, form, field):
match = self.regex.match(field.data)
if not match:
message = self.message
raise ValidationError(message)
return match
class Exist:
regex = re.compile(r"1[3,5,7,9]]\d{9}$")
def __init__(self,message=None):
if message is None:
self.message = "不是手机号码"
self.message = message
def __call__(self, form, field):
# 查询DB中是否已经存在,存在则抛异常
# 否则return数据
主要应用场景,,当内置的验证器无法满足业务需求的时候,比如要检验一个数据在db中是否存在,就需要自己定义。
提示 csrf_token错误的情况
error : csrf_token': ['The CSRF token is missing.']}
请求正常成功的情况下,F12中,headers里面,request headers里面是有csrf_token记载的
在前端页面的表单内,加上,
{{ msg }}
{# url_for("这里面用的事端点名,,是一个字符串类型参数) #}
<form action="{{ url_for("register") }}" method="post">
{{ form.hidden_tag() }}
通过form表单渲染前端页面,与传统手写的有一些不同
<input id="csrf_token" name="csrf_token" type="hidden"
value="IjRlOTllNWYzYTgyODQxMmU1NjhjNzhlYjYzNzZjMWJlMGYzMjZjNzQi.XwEOKA.QUiiOzw_cBG2f9gcbebFILm4edU">
虽然外观上没有区别,但是value是 csrftoken的值,上面的hidden_tag也是隐藏这一属性的方法
label for 属性,点击文字焦点进入输入框
表单验证则与之前的实现没什么不同
{{ form.phone.label() }} {{ form.phone() }}{{ form.phone.errors }}
{{ form.pwd.label() }} {{ form.pwd() }}
{{ form.confirm_pwd.label() }} {{ form.confirm_pwd() }}
同时在路由处判定为get请求吧form表单传入
@app.route('/register', methods=['GET', 'POST'], endpoint="register")
def register():
form = RegisterForm(request.form)
if request.method == "GET":
return render_template("register.html", form=form)
全局变量与中间件
中间件
在流程中进行一些额外的处理,概念类似pytest的前后置。
通常称为钩子方法,
@app.before_request
在获取任何一个请求之前可以做一些额外的处理,可视为全局前置
需求场景:
运行:数据埋点,当用户访问的时候进行计数,通常来说放在缓存里面或者是db中。
使用:
路由文件下进行编辑
app.py
# 在某个请求之前,就会在任何一个请求之前进行被装饰的钩子方法
@app.before_request
def get_num_of_interface():
print("访问计数+1")
@app.after_request
当某一个请求结束了之后要做的事情
场景:
封装响应信息,组装响应对象response参数必须传,
return必须是一个response对象通常用来修改响应
如果访问的视图出现错误,则不会调用
@app.after_request
def after(response):
print("after", request.url)
response.headers['you'] = 'love'
return response
@app.tear_down_request
不管发不发生错误,都会被调用
场景:
如果一个正常的视图,应该使用afterrequest
teardownrequest是无论如何都需要执行的方法
比如一些资源的释放,db连接之类
本实例逻辑:
在所有连接执行之前去连接db,不管请求是否成功,关闭db连接
@app.before_request
def get_db():
if 'db' not in g:
# 检测如果不在就进行连接DB
g.db = connect_to_database()
@app.teardown_request
def teardown_db(exception):
# pop方法移除db变量
db = g.pop('db', None)
if db is not None:
db.close();
@app.before_first_request
全局变量
request
当flask应用处理请求时,会根据从wsgi服务器收到的环境创建一个request对象,
因为工作者(取决于服务器的线程,进程,或者协程),一次只能处理一个请求,
所以该请求期间,请求数据可被认为是该工作者的全局数据,flask对此使用术语,本地情景。
g 变量
用来同一个请求中共享数据使用,因为每个请求之后都会重新实例化该变量,所以不同请求之间不通用。
主要是用来验证用户信息之类的操作。
def connect_to_database():
conn = pymysql.connect()
return conn.cursor()
@app.before_request
def get_db():
if 'db' not in g:
# 检测如果不在就进行连接DB
g.db = connect_to_database()
# 检查如果db不在g变量内进行连接,连接之后,db在g变量内部,可以通过移除,或者调用来操作db
@app.teardown_request
def teardown_db(exception):
# pop方法移除db变量
db = g.pop('db', None)
if db is not None:
db.close();
# 视图中进行使用
@app.route("/register")
def reg():
g.db.execute("sql")
a = g.db.fetchall()
print(a)
session
login之后 session[‘user’] =username 的形式将用户信息存在其中
session与g变量不同,可以跨域请求,登出的时候移除session中保存的suer信息即可
比如在before_request中进行session中是否含有用户信息的判定,。
session.get(key)
可以对session进行相关设置
session_cookie_name , permannet_session_lifetime
解析:
登录成功的时候 F12-network下,会多出来一个set-cookie的一个字段
sessionid就存放在其中。
而后在去访问其他页面的时候就会带上这个session字段
整体流程:
浏览器—登录—服务器—设置session[‘user’]=xxx—返回给浏览器—返回的变量名为set-cookie
set-cookie可修改、之后浏览器每次发送请求都会在cookie中添加sessionid。
@app.route('/')
def home():
if not session.get('user'):
return '没有登录'
return '首页'
@app.route('/login')
def login():
username = request.args.get('username')
pwd = request.args.get('pwd')
if username and pwd :
session['auth'] = username
return "登录成功"
return "未登录"