几个编写习惯

表单属性名要和前端input框内变量名一直

尽量保持与db字段一致

flask_wtf

一个整合wtf的第三方库,其中的一些结构和设计与django相似。

使用之前

使用wtfomr组件需要设置一个秘钥,不设置就会出现下面这样的错误

  1. RuntimeError: A secret key is required to use CSRF.
  2. 需要在 路由类中设置一个秘钥属性,
  3. app.py
  4. app.config["SECRET_KEY"] = os.urandom(24) # 秘钥设置任意

使用方法

定义一个表单类,并需要继承FlaskForm

  1. forms.py 文件内,单独写一个表单验证类提供表单验证。
  2. from flask import session
  3. from flask_wtf import FlaskForm
  4. from wtforms import StringField, PasswordField,SelectMultipleField
  5. from wtforms.validators import Regexp, DataRequired, Length, EqualTo, ValidationError
  6. class RegisterForm(FlaskForm):
  7. phone = StringField(render_kw={'class':"form-control"},label="手机号码", validators=[
  8. Regexp(r'^1[3,5,7,8,9]\d{9}$', message="手机号码格式错误"),
  9. Mobile(message="手机号已经存在"),
  10. DataRequired("手机号码不能为空")]) #
  11. pwd = PasswordField(label="密码", validators=[
  12. Length(6, 32, message='长度不对'),
  13. DataRequired("密码不能为空")])
  14. confirm_pwd = PasswordField(validators=[
  15. EqualTo('pwd',message="确认密码错误")])

路由处使用

  1. @app.route('/register', methods=['GET', 'POST'], endpoint="register")
  2. def register():
  3. form = RegisterForm(request.form)
  4. if form.validate():
  5. return "success"
  6. return f'error:{form.errors}'
  7. # 调用的时候,从表单模块拿到验证类,然后将前端提交的form信息传入
  8. # 然后调用生成form对象的validate方法进行验证,返回值为布尔型
  9. # true 成功,false 失败
  10. # 从请求对象request中取得form表单,然后调用表单对象的验证方法进行验证,
  11. # 成功返回成功,失败则返回表单类中设置的错误信息

关于request传送请求信息的优先级

如果传送的请求信息不是通过form表单传送过来的,回默认去检索obj。

  1. def __init__(self,
  2. formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
  3. # 先formdata,如果不是,则obj,如果不是,则data
  4. # data 需要时一个字典格式的数据,比如json数据
  5. # obj 可以时一个类,比如一些ajax请求返回的数据

表单验证类属性,方法

  1. class RegisterForm(FlaskForm):
  2. phone = StringField(
  3. render_kw={'class':"form-control"},
  4. label="手机号码",
  5. validators=[
  6. Regexp(r'^1[3,5,7,8,9]\d{9}$', message="手机号码格式错误"),
  7. Mobile(message="手机号已经存在"),
  8. DataRequired("手机号码不能为空")
  9. ]
  10. ) #
  11. pwd = PasswordField(label="密码", validators=[
  12. Length(6, 32, message='长度不对'),
  13. DataRequired("密码不能为空")])
  14. confirm_pwd = PasswordField(validators=[
  15. EqualTo('pwd',message="确认密码错误")])
  16. # webget可以通过form表单去渲染前端页面中

field

field wtform的第一个widget,一个filed对应一个输入框,或者需要校验的

  1. FloatField
  2. DecimalField 必须输入数值,显示时保留一位小数
  3. DateField 必须是 年-月-日 格式的日期
  4. PasswordField 密码输入框
  5. RedioField 单选框,choices里面内容会在ul标签中,
  6. gender = RadioField('Gender',choices=[('m','Male'),("f","female")])
  7. # 每个项是 值,显示名 choices=[('m','Male')]
  8. SelectField 下拉单选框,choices里面内容会在option
  9. job = SelectField('job',choices=[('IT','se'),("docor","aid")])
  10. # 每个项是 值,显示名 choices=[('m','Male')]
  11. Select 类型,多选框 choices里面的内容会在option里面,里面每个项同上面的一样,
  12. # 值,显示名对
  13. # hobby = SelectMultipleField('hobby',choices=[('value','showname')])

通过wideget来渲染前端页面/传统方式实现前端页面

  1. 传统手写代码实现表单
  2. label 第一个参数
  3. <label >手机号码</label><input name="phone" type="text">
  4. <label >密码</label><input name="pwd" type="password">
  5. <label >确认密码</label><input name="confirm_pwd" type="password">
  6. 使用wtforms渲染表单
  7. {{ form.phone.label() }} {{ form.phone() }}{{ form.phone.errors }}
  8. {{ form.pwd.label() }} {{ form.pwd() }}
  9. {{ form.confirm_pwd.label() }} {{ form.confirm_pwd() }}

render_kw

添加一些额外的属性进取,# 模板引擎的编写方式是在前端直接通过form对象去渲染# 或者是使用原生html代码# render_kw={‘class’:”form-control”} 前端F12会发现属于一个类# 名为 form-control

# 默认值

filed内 default= 如果没有输入就默认为0,输入了,进行验证判断

validators

属于wtform下的一个组件,需要引用才可以使用。

  1. from wtforms.validators import Regexp, DataRequired, Length, EqualTo, ValidationError
  2. # validators属性本来是一个元祖,其中含有各种各样的验证逻辑以供使用。在实际使用的时候以列表的形式进行引用
  3. pwd = PasswordField(label="密码",
  4. validators=[
  5. Length(6, 32, message='长度不对'),
  6. DataRequired("密码不能为空")])
  1. 看几种常见的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 "未登录"