几个编写习惯
表单属性名要和前端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 sessionfrom flask_wtf import FlaskFormfrom wtforms import StringField, PasswordField,SelectMultipleFieldfrom wtforms.validators import Regexp, DataRequired, Length, EqualTo, ValidationErrorclass 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对应一个输入框,或者需要校验的
FloatFieldDecimalField 必须输入数值,显示时保留一位小数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 "未登录"
                    