几个编写习惯

表单属性名要和前端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方法,实际需要使用验证类的时候可以直接点进原码进行寻找,如果没有的话就自行进行定义自定义的验证类即可
  1. DataRequired 参数必须
  2. Regexp 正则匹配
  3. Email 格式校验
  4. IPAddress ip格式校验
  5. EqualTo 相等校验
  6. // 使用实例
  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="确认密码错误")])
  16. # webget可以通过form表单去渲染前端页面中

自定义validator

不需要深入思考验证类的构成方式,只需要知道自定义验证类需要定义一个init方法,一个call方法,
call方法前面的文章有提到过,是为了类可以直接被调用,类直接被调用的时候是调用的call方法里面的语句块,下看实例

  1. # init 主要设置各种各样的实例属性,在下面的call方法中使用
  2. # call 主要包含各种验证方法的编写
  3. class Mobile:
  4. regex = re.compile(r"1[3,5,7,9]]\d{9}$")
  5. def __init__(self,message=None):
  6. if message is None:
  7. self.message = "不是手机号码"
  8. self.message = message
  9. def __call__(self, form, field):
  10. match = self.regex.match(field.data)
  11. if not match:
  12. message = self.message
  13. raise ValidationError(message)
  14. return match
  15. class Exist:
  16. regex = re.compile(r"1[3,5,7,9]]\d{9}$")
  17. def __init__(self,message=None):
  18. if message is None:
  19. self.message = "不是手机号码"
  20. self.message = message
  21. def __call__(self, form, field):
  22. # 查询DB中是否已经存在,存在则抛异常
  23. # 否则return数据

主要应用场景,,当内置的验证器无法满足业务需求的时候,比如要检验一个数据在db中是否存在,就需要自己定义。

提示 csrf_token错误的情况

  1. error csrf_token': ['The CSRF token is missing.']}

请求正常成功的情况下,F12中,headers里面,request headers里面是有csrf_token记载的
在前端页面的表单内,加上,

  1. {{ msg }}
  2. {# url_for("这里面用的事端点名,,是一个字符串类型参数) #}
  3. <form action="{{ url_for("register") }}" method="post">
  4. {{ form.hidden_tag() }}
  5. 通过form表单渲染前端页面,与传统手写的有一些不同
  6. <input id="csrf_token" name="csrf_token" type="hidden"
  7. value="IjRlOTllNWYzYTgyODQxMmU1NjhjNzhlYjYzNzZjMWJlMGYzMjZjNzQi.XwEOKA.QUiiOzw_cBG2f9gcbebFILm4edU">
  8. 虽然外观上没有区别,但是value是 csrftoken的值,上面的hidden_tag也是隐藏这一属性的方法
  9. label for 属性,点击文字焦点进入输入框
  10. 表单验证则与之前的实现没什么不同
  11. {{ form.phone.label() }} {{ form.phone() }}{{ form.phone.errors }}
  12. {{ form.pwd.label() }} {{ form.pwd() }}
  13. {{ form.confirm_pwd.label() }} {{ form.confirm_pwd() }}

同时在路由处判定为get请求吧form表单传入

  1. @app.route('/register', methods=['GET', 'POST'], endpoint="register")
  2. def register():
  3. form = RegisterForm(request.form)
  4. if request.method == "GET":
  5. return render_template("register.html", form=form)

全局变量与中间件

中间件

在流程中进行一些额外的处理,概念类似pytest的前后置。
通常称为钩子方法,

@app.before_request

在获取任何一个请求之前可以做一些额外的处理,可视为全局前置
需求场景:
运行:数据埋点,当用户访问的时候进行计数,通常来说放在缓存里面或者是db中。
使用:

  1. 路由文件下进行编辑
  2. app.py
  3. # 在某个请求之前,就会在任何一个请求之前进行被装饰的钩子方法
  4. @app.before_request
  5. def get_num_of_interface():
  6. print("访问计数+1")

@app.after_request

当某一个请求结束了之后要做的事情
场景:
封装响应信息,组装响应对象response参数必须传,
return必须是一个response对象通常用来修改响应
如果访问的视图出现错误,则不会调用

  1. @app.after_request
  2. def after(response):
  3. print("after", request.url)
  4. response.headers['you'] = 'love'
  5. return response

@app.tear_down_request

不管发不发生错误,都会被调用
场景:
如果一个正常的视图,应该使用afterrequest
teardownrequest是无论如何都需要执行的方法
比如一些资源的释放,db连接之类

  1. 本实例逻辑:
  2. 在所有连接执行之前去连接db,不管请求是否成功,关闭db连接
  3. @app.before_request
  4. def get_db():
  5. if 'db' not in g:
  6. # 检测如果不在就进行连接DB
  7. g.db = connect_to_database()
  8. @app.teardown_request
  9. def teardown_db(exception):
  10. # pop方法移除db变量
  11. db = g.pop('db', None)
  12. if db is not None:
  13. db.close();

@app.before_first_request

在所有请求之前执行,并且只执行一次

全局变量

request

当flask应用处理请求时,会根据从wsgi服务器收到的环境创建一个request对象,
因为工作者(取决于服务器的线程,进程,或者协程),一次只能处理一个请求,
所以该请求期间,请求数据可被认为是该工作者的全局数据,flask对此使用术语,本地情景。

g 变量

用来同一个请求中共享数据使用,因为每个请求之后都会重新实例化该变量,所以不同请求之间不通用。
主要是用来验证用户信息之类的操作。

  1. def connect_to_database():
  2. conn = pymysql.connect()
  3. return conn.cursor()
  4. @app.before_request
  5. def get_db():
  6. if 'db' not in g:
  7. # 检测如果不在就进行连接DB
  8. g.db = connect_to_database()
  9. # 检查如果db不在g变量内进行连接,连接之后,db在g变量内部,可以通过移除,或者调用来操作db
  10. @app.teardown_request
  11. def teardown_db(exception):
  12. # pop方法移除db变量
  13. db = g.pop('db', None)
  14. if db is not None:
  15. db.close();
  16. # 视图中进行使用
  17. @app.route("/register")
  18. def reg():
  19. g.db.execute("sql")
  20. a = g.db.fetchall()
  21. 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。

  1. @app.route('/')
  2. def home():
  3. if not session.get('user'):
  4. return '没有登录'
  5. return '首页'
  6. @app.route('/login')
  7. def login():
  8. username = request.args.get('username')
  9. pwd = request.args.get('pwd')
  10. if username and pwd :
  11. session['auth'] = username
  12. return "登录成功"
  13. return "未登录"