最小框架
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=False, port=5000)
项目配置
开启DEBUG
为什么要开启
- 如果开启DEBUG模式,那么在代码中抛出异常,在浏览器的页面中可以看到具体的错误信息及具体的错误位置,方便开发者调试。但如果想在页面上调试,应该输入控制台的PIN码。
- 如果开启,那么以后在python代码中修改了任何代码,只要
CTRL+S
保存代码,flask就会重新记载整个网站,不需要手动点击重新运行。
4种开启方式
- 在
app.run()
传参debug=True;
- 增加语句
app.debug = True;
- 配置参数:
app.config.update(DEBUG=True)
- 配置文件(以后项目使用这种):
app.config.from_object(config)
示例
from flask import Flask
app = Flask(__name__)
# 方式2
# app.debug = True
# 方式3
# app.config.update(DEBUG=True)
# 方式4(真正项目采用这种配置文件方式)
# app.config.from_pyfile('config.py')
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
# 方式1
app.run(debug=True)
配置config
from_object(obj)
方式
-
使用:
import config app.config.from_object(config)
from_pyfile(path[,slient=False])
方式
app.config.from_pyfile("./config.py")
- 此方式不局限于
.py
文件,也可以用.txt
文件… silent
:False
:如果找不到文件报错反之True
不报错
url请求
url唯一
-
在定义
url
的时候,在最后加一个/
-
原因:
- 如果不加,那么浏览器访问这个
url
的时候,用户一旦加入斜杠,就访问不到。 - 搜索引擎会将不加和加
/
的是为两个不同的url,而其实它们代表同一个,那么就会给搜索引擎造成误解。加了斜杠,就不会出现没有斜杠的情况。
# 一定要在定义url时最后加一个'/' @app.route('/user/') def user(): return 'User...'
- 如果不加,那么浏览器访问这个
Http请求
GET和POST
-
GET: 把参数放到url中,通过
?key=value
的形式传递,因为会把参数放到url中,所以某些视力好的人一眼就能看到用户密码,这样就不太安全。 -
POST: 把参数放到
Form Data
中,这样就避免了被偷窥的风险,但是别人想获取密码,可以采用抓包的形式。同时,因为POST请求可以提交一些文件、数据,那么增加了让黑客恶意发送病毒文件的风险,所以也不是绝对安全
设置请求方式
- 默认只用
GET
方式,设置请求方式:app.route(url, methods)
@app.route('/user/',methods=['GET'])
def get_user():
return 'Get User...'
@app.route('/user/',methods=['POST'])
def add_user():
return 'Add User...'
@app.route('/user/',methods=['PUT'])
def update_user():
return 'Update User...'
@app.route('/user/',methods=['DELETE'])
def delete_user():
return 'Delete User...'
获取请求参数
[GET]路径参数
- 参数传递:
/<参数名>
,然后在视图函数也要定义同名的参数。 - 限定数据类型:
<数据类型:变量名>
,数据类型:string
: 默认类型,但不接受/
,\
path
: 能接受/
,\
int
: 整型float
: 浮点型uuid
: 接受唯一的uuid
字符串,uuid
是个全宇宙唯一的字符串,可用来作为表的主键any
: 可以在url中指定多种路径
@app.route('/p/<path:path>') def param_path(path): return 'path:{}'.format(path) @app.route('/s/<string:string>') def param_string(string): return 'string:{}'.format(string) @app.route('/<int:int>') def param_int(int): return 'int:{}'.format(int) @app.route('/<float:float>') def param_float(float): return 'float:{}'.format(float) @app.route('/<uuid:uuid>') def param_uuid(uuid): return 'uuid:{}'.format(uuid) @app.route('/<any(blog,qq):which>/<id>') def param_any(which,id): return '{}:{}'.format(which,id)
[GET]查询参数
-
使用
path
形式将参数嵌入到路径中 -
使用查询字符串, 即通过
?key=value
的形式传递,要引入request
模块:@app.route('/query') def query(): account = request.args.get('account') password = request.args.get('password') return 'User(account={},password={})'.format(account, password)
-
默认情况下,上述取参方式得到的都是
str
类型,想要直接得到int,则:@app.route('/list') def list_user_page(): current = request.args.get('current', type=int) size = request.args.get('size', type=int) return 'UserList(current={},size={})'.format(current, size)
-
适应性:若页面想要做SEO优化, 那么使用
path
的形式;如果不在乎搜索引擎的优化,那么使用查询字符串更好。
[POST]表单参数
- POST传参可分为两大类,一类是在form传参,另一类是请求体传参body。
- flask对这两类参数要采用不同的方式接收
@app.route('/login/form', methods=['POST'])
def login_form():
""" 可接受form和urlencode """
account = request.form.get('account')
password = request.form.get('password')
ok = account == '90217' and password == '123abc'
return '登录成功' if ok else '登陆失败'
@app.route('/login/json', methods=['POST'])
def login_json():
""" 可接受json表单 """
account = request.json.get('account')
password = request.json.get('password')
ok = account == '90217' and password == '123abc'
return '登录成功' if ok else '登陆失败'
注意
- 获取请求参数,其实也可以
request.args[key],request.form[key],request.json[key]
的形式,但以后要禁止使用,因为如果key不存在会报错(500服务器内部错误影响用户体验)。以后都用dict的get
方法获取参数 - 对于get查询参数,使用
request.args.get('xxx',type=int)
时指定type
属性来确定参数的数据类型。但是对于post表单参数来说,无法指定type
属性只能使用get('xxx')
,即request.json.get('xxx', type=int)
是错误的
URL与视图函数的映射
url_for
-
一般通过一个
url
就可执行到某一函数。反过来,我们知道一个函数,该如何去获取url呢? -
语法:
url_for(func[,**kwargs])
func
: 视图函数的名字。**kwargs
: 传递给url
,如果传递的参数在之前的url
已定义, 那么这个参数会以path
的形式传递给url
;若没定义,那么将以查询字符串?key=value
的形式放到url
中
@app.route('/urlfor/') def urlfor(): return url_for('url_target', q='shuang') # 此方式可以一行代码不用改,就改变url @app.route('/urltarget/<path:q>') # 此处的函数名和url_for里的相同 def url_target(q): return 'q:{}'.format(q)
-
优点:
- 将来如果修改了
url
,但没有修改url
对应的函数名,就不用到处替换url
了 url_for()
会自动处理那些特殊的字符,无需手动处理。
- 将来如果修改了
自定义url转换器
-
刚刚在url映射的时候,我们看到了flask内置了几种数据类型的转换器,比如
int/string
。但如果它们不能满足你的需求时,可以自定义转换器。 -
步骤:
- 导入模块:
from werkzeug.routing import BaseConverter
- 写一个自定义类,继承
BaseConverter
,在自定义类中,重写regex
- 写一个转换器类,继承
BaseConverter
,
- 在转换器类中实现
to_python(self,value)
:这个方法返回值将会传递到view
函数作为参数 - 在转换器类中,实现
to_url(self,values)
:这个方法的返回值,将会在调用url_for
函数的时候生成符合要求的url
形式
- 将这两个类, 映射到
app.url_map.converters
from werkzeug.routing import BaseConverter # url含有手机号的变量,必须限定这个变量的格式满足手机号码格式 class TelConverter(BaseConverter): '''自定义URL转换器''' regex = r'1[34578]\d{9}' app.url_map.converters['tel'] = TelConverter class ListConverter(BaseConverter): def to_python(self, value): return value.split("+") def to_url(self, value): return '+'.join(value) app.url_map.converters['list'] = ListConverter @app.route('/tel/<tel:telNum>') def tel(telNum): return "您的手机号:{}".format(telNum) @app.route('/scripts/<list:lang>') def scripts(lang): return "选择的语言为:{}".format(lang) if __name__ == '__main__': app.run(debug=True)
- 导入模块:
重定向和跳转
- 语法:
redirect(path[,code=302])
code
:重定向的种类,默认302
时临时性重定向,也可传301
实现永久性重定向
from flask import request, redirect @app.route('/userinfo') def userinfo(): account = request.args.get('account') if account: return "用户{}的个人中心".format(account) else: # 如果未登录,就临时性重定向到登陆界面 return redirect('/login/', code=302) @app.route('/login/') def login(): return "Login..."
url响应
常见响应结果
string
@app.route('/')
def hello_world():
return 'Hello World!'
json
from flask import jsonify
@app.route('/')
def hello_world():
return jsonify(status=1, message='Hello World!', data=None)
Response
关于响应
- 视图函数的返回值会自动转换成一个响应对象,flask转换逻辑如下:
- 如果是一个合法的响应对象,直接返回;
- 如果是字符串,flask会重新创建一个
werkzeug.wrappers.Response
对象,Response
将该字符串作为主体,状态码为200
,MIME
类型为text/html
,然后返回该Response
对象。 - 返回元组
(response,status,headers)
,其中status
值会覆盖默认的200状态码 headers
可以是一个列表或字典,作为额外的消息头。- 如果以上条件都不满足,flask会假设返回值是一个合法的
WSGI_t
应用程序,并通过
Response.force_type(rv,request.environ)
转换作为一个请求对象。
- 注意: 可以使用
make_response
方法来创建Response对象
,这个方法可以设置如cookies,headers
等信息。
视图函数中可以返回的值
- 字符串,返回的字符串其实底层将这个字符串包装成了一个
Response
对象。 - 元组,元组的形式是(响应体,状态码,头部信息),也不一定3个都要写,写两个也是可以的;其实在底层也将元组包装成了一个
Response对象
Response
及子类.
自定义响应
- 继承
Response
类 - 实现方法:
force_type(cls,rv,environ=None)
- 指定
app.response_class
为程序员自定义的对象.- 条件:视图函数返回的数据既不是字符串,又不是元组,也不是Response对象
- 注:如果满足条件,就将返回值传给
force_type
,然后再将force_type
的返回值返回给前端
jsonify
除了将字典转换成json
对象,还将该对象包装成了Response
对象
示例
from flask import Flask, Response
from flask import jsonify
app = Flask(__name__)
# 将视图函数中返回的字典,转换成json再返回
class JsonResponse(Response):
'''视图函数Response返回值详解'''
@classmethod
def force_type(cls, response, environ=None):
"""只有返回非字符串/非元组/非Response对象才会调用"""
# response是视图函数的返回值
if isinstance(response, dict): # 如果返回字典,改成json对象
response = jsonify(response)
return super(JsonResponse, cls).force_type(response, environ)
app.response_class = JsonResponse
@app.route('/demo01')
def demo01():
"""如果是一个合法的响应对象,直接返回"""
return Response('test1')
@app.route('/demo02')
def demo02():
"""返回元组(response,status,headers)"""
return ('test2', 200, {'username': 'Azir'}) # 设置头部信息,在响应头
@app.route('/demo03')
def demo03():
"""如果以上条件都不满足,会假设返回值是一个合法的WSGI_t,并通过Response转换"""
return {'username': 'LZC', 'age': 20}
@app.route('/demo04')
def demo04():
"""Response及子类"""
res = Response('test4')
res.set_cookie('goddess', 'zhengshuang') # 可以设置cookie等参数
return res
if __name__ == '__main__':
app.run(debug=True)
模板
介绍
- 在之前的章节中,视图函数只是直接返回文本,而在实际生产中很少这样用。因为实际的页面大多是带有样式和复杂逻辑的HTML代码,这可以让浏览器渲染出非常漂亮的页面。目前市场上有非常多的模板系统,其中最知名最好用的就是Jinja2和Mako。我们先来看一下这两个模板的特点和不同:
- Jinja2:Jinja是日本寺庙的意思,并且寺庙的英文是temple和模板的英文template的发音类似。Jinjia2是默认的仿Django模板的一个模板引擎,由Flask的作者开发。它速度快,被广泛使用,并且提供了可选的沙箱模板来保证执行环境的安全。它有以下优点:
- 让前端和后端开发者分离;
- 减少flask项目代码的耦合性,页面逻辑放在模板中,业务逻辑放在视图函数中,将页面逻辑和业务逻辑解耦有利于代码的维护;
- 提供控制语句、继承等高级功能,降低开发的复杂度。
- Marko:它从Django、Jinja2等模板借鉴很多语法和API,它有以下优点:
- 性能和Jinja2相近。
- 有大型网站在使用,有成功的案例,如Reddit和豆瓣都在使用;
- 有知名的web框架支持。Pylons和Pyramid这两个web框架内置模板就是Mako;
- 支持在模板中写几乎原生的Python语法,对Python工程师比较友好,开发效率高;
- 自带完整的缓存系统,也提供了非常好的扩展接口,很容易切换成其他的缓存系统
模板规则
模板查找路径
- 渲染模板时,默认从项目根目录下的
templates
查找html文件 - 更换目录:
app = Flask(__name__[,template_folder])
方法传入这个可选参数。
模版传参及其技巧
- 在使用
render_template
可以直接传递参数。 - 若参数过多,可将参数放入字典中,然后向
render_template()
传参的时候,使用**context
传递动态参数;如果字典中还嵌套字典,在html页面可以用母参数.key
或母参数['key']
的方式传递二级参数。
模版中使用url_for
- 使用起来跟我们后台视图函数中的
url_for()
基本相同,也是传递视图函数的名字,也可以传递参数。 - 例子:
{{ url_for('函数名',**kwargs) }}
过滤器
什么是过滤器
- 定义: 过滤器是通过管道符号
|
进行使用的。例如{{ name | length }}
将返回name
的长度。过滤器相当于一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。 - 语法:
{{ 变量 | 过滤器名字 }}
常用过滤器
- 默认值过滤器:
value | default('默认值'[,boolean=False])
:参数boolean
,False
:如果value
根本不存在,就会显示默认值;如果value
存在、即使为False
类型,也不会使用默认值;True
:如果value
结果为False
(不存在、None、空字符串、空字典、空列表…),就会返回默认值。
- 可使用
{{value or '默认值'}}
来代替{{value | default('默认值',boolean=True)}}
- 转义过滤器:
{{ value | safe }}
,开启转义,但可以不设置,默认就是自动转义,将<
,>
等字符转化为HTML符号。- 可以关闭字符的自动转义;
{{ value | escape }}
{% autoescape off/on %} # 可以利用此方法代替上面过滤器来开启 <>代码块</> # 或关闭自动转义 {% endautoescape %}
- 可以关闭字符的自动转义;
- 首尾过滤器:
{{ list | first/last }}
返回序列的第一个或最后1个元素。 - 格式化过滤器:
{{ "你好,%s" | format("Unity") }}
- 替代过滤器:
{{ value | replace('要替换的','替换后的' }}
join(value,d=u')
:将一个序列用d这个参数的值拼接字符串int/float/string(value)
:将值转换为指定的类型。lower/upper(value)
: 对字符串大小写trim
:截取字符串前边和后边的空白字符wordcount(s)
:计算一个长字符串中单词的个数。
语句
条件语句
-
和python类似可使用
>,<,<=,>=,==,and,or,not,()
{% if 条件0 %} 语句0 {% else %} 语句1 {% endif %}
循环语句
-
和python相似可遍历所有的序列及迭代器
{% for . in ... %} 语句0 {% else %} 语句1 {% endfor %}
-
获取遍历状态:
loop.index
: 当前迭代的序号loop.index0
: 当前迭代的索引loop.first
: 判断是否为第一次迭代loop.last
: 判断是否为最后一次迭代loop.length
: 序列的长度
-
注意:无
break
和continue
语句
宏
概述
- 模板中的宏跟python中的函数相似,可传递参数,但不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量。使用宏时,参数可为默认值
导入宏
- 语法:
{% imprt '宏文件路径' as xxx %}
{% from '宏文件路径' import '宏名称' (as xxx) %}
- 宏文件路径不要以相对路径,都要以
templates
作为绝对路径寻找。 - 如果想在导入宏的时候,就把当前模板的一些参数传给宏所在的模板,那么应该在导入时使用
{% imprt '宏文件路径' as xxx with context %}
include标签
- 这个标签相当于直接将指定的模板中的代码复制粘贴到当前位置。
- 如果想要使用父模板中的变量,直接用即可,不需要使用
... with context
- 路径也要用绝对路径。
set语句
-
在模板中可以使用
set
语句定义变量,一旦定义了这个变量,在后面的代码中都可以使用这个变量 -
语法:
{% set a = '哈哈哈' %}
, 此时a
相当于一个全局变量
with语句
- 定义的变量,只能在with语句中执行,超过这个代码块就不能使用,相当于一个局部变量
{% with class_='数媒1902' %} <p>班级:{{ class_ }}</p> {% endwith %} <!-- 不要忘记endwith --> <p>超过作用域的班级:{{ class_ }}</p> <!-- 无效 -->
- 注意:
with语句
也不一定要跟一个变量,可以定义一个空的{% with %}
,以后在它当中通过set语句
定义变量,这样的效果是不变的
加载静态文件
- 语法:
{{ url_for('static',filename='路径') }}
,在html标签中传递这个属性即可
模板继承
- 可以把一些公用的代码单独取出来放到一个父模板中,以后子模版直接继承就可以使用,减少了代码的复用,以后修改起来也非常方便。
<!-- 父模板 -->
{% block body_block %}
{% endblock %}
<!-- 子模版 -->
{% extends '文件路径' %}
{% block body_block %}
子模版内容
{% endblock %}
- 子模版调用父模板
block
中的代码:默认情况下,子模版如果调用父模板的block
,父模板block
语句中的代码会被覆盖。如果想保留,可以在子模版block
语句中使用语句{{ super() }}
语句 - 子模版一个
block
调用另一个block
中的代码:{{ self.其他block名称() }}
- 子模版中代码第一行必须为继承语句:
{% extends '文件路径' %}
- 子模版中,如果想要在浏览器页面渲染代码,必须放在
block
语句中,否则不会被渲染。如果想写其他代码,必须在父模板中写。
视图
映射
- 添加映射:添加url与视图函数的映射
- 语法:
add_url_rule(rule[,endpoint=None,view_func=None])
,endpoint
参数:- 如果传入那么默认会使用
view_func
的名字作为endpoint
,以后在使用url_for()
时,就要看在映射的时候有没有传递endpoint
参数:如果传了,那么就应该使用endpoint
指定的字符串; - 如果没有传递,应该使用
view_func
的名字
- 如果传入那么默认会使用
- 语法:
- 装饰器
@app.route(rule,**options)
:这个装饰器的底层,其实也是用add_url_rule()
来实现url与视图函数映射的。
类视图
标准类视图及场景
- 标准类视图,必须继承
flask.views.View
- 必须实现
dipatch_request()
方法,以后请求过来后,都会执行这个方法,这个方法的返回值相当于之前的视图函数,也必须返回Response对象或者子类的对象,或者是字符串、元组。 - 须通过
app.add_url_rule(rule,endpoint,view_func=ListView.as_view('list'))
来做url与视图的映射。view_func
参数必须使用类视图下的as_view
类方法来转换。 - 如果指定
endpoint
,那么在使用url_for()
反转时必须使用endpoint
指定的名字;如果未指定,那么就可以使用as_view(视图名字)
中指定的视图名字作为反转。 - 类试图的的好处:可以继承,把一些公共的东西抽取出来放到父类中,子视图直接拿来用即可。但是不是说所有的视图都要使用类试图,这个视情况而定。
基于请求方法的类视图
- 基于方法的类试图,是根据请求的method来执行不同的方法的。如果用户发送get请求,会执行get方法。如果发送post请求,执行post方法。其他方法也类似,如delete和put。
- 优点: 让代码简洁,所有get请求相关的代码都放在
get()
中, 所有post请求相关的代码都放在post()
中,无需通过每个函数下的if request.method == 'GET'
就可处理所有请求
类视图中使用装饰器
- 如果使用的是函数视图,那么自己定义的装饰器必须放在
@app.route()下
面,否则这个装饰器不起任何作用。 - 类视图的装饰器,需要重写类视图的一个类属性
decoractors=[]
,它是一个列表或元组,里面装的是所有装饰器的名称。
模块化路由(蓝图)
蓝图
基本使用
# 蓝图文件
from flask import Blueprint
user_bp = Blueprint('user', __name__)
# 主文件
from 蓝图文件名.py文件名 import user_bp
app.regist_blueprint(user_bp)
-
如果想在某个蓝图下的所有url都有一个url前缀,如在有关用户的url前面都加一个
/user
,可在定义蓝图时指定url_prefix='/user'
。但在定义时注意后面的斜杠, 如果加上, 以后在定义url视图函数的时候就不要再url前边加斜杠了。语法如下:
user_bp = Blueprint('user', __name__,url_prefix='/user')
-
优点:让Flask项目更加模块化,结构更清晰,可以将相同模块的视图函数放在同一个蓝图下、同一个文件中,方便管理。
模版文件寻找规则
- 如果项目中的
templates
文件夹中有相应的模板文件,就直接优先使用。 - 如果没有,那么就到在定义蓝图时指定的路径中寻找。该路径可为绝对路径,也可为相对路径。相对路径是相对当前这个蓝图py文件所在的目录。
- 语法:
user_bp=Blueprint('user', __name__, template_folder='mods')
, 因为这个蓝图文件是在blueprint/user.py
,那么就会到blueprint/mods
直接查找模板。
蓝图中静态文件寻找规则
- 在模板文件中,加载静态文件,如果使用
url_for('static')
,那么就只会在app默认的static文件夹目录下查找静态文件 - 如果在加载静态文件时,指定蓝图的名字,如
url_for('user.static')
,那么就会到这个蓝图指定的static_folder下查找静态文件。 - py主文件:
# 这个蓝图文件是在blueprint/user.py,那么到blueprint/statics直接查找模板 user_bp = Blueprint('user', __name__, static_folder='statics')
- HTML模板文件:
<!-- url_for()第1个参数传入蓝图名字user,而不是py文件名 --> <link rel="stylesheet" href="{{ url_for('user.static',filename='用户.css') }}">
url_for反转蓝图
- 使用蓝图,以后想反转蓝图中的视图函数为url,就应该在使用
url_for()
时指定这个蓝图的名字,比如url_for('user.login')
,否则就找不到这个endpoint
- 在html模板中的
url_for()
同样也要这么写。 - 即使在同一个蓝图本身反转url,也要这么写,指定蓝图的名字。
蓝图实现子域名
- 创建蓝图对象时,可传递
subdomain
参数指定子域名的前缀:
user_bp = Blueprint('user', __name__, subdomain='lzc')
- 需要在主app文件中配置
app.config
参数,注意IP和localhost中都不能有子域名:app.config['SERVER_NAME']='jd.com:5000'
- 在
C:\Windows\System32\drivers\etc
找到hosts文件,添加域名和本机的映射:# 注意: 域名,子域名都需要做映射 127.0.0.1 jd.com 127.0.0.1 lzc.jd.com
路由模块化
创建路由模块(蓝图)
- 在根目录(与
app.python
同级目录)下创建blueprint
文件夹 - 假设要创建user路由模块,则在blueprint文件夹下创建
user.py
- 在
user.py
中创建bp对象,并编写路由处理函数
from flask import Blueprint
# url_prefix: 公共路由前缀
bp_user = Blueprint('user', __name__, url_prefix='/user')
@bp_user.route('/login', methods=['POST'])
def login():
""" 用户登录 """
return 'logining..'
app导入及注册路由模块
from flask import Flask
from blueprint.user import bp_user
app = Flask(__name__)
# 模块化路由
app.register_blueprint(bp_user)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=True, port=5000)
WTForms
表单验证
两个作用
- 做表单验证,用户提交上来的数据验证是否合法
- 模板渲染
步骤
- 自定义一个类,继承自
wtforms.Form
- 定义好需验证的字段,字段名必须和模板中需要验证的
input标签name属性
保持一致 - 在需要验证的字段上,指定验证器
- 以后在视图中,就只需要使用这个表单类的对象,把需要验证的数据
request.form
传给这个表单类,以后调用form.validate()
方法- 如果返回
True
,代表用户输入的数据合法,否则不合法 - 如果验证失败,可通过
form.errors
来获取具体的错误信息
- 如果返回
示例
from wtforms import Form, StringField, IntegerField, BooleanField, SelectField
from wtforms.validators import *
class RegisterForm(Form):
'''注册表单验证'''
username = StringField(validators=[Length(min=4, max=18, message='用户名长度必须在4-18位之间')])
passward = StringField(validators=[Length(min=6, max=10)])
passward_repeat = StringField(validators=[EqualTo('passward')])
class LoginForm(Form):
'''登录表单验证'''
# pip install -i https://pypi.douban.com/simple email-validator
email = StringField(validators=[Email()])
str = StringField(validators=[InputRequired()])
age = IntegerField(validators=[NumberRange(18, 88)])
phone_num = StringField(validators=[Regexp(r'1[34578]\d{9}')])
url = StringField(validators=[URL()])
uuid = StringField(validators=[UUID()])
vcode = StringField(validators=[Length(min=4, max=4)])
def validate_vcode(self, field):
"""自定义验证"""
if field.data != '6666':
raise ValidationError('验证码错误!')
class SettingsForm(Form):
""" 设置表单验证 """
username = StringField('用户名:', validators=[InputRequired()])
age = IntegerField('年龄:', validators=[NumberRange(18, 88)])
remember = BooleanField('记住我: ')
tags = SelectField('选择', choices=[(1, 'JavaWEB'), (2, 'NodeJs')])
系统验证器
Email()
:验证是否为邮箱EqualTo()
:验证上传的数据是否和指定字段相等,常用于注册时检验’密码’和’确认密码’操作InputRequired()
:必须输入内容,不能不输入。Length(min=,max=)
:长度限制NumberRange(a,b)
:数字区间。Regexp
:使用正则表达式验证。URL
:验证是否为url。UUID
:验证uuid数据。
自定义验证器
步骤
- 在类里定义一个方法,命名规则为
validate_字段名(self,field)
- 在方法中,
field.data
可以获取到这个字段的值 - 如果数据满足条件,可以
pass
;如果验证失败, 应该抛出一个validators.ValidationError
的异常 , 并把这个验证失败的值传到这个异常类中
示例
class LoginForm(Form):
'''登录表单验证'''
# pip install -i https://pypi.douban.com/simple email-validator
email = StringField(validators=[Email()])
str = StringField(validators=[InputRequired()])
age = IntegerField(validators=[NumberRange(18, 88)])
phone_num = StringField(validators=[Regexp(r'1[34578]\d{9}')])
url = StringField(validators=[URL()])
uuid = StringField(validators=[UUID()])
vcode = StringField(validators=[Length(min=4, max=4)])
def validate_vcode(self, field):
"""自定义验证"""
if field.data != '6666':
raise ValidationError('验证码错误!')
文件上传及资源开放
static资源开放
**开放static文件夹下的资源:**在定义app变量时,指定static_folder
参数为static文件夹根目录即可,注意相对路径
# 这里static文件夹和app.py在同一路径
app = Flask(__name__, static_folder='./static')
访问:格式为http://虚拟根目录/static_folder最后一层目录/文件路径
,
-
注意不能省略
/static
,如http://localhost:5000/static/parser/Drain_result/1_00e355a5-22a7-4c48-9c0b-5b75c7b690e5.csv
-
注意如果
static_folder
是多层目录,那么中间要添加最后一层目录。例如app = Flask(__name__, static_folder='./results/data/')
,那么要访问下面的资源,访问的url为http://127.0.0.1:9000/data/origin/01.png
,注意不是http://127.0.0.1:9000/results/data/origin/01.png
!!!
代码调用:
filename1 = "meituan/meituan_hongbao/meituanbg.jpg"
meituanbg = url_for('static', filename=filename1, _external=True)
# 效果: meituanbg = http://localhost:5000/static/meituan/meituan_hongbao/meituanbg.jpg
下载资源
如果出于安全因素不希望用户访问static路径下的所有文件,而是提供一个公共接口让用户传参访问获取,则可采用此方式。
后端:有两种方式,尽量不要使用send_file(path)
,而使用send_from_directory(directory,path)
,后者限定了文件夹更安全。
- 后者传入directory和path,当然
path
不一定非得是末层文件(如a.txt
),是路径也可以(如a/b/c.txt
,下面例子就是这种方式) - flask返回文件流,不要直接
return open(path, 'wb')
,而是用上面两个接口,具体原因不详
import os
from flask import Flask, request, send_from_directory, send_file
UPLOAD_PATH = os.path.join(os.path.dirname(__file__), 'upload')
app = Flask(__name__)
@app.route('/file/<user_id>/<method>')
def download_file_01(user_id, method):
filename = '{}/{}.csv'.format(user_id, method)
return send_from_directory(UPLOAD_PATH, filename)
@app.route('/download', methods=['POST'])
def download_file_02():
user_id = request.json['user_id']
method = request.json['method']
template_path = './static/parser/{}/{}.csv'.format(user_id, method)
# 文件存在性和合法性校验
if not os.path.exists(template_path) or not os.path.isfile(template_path):
return jsonify(status=0, message='未找到此文件', data=None)
return send_file(os.path.abspath(template_path))
前端:假设要下载的文件在服务器中存储在/static/parser/2/Drain.csv
- static资源开放的方式访问:
url = 'http://localhost:5000/static/parser/2/Drain.csv' response = requests.get(url) with open('./test.csv', 'wb') as fp: fp.write(response.content)
download_file_01
方式访问:url = 'http://127.0.0.1:5000/file/2/Drain' response = requests.get(url) with open('./test.csv', 'wb') as fp: fp.write(response.content)
download_file_02
方式访问:url = 'http://127.0.0.1:5000/download' body = { 'user_id': 2, 'method': 'Drain', } response = requests.post(url, json=body) with open('./test.csv', 'wb') as fp: fp.write(response.content)
上传及访问
import os
from flask import Flask, request, render_template
app = Flask(__name__)
UPLOAD_PATH = os.path.join(os.path.dirname(__file__), 'upload')
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'GET':
return render_template('fileupload.html')
else:
file_avatar = request.files['avatar']
filename = secure_filename(file_avatar.filename)
file_avatar.save(os.path.join(UPLOAD_PATH, filename))
return '文件上传成功'
上传时添加校验
步骤
- 在模板form表单中,必须指定
encotype='multipart/form-data'
- 后台使用
request.files.get('avatar')
来获取文件数据 - 保存文件前,先要使用
werk.utils.secure_filename
来对上传上来的文件名进行过滤,这保证不会有安全问题 - 获取到上传来的文件后,调用
avatar.save(path)
保存文件 - 从服务器上读取文件,应该定义一个url和视图函数,来获取指定的文件。在这个视图函数中,使用
send_from_directory(path,filename)
来获取。
示例
import os
from flask import Flask
from flask import request, render_template, send_from_directory
from werkzeug.utils import secure_filename
from werkzeug.datastructures import CombinedMultiDict
from forms import UploadForm
app = Flask(__name__)
paths = os.path.join(os.path.dirname(__file__), 'upload')
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'GET':
return render_template('fileupload.html')
else:
# form是不可变类型,无法使用form.update()来更新files和form
form = UploadForm(CombinedMultiDict([request.form, request.files]))
if form.validate():
# 获取描述信息:有两种方法
# desc = request.form.get('desc')
print(form.desc.data)
# avatar = request.files.get('avatar')
avatar = form.avatar.data
filename = secure_filename(avatar.filename)
avatar.save(os.path.join(paths, filename))
return '文件上传成功!!'
else:
return form.errors
if __name__ == '__main__':
app.run(debug=True)
验证文件
步骤
- 定义表单的时候,对文件的字段,需要采用
FileField
类型 - 验证器应该从
flask_wtf.file
中导入,flask_wtf.file.FileRequired
验证上传文件是否为空;flask_wtf.file.FileAllowed
验证文件后缀名是否正确。
- 视图函数中,使用
from werkzeug.datastructures import CombinedMultDict
来把request.form
和request.files
合并,再传给表单来验证。
获取描述信息
desc = request.form.get('desc')
desc = form.desc.data
示例
- form文件
from wtforms import Form,FileField from wtforms.validators import InputRequired from flask_wtf.file import FileRequired,FileAllowed class UploadForm(Form): avatar = FileField(validators=[FileRequired(),FileAllowed(['jpg','png'])]) desc = StringField(validators=[InputRequired()])
- 主文件:
from werkzeug.datastructures import CombinedMultiDict from forms import UploadForm # form是不可变类型,无法使用form.update()来更新files和form form = UploadForm(CombinedMultiDict([request.form,request.files]))