1、Flask是什么?
Flask是一个非常小的PythonWeb框架,被称为微型框架(类似Java的SpringBoot);只提供了一个稳健的核心,其他功能全部是通过扩展实现的;意思就是我们可以根据项目的需要量身定制,也意味着我们需要学习各种扩展库的使用。
特点
1、微框架、简洁,给开发者提供了很大的扩展性。
2 、Flask和相应的插件写得很好,用起来很爽。
3 、开发效率非常高,比如使用 SQLAlchemy 的 ORM 操作数据库可以节省开发者大量书写 sql 的时 间。
Flask 的灵活度非常之高,他不会帮你做太多的决策,很多都可以按 照自己的意愿进行更改。
文档地址
1、中文文档(http://docs.jinkan.org/docs/flask/)
2、英文文档(http://flask.pocoo.org/docs/1.0/)
2、Flask的扩展组件
扩展列表:http://flask.pocoo.org/extensions/
Flask-SQLalchemy:操作数据库;
Flask-script:插入脚本;
Flask-migrate:管理迁移数据库;
Flask-Session:Session存储方式指定;
Flask-WTF:表单;
Flask-Mail:邮件;
Flask-Bable:提供国际化和本地化支持,翻译;
Flask-Login:认证用户状态;
Flask-OpenID:认证;
Flask-RESTful:开发REST API的工具;
Flask-Bootstrap:集成前端Twitter Bootstrap框架;
Flask-Moment:本地化日期和时间;
Flask-Admin:简单而可扩展的管理接口的框架
3、Flask使用
3.1、安装
1)安装: pip install flask
2)组成:WSGI系统、调试、路由
3)模板引擎:Jinja2(由Flask核心开发者人员开发)
4)使用到装饰器:以@开头的代码方法
3.2、Flask实践使用
1、使用route进行路由转发请求
from flask import Flask# 用当前脚本名称实例化Flask对象,方便flask从该脚本文件中获取需要的内容
app = Flask(__name__)#程序实例需要知道每个url请求所对应的运行代码是谁。
#所以程序中必须要创建一个url请求地址到python运行函数的一个映射。
#处理url和视图函数之间的关系的程序就是"路由",在Flask中,路由是通过@app.route装饰器(以@开头)来表示的
@app.route("/")
#url映射的函数,要传参则在上述route(路由)中添加参数申明
def index():return "Hello World!"# 直属的第一个作为视图函数被绑定,第二个就是普通函数
# 路由与视图函数需要一一对应
# def not():
# return "Not Hello World!"# 启动一个本地开发服务器,激活该网页
app.run()
通过路由的methods指定url允许的请求格式:
from flask import Flask# 用当前脚本名称实例化Flask对象,方便flask从该脚本文件中获取需要的内容
app = Flask(__name__)#程序实例需要知道每个url请求所对应的运行代码是谁。
#所以程序中必须要创建一个url请求地址到python运行函数的一个映射。
#处理url和视图函数之间的关系的程序就是"路由",在Flask中,路由是通过@app.route装饰器(以@开头)来表示的
@app.route("/")
#url映射的函数,要传参则在上述route(路由)中添加参数申明
def index():return "Hello World!"# 直属的第一个作为视图函数被绑定,第二个就是普通函数
# 路由与视图函数需要一一对应
# def not():
# return "Not Hello World!"# 启动一个本地开发服务器,激活该网页
app.run()
通过路由的methods指定url允许的请求格式:
from flask import Flaskapp = Flask(__name__)#methods参数用于指定允许的请求格式
#常规输入url的访问就是get方法
@app.route("/hello",methods=['GET','POST'])
def hello():return "Hello World!"
#注意路由路径不要重名,映射的视图函数也不要重名
@app.route("/hi",methods=['POST'])
def hi():return "Hi World!"app.run()
通过路由在url内添加参数,其关联的函数可以接收该参数:
from flask import Flaskapp = Flask(__name__)# 可以在路径内以/<参数名>的形式指定参数,默认接收到的参数类型是string'''#######################
以下为框架自带的转换器,可以置于参数前将接收的参数转化为对应类型
string 接受任何不包含斜杠的文本
int 接受正整数
float 接受正浮点数
path 接受包含斜杠的文本
########################'''@app.route("/index/<int:id>",)
def index(id):if id == 1:return 'first'elif id == 2:return 'second'elif id == 3:return 'thrid'else:return 'hello world!'if __name__=='__main__':app.run()
除了原有的转换器,我们也可以自定义转换器(pip install werkzeug):
from werkzeug.routing import BaseConverter #导入转换器的基类,用于继承方法
from flask import Flaskapp = Flask(__name__)# 自定义转换器类
class RegexConverter(BaseConverter):def __init__(self,url_map,regex):# 重写父类定义方法super(RegexConverter,self).__init__(url_map)self.regex = regexdef to_python(self, value):# 重写父类方法,后续功能已经实现好了print('to_python方法被调用')return value# 将自定义的转换器类添加到flask应用中
# 具体过程是添加到Flask类下url_map属性(一个Map类的实例)包含的转换器字典属性中
app.url_map.converters['re'] = RegexConverter
# 此处re后括号内的匹配语句,被自动传给我们定义的转换器中的regex属性
# value值会与该语句匹配,匹配成功则传达给url映射的视图函数
@app.route("/index/<re('1\d{10}'):value>")
def index(value):print(value)return "Hello World!"if __name__=='__main__':app.run(debug=True)
endpoint的作用
- 说明:每个app中都存在一个url_map,这个url_map中包含了url到endpoint的映射;
- 作用:当request请求传来一个url的时候,会在url_map中先通过url找到endpoint,然后再在view_functions中根据endpoint再找到对应的视图函数view_func)
from flask import Flaskapp = Flask(__name__)# endpoint默认为视图函数的名称
@app.route('/test')
def test():return 'test success!'
# 我们也可以在路由中修改endpoint(当视图函数名称很长时适用)
# 相当于为视图函数起别名
@app.route('/hello',endpoint='our_set')
def hello_world():return 'Hello World!'if __name__ == '__main__':print(app.view_functions)print(app.url_map)app.run()
可以通过view_functions查看到当前endpoint与视图函数的对应情况;
可以通过url_map查看当前url与endpoint的绑定情况;
## 以下为效果
# view_functions
{'static': <function Flask.__init__.<locals>.<lambda> at 0x00000230CC2A7DC0>, 'test': <function test at 0x00000230CC30FD30>, 'our_set': <function hello_world at 0x00000230CC30FDC0>}
# url_map
Map([<Rule '/hello' (OPTIONS, HEAD, GET) -> our_set>,<Rule '/test' (OPTIONS, HEAD, GET) -> test>,<Rule '/static/<filename>' (OPTIONS, HEAD, GET) -> static>])* Serving Flask app 'endpoint_test' (lazy loading)* Environment: productionWARNING: This is a development server. Do not use it in a production deployment.Use a production WSGI server instead.* Debug mode: off* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
值得注意的是,endpoint相当于给url起一个名字,view_functions内存储的就是url的名字到视图函数的映射,且endpoint在同一个蓝图下也不能重名:
from flask import Flaskapp = Flask(__name__)@app.route('/test',endpoint='Test')
def test():return 'None'
def Test():return 'World!'if __name__ == '__main__':print(app.view_functions)print(app.url_map)app.run()
通过view_functions可以看到,即使修改endpoint为其他视图函数名,依然是绑定其正下方的视图函数,说明endpoint作用于url:
{'static': <function Flask.__init__.<locals>.<lambda> at 0x000002056C378CA0>, 'Test': <function test at 0x000002056C3E0C10>}
2、Request对象的使用
什么是request对象?
render_template():可以用于呈现一个我们编写的html文件模板
request.method用于获取url接收到的请求方式,以此返回不同的响应页面
#request:包含前端发送过来的所有请求数据from flask import Flask,render_template,request# 用当前脚本名称实例化Flask对象,方便flask从该脚本文件中获取需要的内容
app = Flask(__name__)@app.route("/",methods=['GET','POST'])
#url映射的函数,要传参则在上述route(路由)中添加参数申明
def index():if request.method == 'GET':# 想要html文件被该函数访问到,首先要创建一个templates文件,将html文件放入其中# 该文件夹需要被标记为模板文件夹,且模板语言设置为jinja2return render_template('index.html')# 此处欲发送post请求,需要在对应html文件的form表单中设置method为postelif request.method == 'POST':name = request.form.get('name')password = request.form.get('password')return name+" "+passwordif __name__=='__main__':app.run()
3、请求钩子before/after_request
想要在正常执行的代码的前、中、后时期,强行执行一段我们想要执行的功能代码,便要用到钩子函数——用特定装饰器装饰的函数。
下面将介绍Flask内,四种常用的钩子:
1、before_request:在每一次请求之前调用
- 该钩子函数表示每一次请求之前,可以执行某个特定功能的函数;
- 执行顺序是先绑定的先执行;
- 并且先执行 flask app 的 before_request, 再执行 blueprint 的 before_request;
- 一般用于检验用户权限、请求是否合法等场景;
from flask import Flask
app = Flask(__name__)@app.route('/')
def hello_world():return 'Hello World!'@app.before_request
def before_request_a():print('I am in before_request_a')@app.before_request
def before_request_b():print('I am in before_request_b')if __name__ == '__main__':app.run()# 打印结果 -=-=-=-=-=-=-=-=-=-=-=-=-=
I am in teardown_request_a
I am in teardown_request_b
2、before_first_request:与before_request的区别是,只在第一次请求之前调用
- 该钩子函数表示第一次请求之前可以执行的函数;
- 执行顺序同样也是先绑定的先执行;
# 代码替换视图函数hello_world后,if main前
@app.before_first_request
def teardown_request_a():print('I am in teardown_request_a')@app.before_first_request
def teardown_request_b():print('I am in teardown_request_b')# 打印结果 -=-=-=-=-=-=-=-=-=-=-=-=-=
I am in teardown_request_a
I am in teardown_request_b
3、after_request:每一次请求之后都会调用
- 该钩子函数表示每一次请求之后,可以执行某个特定功能的函数,这个函数接收response对象,所以执行完后必须归还response对象;
- 执行的顺序是先绑定的后执行;
- 被触发的前提是没有异常抛出,或者异常被 errorhandler捕获并处理;
- 一般可以用于产生csrf_token验证码等场景;
# 代码替换视图函数hello_world后,if main前
@app.after_request
def after_request_a(response):print('I am in after_request_a')# 该装饰器接收response参数,运行完必须归还response,不然程序报错return response@app.after_request
def after_request_b(response):print('I am in after_request_b')return response# 打印结果 -=-=-=-=-=-=-=-=-=-=-=-=-=
I am in teardown_request_b
I am in teardown_request_a
4、teardown_request:每一次请求之后都会调用
- 该钩子函数接收一个参数,该参数是服务器出现的错误信息;
- 执行顺序也是先绑定的后执行;
- 只有在请求上下文被 pop 出请求栈的时候才会直接跳转到teardown_request;
- 所以在被正常调用之前,即使某次请求有抛出错误,该请求也都会被继续执行, 并在执行完后返回 response;
# 代码替换视图函数hello_world后,if main前
@app.teardown_request
def teardown_request_a(exc):print('I am in teardown_request_a')@app.teardown_request
def teardown_request_b(exc):print('I am in teardown_request_b')# 打印结果 -=-=-=-=-=-=-=-=-=-=-=-=-=
I am in teardown_request_b
I am in teardown_request_a
4、redirect重定向
什么是redirect重定向?
在flask 中,重定向是通过flask.redirect(location, code=302)这个函数来实现的,location表示需要重定向的url, 应该配合url_for函数来使用, code表示采用哪个重定向,默认是302,即临时性重定向, 可以修改为301来实现永性重定向;
from flask import Flask,redirect,url_forapp = Flask(__name__)@app.route('/index')
def index():# redirect重定位(服务器向外部发起一个请求跳转)到一个url界面;# url_for给指定的函数构造 URL;# return redirect('/hello') 不建议这样做,将界面限死了return redirect(url_for('hello'))@app.route('/hello')
def hello():return 'this is hello fun'if __name__ == '__main__':app.run()
5、返回json给前端
1、使用make_response方法和json库共同完成
from flask import Flask,make_response,jsonapp = Flask(__name__)@app.route("/index")
def index():data = {'name':'张三'}# json.dumps 将一个python数据结构转化为json# json.dumps 序列化时对中文默认使用的ascii编码.想输出真正的中文需要指定ensure_ascii=False# 生成一个response响应对象,而不是直接return来返回响应对象,便于执行更多的后续操作response = make_response(json.dumps(data,ensure_ascii=False))# 修改数据的MIME标准类型为json(在发送数据前会先发送该类型)response.mimetype = 'application/json'return responseif __name__=='__main__':app.run()
2、jsonify库实现,减少代码行数
from flask import Flask,jsonifyapp = Flask(__name__)
# 在Flask的config是一个存储了各项配置的字典
# 该操作是进行等效于ensure_ascii=False的配置
app.config['JSON_AS_ASCII'] = False@app.route("/index")
def index():data = {'name':'张三'}return jsonify(data)if __name__=='__main__':app.run()
6、abort函数的使用
什么是abort()?
- 使用类似于python中的raise函数,可以在需要退出请求的地方抛出错误,并结束该请求;
- 我们可以使用errorhandler()装饰器来进行异常的捕获与自定义:
from flask import Flask,render_template,request,abortapp = Flask(__name__)@app.route("/",methods=['GET','POST'])
def index():if request.method == 'GET':return render_template('index.html')elif request.method == 'POST':name = request.form.get('name')password = request.form.get('password')if name == 'zhangsan' and password == '123456':return 'login sucess'else:# abort的用法类似于python中的raise,在网页中主动抛出错误abort(404)return None# 自定义错误处理方法,将404这个error与Python函数绑定
# 当需要抛出404error时,将会访问下面的代码
@app.errorhandler(404)
def handle_404_error(err):# return "发生了错误,错误情况是:%s"%err# 自定义一个界面return render_template('404.html')if __name__ == '__main__':app.run()
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<!-- 注意图片文件需要放在一个静态文件夹static里 -->
<img src="../static/error404.jpg" alt="" width="1428px" height="57px">
</body>
</html>
7、url_for的作用
1、url_for是实现url反转的工具,即视图函数 → \rightarrow →url;
2、静态文件引入:url_for(‘static’, filename=‘文件路径’)
静态文件需要存储在当前工程下的static目录内。
3、定义路由:url_for(‘模块名.视图名’,变量=参数)
参数一中的视图名实质指的是endpoint,url_map中存储了url到endpoint的映射,只是默认情况下endpoint与视图函数名相同;
如果在装饰器中修改了endpoint,则url_for只能使用endpoint设置的名字来反转url;
在单模块程序下我们可以省略模块名,但当使用了蓝图(buleprint)后,参数一就必须使用"蓝图模块名.视图名",因为不同蓝图下的视图函数可以重名。
3.3、Flask高级视图
1、add_url_rule
欲实现url与视图函数的绑定,除了使用路由装饰器@app.route,我们还可以通过add_url_rule(rule,endpoint=None,view_func=None)方法,其中:
- rule:设置的url
- endpoint:给url设置的名称
- view_func:指定视图函数的名称
因此,我们可以这样用:
def my_test():return '这是测试页面'
app.add_url_rule(rule='/test',endpoint='test',view_func=my_test)from flask import Flask,url_forapp = Flask(__name__)@app.route('/',endpoint='index')
# 底层其实是使用add_url_rule实现的
def hello_world():return 'Hello World!'def my_test():return '这是测试页面'
app.add_url_rule(rule='/test',endpoint='test',view_func=my_test)# 请求上下文只有在发送request请求时才会被激活,激活后request对象被设置为全局可访问
# 其内部封装了客户端发出的请求数据报文
# 此处是主动生成一个临时的测试请求上下文
with app.test_request_context():print(url_for('test')) # 输出结果为/testif __name__ == '__main__':app.run(debug=True)
2、类视图
之前我们所定义的视图都是通过函数来实现的,所以称之为视图函数,但其实视图还可以由类来实现,即类视图;
标准类视图:
- 定义时需要继承flask的views.View这一基类;
- 每个类视图内必须包含一个dispatch_request方法,每当类视图接收到请求时都会执行该方法,返回值的设定和视图函数相同;
- 视图函数可以通过@app.route和app.add_url_rule来进行注册(映射到url),但类视图只能通过app.add_url_rule来注册,注册时view_func不能直接使用类名,需要调用基类中的as_view方法来为自己取一个“视图函数名”
采用类视图的最大优势,就是可以把多个视图内相同的东西放在父类中,然后子类去继承父类;而类视图不方便的地方,就是每一个子类都要通过一个add_url_rule来进行注册。
下面将创建一个网站包含三个页面,每个页面中都展示相同的对联广告,py文件如下:
from flask import Flask,render_template,viewsapp = Flask(__name__)# 定义父视图类继承基类View
class Ads(views.View):def __init__(self):super(Ads, self).__init__()# 实例属性self.context={'ads':'这是对联广告!'}# 定义子视图类继承父类并实现工程
class Index(Ads):def dispatch_request(self):# 字典传参方式==不定长的关键字传参return render_template('class_mould/index.html',**self.context)
class Login(Ads):def dispatch_request(self):# 字典传参方式==不定长的关键字传参return render_template('class_mould/login.html',**self.context)
class Register(Ads):def dispatch_request(self):# 字典传参方式==不定长的关键字传参return render_template('class_mould/register.html',**self.context)# 注册我们创建的类视图,as_view给类视图起名
app.add_url_rule(rule='/',endpoint='index',view_func=Index.as_view('index'))
app.add_url_rule(rule='/login/',endpoint='login',view_func=Login.as_view('login'))
app.add_url_rule(rule='/register/',endpoint='register',view_func=Register.as_view('register'))if __name__=='__main__':print(app.view_functions)app.run(debug=True)
3、基于方法的类视图
当我们需要根据不同请求来实现不同逻辑时,用视图函数需要在内部对请求方法做判断,但我们使用方法类视图就可以通过重写其内部方法简单实现;
Flask除了基本类视图,还为我们提供了另一种类视图flask.views.MethodView,在其内部编写的函数方法即是http方法的同名小写映射
from flask import Flask,render_template,request,viewsapp = Flask(__name__)@app.route('/')
def hello_world():return render_template('index.html')# 定义LoginView类
class LoginView(views.MethodView):# 定义get函数def get(self):return render_template("index.html")# 定义post函数def post(self):username = request.form.get("username")password = request.form.get("password")if username == 'admin' and password == 'admin':return "用户名正确,可以登录!"else:return "用户名或密码错误,不可以登录!"# 注册类视图
# 未设置endpoint,则endpoint默认为as_view设置的类视图名
app.add_url_rule('/login',view_func=LoginView.as_view('loginview'))if __name__ == '__main__':print(app.url_map)app.run(debug=True)
4、装饰器的自定义与使用(方法增强使用)
装饰器本质上是一个python函数,他可以让其他函数在不需要做任何代码变得的前提下增加额外的功能,其传入参数一般是函数对象(如视图函数),返回值也是一个函数对象;
装饰器主要用于有切面需求的场景,如插入日志、性能测试、事务处理等与函数功能无关的操作,对于这些需要多次重用的代码,我们将其放置在装饰器里,就可以无需在每个函数中反复编写;
如我们要在新闻页面前插入登录操作,我们可以这样实现:
from flask import Flaskapp = Flask(__name__)@app.route('/')
def hello_world():return 'Hello World!'# 定义装饰器函数
def user_login(func):def inner():# 替代登录操作print('登录操作!')# 执行传入的函数对象func()# 此处如果return inner(),那么返回的是inner函数的执行结果# 而使用return inner,则返回的是inner函数return inner# 定义新闻页面视图函数news
def news():print('这是新闻详情页!')
# 将news函数作为参数传给装饰器函数
show_news=user_login(news)
# 因为user_login返回inner函数,所以show_news()==inner()
show_news()
# 打印出show_news的真实函数名(为inner)
print(show_news.__name__)if __name__ == '__main__':app.run(debug=True)
上述代码的运行逻辑是这样的:首先我们将新闻页面函数作为一个参数传给装饰器,装饰器将我们需要插入的登录操作与我们的视图函数包装成一个inner函数对象并返回,最后执行该对象便可以实现在新闻页面显示前执行登录操作;
其中登录操作并不是新闻页面函数的功能,且访问每一个新闻页面都应当先执行该操作,固我们将其放置在定义的装饰器中,需要添加该功能的函数使用该装饰器即可;
运行结果如下:
登录操作!
这是新闻详情页!
inner
当然上述的写法和我们平时调用装饰器的方法不太一样,我们将其变为标准的装饰器形式:
@user_login
# 定义函数news,该函数将自动被传给装饰器做参数
def news():print('这是新闻详情页!')
# 此时相当于已经执行完news=user_login(news)
news()
print(news.__name__)# show_news=user_login(news)
# show_news()
# print(show_news.__name__)
刚才我们展示的是不含参数的函数使用装饰器,对于带参数的函数我们同样也可以使用装饰器,这里要先回顾Python的可变参数:
def func(*args,**kwargs) :*:代表元组,长度不限;**:代表键值对,个数不限;*args:指用元组传参,元组内包含不定个数的位置参数;**kwargs:指用字典传参,字典内包含不定个数的关键字参数(键值对);
对于函数传参的演示略过,直接展示带参数的函数如何使用装饰器:
from flask import Flaskapp = Flask(__name__)@app.route('/')
def hello_world():return 'Hello World!'# 定义装饰器函数
def user_login(func):# inner函数接收参数def inner(*args,**kwargs):print('登录操作!')# 执行传入函数时使用inner接收到的参数func(*args,**kwargs)return inner# 不带参的不受影响
@user_login
def news():print(news.__name__)print('这是新闻详情页!')
news()# 带参的定义时预声明接收的参数
@user_login
def news_list(*args):# 获取元组args的第一个元素page=args[0]print(news_list.__name__)print('这是新闻列表页的第'+str(page)+'页!')
# 传递给args的元组即为(5,)
news_list(5)if __name__ == '__main__':app.run(debug=True)
运行后可以看到上述的两个视图函数名都已经变为inner,说明打包成功:
登录操作!
inner
这是新闻详情页!登录操作!
inner
这是新闻列表页的第5页!
上述结果也反映出代码存在一定的问题,就是不管我们定义的视图函数名称是news还是news_list,最终执行时都变为了inner。为了解决这一问题,我们可以使用
functools.wraps方法来保留原函数的属性与名称,通俗一点理解就是“不换外包装”;
方法的导入:from functools import wraps;
在自定义的装饰器下方添加一行@wraps(<形参名>)即可;
from functools import wraps# 定义装饰器函数
def user_login(func):@wraps(func)# inner函数接收参数def inner(*args,**kwargs):print('登录操作!')# 执行传入函数时使用inner接收到的参数func(*args,**kwargs)return inner
替换修改后的装饰器,运行结果如下,可以看到原视图函数名称被保留:
登录操作!
news
这是新闻详情页!登录操作!
news_list
这是新闻列表页的第5页!
5、蓝图(代码模块化)
上述类视图、装饰器分别通过继承、包装的方式减少了单个flask程序文件里重复代码的出现,实现了程序的优化;
但是这样处理后的文件内,不同功能的代码块(类视图、视图函数)仍然混杂在一起。如果要制作一个非常大型的程序项目,这样不仅会让代码阅读变得十分困难,而且不利于后期维护;
为了解决这一问题,我们需要引入蓝图(flask.Blueprint),用于实现程序功能的模块化;
导入方法:from flask import Blueprint
主路由视图函数:创建flask对象,并为拓展模块中的蓝图对象提供注册入口
from flask import Flask
from flask学习 import news,productsapp = Flask(__name__)
@app.route('/')
def hello_world():return 'hello my world !'# 将对应模块下的蓝图对象注册到app中
app.register_blueprint(news.new_list)
app.register_blueprint(products.product_list)if __name__ == '__main__':app.run(debug=True)
模块news.py
from flask import Blueprint# 实例化蓝图对象,参数一类似于蓝图对象的名称
# 一个app下的蓝图对象不可重名
new_list = Blueprint('news',__name__)# 蓝图对象的使用和app类似
# 一个蓝图下的视图函数名、endpoint不可重复
@new_list.route('/news')
def new():return '这是新闻模块!'
模块products.py
from flask import Blueprint# 实例化蓝图对象,参数一类似于蓝图对象的名称
# 一个app下的蓝图对象不可重名
new_list = Blueprint('products',__name__)# 蓝图对象的使用和app类似
# 一个蓝图下的视图函数名、endpoint不可重复
@new_list.route('/products')
def product():return '这是产品模块!'
url_prefix设置蓝图前缀
一般在蓝图对象定义时添加,为当前蓝图下的所有视图函数添加统一的前缀,这样不同蓝图下的视图函数的url就不易发生重复;
如下例添加前缀后,加载该新闻模块的url就变为"/index/news":
new_list = Blueprint('news',__name__,url_prefix='/index')@new_list.route('/news')
def new():return '这是新闻模块!'
此外,在主路由中注册蓝图时也可以为蓝图添加前缀,并且此次添加会覆写蓝图对象创建时添加的前缀;
如下例中,注册后的新闻模块的url又变为了"/test/news":
app.register_blueprint(news.new_list,url_prefix='/test')
3.4、Flask处理表单
1、使用flask-wtf处理表单
传统的前端通用表单,需要前后端共同完成操作,前端需要使用form标签来定义表单,而后端则需要使用request.form来获取post请求中的表单数据:
# 判断请求方式
if request.method == 'POST':# 获取表单中name为username的文本域提交的数据name = request.form.get('username')# 获取表单中name为password的文本域提交的数据password = request.form.get('password')return name+" "+password
上述的方法既没有为表单提供保护措施,也不利于前后端分离的改进需求,固我们引入第三方扩展包:flask-wtf与wtforms,来实现由后端单独完成的表单操作:
WTForms表单验证的基本使用
1、自定义一个表单类,继承自wtforms.Form类。
2、定义好需要验证的字段,字段的名字必须和模版中那些需要验证的input标签的name属性值保持一 致。
3、在需要验证的字段上,需要指定好具体的数据类型。
4、在相关的字段上,指定验证器。
5、以后在视图函数中,只需要使用这个表单类的对象,并且把需要验证的数据,也就是request.form 传给这个表单类,再调用表单类对象.validate()方法进行,如果返回True,那么代表用户输入的数 据都是符合格式要求的,Flase则代表用户输入的数据是有问题的。如果验证失败了,那么可以通 过表单类对象.errors来获取具体的错误信息。
from flask import
Flask,render_template,request
from wtforms import Form,StringField
from wtforms.validators import
Length,EqualTo
app = Flask(__name__)
@app.route('/')
def index():return 'Hello! '
class RegisterForm(Form):uname = StringField(validators=[Length(min=2,max=10,message='用户名长度2-10之间')])pwd = StringField(validators=[Length(min=2,max=10)])pwd2 = StringField(validators=[Length(min=2,max=10),EqualTo('pwd',message='2次密码不一致')])
@app.route('/register/', methods=['GET','POST'])
def register():if request.method == 'GET':return render_template('register.html')else:form = RegisterForm(request.form)if form.validate(): # 验证成功:True,失败:Falsereturn '验证成功!'else:return f'验证失败!{form.errors}'
if __name__ == '__main__':app.run(debug=True)
2、使用flask-restful
优势: Flask-Restful是一个专门用来写restful api的一个插件。 使用它可以快速的集成restful api接口功能。 在系统的纯api的后台中,这个插件可以帮助我们节省很多时间。
缺点: 如果在普通的网站中,这个插件就没有优势了,因为在普通的网站 开发中,是需要去渲染HTML代码的, 而Flask-Restful在每个请求中都是返回json格式的数据。
安装:pip install flask-restful
基本使用:
1、定义Restful的类视图
2、 从 flask_restful 中导入 Api ,来创建一个 api 对象。
3、写一个类视图,让他继承自 Resource 类,然后在这个里面,使用 你想要的请求方式来定义相应的方法,比如你想要将这个类视图只 能采用 post 请求,那么就定义一个 post 方法。
4、使用 api.add_resource 来添加类视图与 url 。
from flask import Flask,url_for
# pip install flask-restful
from flask_restful import Resource,Api
app = Flask(__name__)
# 建立Api对象,并绑定应用APP
api = Api(app)
class LoginView(Resource):def get(self):return {"flag":True}def post(self):return {"flag":False}
# 建立路由映射
# api.add_resource(LoginView,'/login/')
api.add_resource(LoginView,'/login/','/login2/',endpoint='login')
with app.test_request_context():# werkzeug.routing.BuildError: Could not build url for endpoint 'LoginView'.# Did you mean 'loginview' instead?# 默认没有写endpoint反向url_for函数通过小写函数名# 如果有多个url,会返回第1个URL# print(url_for('loginview'))print(url_for('login'))
if __name__ == '__main__':app.run(debug=True)
3、flask-restful参数验证
参数验证也叫参数解析 Flask-Restful插件提供了类似WTForms来验证提交的数据是否合法 的包,叫做reqparse。
1、通过 flask_restful.reqparse 中 RequestParser 建立解析器
2、通过 RequestParser 中的 add_argument 方法定义字段与解析规则
3、通过 RequestParser 中的 parse_args 来解析参数
from flask import Flask
from flask_restful import Api,Resource
from flask_restful.reqparse import
RequestParser
app = Flask(__name__)
api = Api(app)
class RegisterView(Resource):def post(self):# 建立解析器parser = RequestParser()# 定义数据的解析规则parser.add_argument('uname',type=str,required=True,help='用户名验证错误',trim=True)# 解析数据args = parser.parse_args()# 正确,直接获取参数print(args)# 错误,回馈到前端# 响应数据return {'msg':'注册成功!!'}
# 建立映射关系
api.add_resource(RegisterView,'/register/')
if __name__ == '__main__':app.run(debug=True)
add_argument方法参数详解
add_argument方法可以指定这个字段的名字,这个字段的数据类 型等,验证错误提示信息等,具体如下:
- default:默认值,如果这个参数没有值,那么将使用这个参数 指定的默认值。
- required:是否必须。默认为False,如果设置为True,那么这 个参数就必须提交上来。
- type:这个参数的数据类型,如果指定,那么将使用指定的数 据类型来强制转换提交上来的值。可以使用python自带的一些 数据类型(如str或者int),也可以使用flask_restful.inputs下的一 些特定的数据类型来强制转换。
url:会判断这个参数的值是否是一个url,如果不是,那么就会抛出异常。regex:正则表达式。date:将这个字符串转换为datetime.date数据类型。如果转换不成功,则会抛出一个异常.
- choices:固定选项。提交上来的值只有满足这个选项中的值才 符合验证通过,否则验证不通过。
- help:错误信息。如果验证失败后,将会使用这个参数指定的 值作为错误信息。
from flask import Flask
from flask_restful import
Api,Resource,inputs
from flask_restful.reqparse import
RequestParser
app = Flask(__name__)
api = Api(app)
class RegisterView(Resource):def post(self):# 建立解析器parser = RequestParser()# 定义解析规则parser.add_argument('uname',type=str,required=True,trim=True,help='用户名不符合规范') parser.add_argument('pwd',type=str,help='密码错误',default='123456')parser.add_argument('age',type=int,help='年龄验证错误!')parser.add_argument('gender',type=str,choices=['男', '女','保密'],help='性别验证错误')parser.add_argument('birthday',type=inputs.date,help='生日验证错误')parser.add_argument('phone',type=inputs.regex('^1[356789]\d{9}$'),help='电话验证错误') parser.add_argument('homepage',type=inputs.url,help='个人主页验证错误')# 解析数据args = parser.parse_args()print(args)return {'msg':'注册成功!'}api.add_resource(RegisterView,'/register/')
if __name__ == '__main__':app.run(debug=True)
3.5、Flask进阶
1、Flask设置Cookie
设置cookie
在Response的对象上设置。 flask.Response 对象有一个 set_cookie 方法,可以通过这个方法来设置 cookie 信息。
from flask import Flask, make_response
app = Flask(__name__)
@app.route('/cookie')
def set_cookie():resp = make_response('set cookie ok')resp.set_cookie('uname', 'itbaizhan')return resp
查看Cookie
在Chrome浏览器中查看cookie的方式:
方式1:借助于 开发调式工具进行查看
方式2:在Chrome的设置界面->高级设置->内容设置->所有 cookie->找到当前域名下的cookie。
from flask import request
@app.route('/get_cookie')
def get_cookie():resp = request.cookies.get('uname')return resp
删除cookie
通过 Response对象.delete_cookie ,指定cookie的key,就可以删 除cookie了。
from flask import request
@app.route('/delete_cookie')
def delete_cookie():response = make_response('helloworld')response.delete_cookie('uname')return response
2、Flask使用Session
需要先设置SECRET_KEY
class DefaultConfig(object):SECRET_KEY = 'fih9fh9eh9gh2'
app.config.from_object(DefaultConfig)
# 或者直接设置
app.secret_key='xihwidfw9efw'
设置、修改
from flask import session
@app.route('/set_session/')
def set_session():session['username'] = 'zs'return 'set session ok'
读取
@app.route('/get_session/')
def get_session():username = session.get('username')return 'get session username {}'.format(username)
删除
@app.route('/del_session/')
def delete_session():#删除指定的key的session# session.pop('uname')#删除session中的所有的key 【删除所有】session.clear()return '删除成功'
Flask设置Session的有效期
如果没有设置session的有效期。那么默认就是浏览器关闭后过期。 如果设置session.permanent=True,那么就会默认在31天后过 期。 如果不想在31天后过期,按如下步骤操作。
1、session.permanent=True
2、可以设置 app.config[‘PERMANENT_SESSION_LIFETIME’] = timedelta(hour=2) 在两个小时后过期。
3、local对象
要实现并发效果, 每一个请求进来的时候我们都开启一个进程, 这显然是不合理的, 于是就可以使用 线程 那么线程中数据互相不隔离,存在修改数据的时候数据不安全的问题
Local对象
在Flask中,类似于 request 对象,其实是绑定到了一个 werkzeug.local.Local 对象上。 这样,即使是同一个对象,那么在多个线程中都是隔离的。类似的 对象还有 session 对象。
ThreadLocal变量
Python提供了ThreadLocal 变量,它本身是一个全局变量, 但是每个线程却可以利用它来保存属于自己的私有数据, 这些私有数据对其他线程也是不可见的。
from threading import Thread,local
local =local()
local.request = '具体用户的请求对象'
class MyThread(Thread):def run(self):local.request = 'zs'print('子线程:',local.request)
mythread = MyThread()
mythread.start()
mythread.join()
print('主线程:',local.request)
4、Flask_app上下文
上下文(感性的理解)
每一段程序都有很多外部变量,只有像add这种简单的函数才是 没有外部变量的。 一旦一段程序有了外部变量,这段程序就不 完整,不能独立运行。为了能让这段程序可以运行,就要给所 有的外部变量一个一个设置一些值。就些值所在的集合就是叫 上下文。 并且上下文这一概念在中断任务的场景下具有重大意义,其中 任务在被中断后,处理器保存上下文并提供中断处理,因些在 这之后,任务可以在同一个地方继续执行。(上下文越小,延迟 越小)
举例
运行的Flask项目,每一个路由映射的内容片段,都不可以单独 拿出来使用.
当获取到了APP_Context以后,就可以直接通过程序映射的地 址访问逻辑,并且可以重复使用。
应用上下文:
应用上下文是存放到一个 LocalStack 的栈中。和应用app相关的操作就 必须要用到应用上下文
注意
在视图函数中,不用担心应用上下文的问题。因为视图函数要 执行,那么肯定是通过访问url的方式执行的, 那么这种情况下,Flask底层就已经自动的帮我们把应用上下文 都推入到了相应的栈中。
如果想要在视图函数外面执行相关的操作, 比如: 获取当前的app名称,那么就必须要手动推入应用上下文
第一种方式:便于理解的写法
from flask import Flask,current_app
app = Flask(__name__)
#app上下文
app_context = app.app_context()
app_context.push()
print(current_app.name)
@app.route('/')
def hello_world():print(current_app.name) #获取应用的名称return 'Hello World!'
if __name__ == '__main__':app.run(debug=True)
第二种方式:用with语句
from flask import Flask,current_app
app = Flask(__name__)
#app上下文
#换一种写法
with app.app_context():print(current_app.name)
@app.route('/')
def hello_world():print(current_app.name) #获取应用的名称return 'Hello World!'
if __name__ == '__main__':app.run(debug=True)
5、Flask_线程隔离的g对象
保存为全局对象g对象的好处:
g对象是在整个Flask应用运行期间都是可以使用的。 并且也跟request一样,是线程隔离的。 这个对象是专门用来存储开发者自己定义的一些数据,方便在整个 Flask程序中都可以使用。 一般使用就是,将一些经常会用到的数据绑定到上面,以后就直接 从g上面取就可以了,而不需要通过传参的形式,这样更加方便。
3.6、Flask_SQLAlchemy
数据库是一个网站的基础。 比如MySQL、MongoDB、SQLite、PostgreSQL等。
SQLAlchemy是一个基于Python实现的ORM (Object Relational Mapping,对象关系映射)框架。该框架建立在DB API (数据库应用程序接口系统) 之上,使用关系对象映射进行数据库操作。简言之便是将类和对象转换成SQL,然后使用数据API (接口) 执行SQL 并获取执行结果。
它的核心思想于在于将关系数据库表中的记录映射成为对象,以对象的形式展现,程序员可以把对数据库的操作转化为对对象的操作。
文档地址:
http://www.pythondoc.com/flask-sqlalchemy/quickstart.html
1、安装
pip install flask-sqlalchemy
如果映射所连接的是MySQL数据库,还要事先安装好PyMySQL:pip install pymsql
2、示例
有SQL语句如下:
# 建立表book
create table book('id' int(11) NOT NULL AUTO_INCREMENT, 'tiltle' varchar(50),'publishing_office' varchar(100),'isbn' varchar(4));
有book对象如下:
# 使用SQLALchemy创建表book
class Book(db.Model):__tablename__='book'id = db.Column(db.Integer, primary_key = True,autoincrement = True) #定义id字段title = db.Column(db.String(50),nullable = False) #定义title字段publishing_office = db.Column(db.String(100),nullable = False) #定义出版社字段isbn = db.Column(db.String(100),nullable = False) #定义isbn号字段storage_time = db.Column(db.DateTime, default = datetime.now) # 入库时间字段
Flask-SQLALchemy的ORM框架便可以实现将操作数据库转变为操作对象,一个book表被抽象成了一个Book类,一个表中的id、tiltle、publishing_office、isbn、storage_time字段被抽象成一个类的五个属性,而该表的一条数据记录就抽象成该类的一个实例化对象,不用再写烦琐的底层SQL语句了。
3、初始化数据库配置
要使用SQLAlchemy连接数据库,必须要进行必要的初始化配置后才能实现,数据库配置文件一般要求独立成一个文件,便于管理和移植;
配置文件:config.py
USERNAME = 'root' #设置登录账号
PASSWORD = '123456' #设置登录密码
HOST = '127.0.0.1' #设置主机地址
PORT = '3306' #设置端口号
DATABASE ='flaskdb' #设置访问的数据库# 创建URI(统一资源标志符)
'''
SQLALCHEMY_DATABASE_URI的固定格式为:
'{数据库管理系统名}://{登录名}:{密码}@{IP地址}:{端口号}/{数据库名}?charset={编码格式}'
'''
DB_URI = 'mysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME,PASSWORD,HOST,PORT,DATABASE)# 设置数据库的连接URI
SQLALCHEMY_DATABASE_URI = DB_URI
# 设置动态追踪修改,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 设置查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
上述配置文件设置完后,在flask程序文件下导入该文件,再用app.config.from_object方法导入到flask对象内即可;
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import flask学习.config # 导入配置文件app = Flask(__name__)
# 导入配置文件至flask对象
app.config.from_object(flask学习.config)'''1. 直接用键值对插入配置:(使用 localhost 替代 127.0.1:3306)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:123456@localhost/flaskdb?charset=utf8'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_ECHO'] = True
''''''2. 定义配置类后导入:(使用 localhost 替代 127.0.1:3306)
class Config:SQLALCHEMY_DATABASE_URI = 'mysql://root:123456@localhost/flaskdb?charset=utf8'SQLALCHEMY_TRACK_MODIFICATIONS = TrueSQLALCHEMY_ECHO = Trueapp.config.from_object(Config)
'''# 初始化一个SQLAlchemy对象
db = SQLAlchemy(app)
# 测试数据库连接是否成功(create_all将我们定义的所有表类映射为数据库下的表)
db.create_all()@app.route('/')
def index():return 'index'if __name__=='__main__':app.run(debug=True)
4、表模型的定义与数据库映射
SQLAlchemy允许我们依据数据库的表结构来构建数据模型,即用python类的方式定义一个表模型,再通过调用create_all()方法,就可以将我们定义的所有表模型全部映射为数据库下的表;
但要注意的是每次运行create_all()这行代码时都会将所有表创建一次,导致重复创建表的问题,可以在前面添加一行drop_all()代码来删除已经创建的表;
在用类的方式定义表模型时,使用__tablename__='<表名>'来将字符串对象设为表名,使用<列名>=db.Column()来将Column类的实例对象设为表的字段;我们为字段(列)指定的数据类型和约束条件,作为Column实例化时的参数;
利用上述知识点,就可以实现SQLAlchemy创建表了,并可以尝试着进行插入记录的操作SQLAlchemy.session.add():
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import flask学习.config
from datetime import * # datatime库下的datatime类重名了app = Flask(__name__)
app.config.from_object(flask学习.config)# 初始化一个SQLAlchemy对象(该步要在导入config后执行)
# 实例化的同时将与数据库相关的配置读入
db = SQLAlchemy(app)
# 初始化app对象中与数据库相关的配置的设置,防止数据库连接泄露
db.init_app(app)# 创建表模型类对象
class Book(db.Model):__tablename__='book'id = db.Column(db.Integer, primary_key = True,autoincrement = True) # 定义id字段title = db.Column(db.String(50),nullable = False) # 定义title字段publishing_office = db.Column(db.String(100),nullable = False) # 定义出版社字段price = db.Column(db.String(30), nullable=False) # 定义price号字段isbn = db.Column(db.String(50),nullable = False) # 定义isbn号字段storage_time = db.Column(db.DateTime, default = datetime.now) # 入库时间字段# 删除数据库下的所有上述定义的表,防止重复创建
db.drop_all()
# 将上述定义的所有表对象映射为数据库下的表单(创建表)
db.create_all()# 添加数据的路由
@app.route('/add')
def add_record():book1 = Book(title='Python基础教程(第3版)', publishing_office ='人民邮电出版社', price = '68.30 ', isbn = '9787115474889')book2= Book(title='Python游戏编程快速上手第4版',publishing_office = '人民邮电出版社', price = '54.50', isbn = '9787115466419')book3 = Book(title='JSP+Servlet+Tomcat应用开发从零开始学',publishing_office = '清华大学出版社', price = '68.30', isbn = '9787302384496')db.session.add(book1)db.session.add(book2)db.session.add(book3)# 需要提交事务给数据库db.session.commit()return 'add success!'# 查找数据的路由
@app.route('/query')
def query_record():# 查找id=1的第一个对象result = Book.query.filter(Book.id == '1').first()print(result.title)# 查找publishing_office=人民邮电出版社的全体对象result_list = Book.query.filter(Book.publishing_office == '人民邮电出版社').all()for books in result_list:print(books.title)return 'query success!'# 修改数据的路由
@app.route('/edit')
def edit_record():# 查找id=1的第一个对象book1 = Book.query.filter(Book.id == '1').first()book1.price = 168# 需要提交事务给数据库db.session.commit()return 'edit success!'# 删除数据的路由
@app.route('/delete')
def delete_record():# 查找id=9的第一个对象book2 = Book.query.filter(Book.id == '9').first()db.session.delete(book2)# 需要提交事务给数据库db.session.commit()return 'delete success!'if __name__ == '__main__':app.run(debug=True)
5、init_app作用
在数据增删改查部分,我们使用到了一行代码:db.init_app(),当时只知道它是初始化数据库的相关配置,却不知道它实际上干了什么。为了后续解决Flask循环引用的问题,这些基础的语句就必须要弄清楚。
关于init_app(),官方文档给出的解释是这样的:This callback can be used to initialize an application for the use with this database setup. Never use a database in the context of an application not initialized that way or connections will leak.
意思是说:此回调函数可将应用程序中对于此数据库的设置进行初始化。切勿在未以这种方式初始化的应用程序上下文中使用数据库,否则(数据库)连接将泄漏。
未完待续
参考文章:
https://blog.csdn.net/wly55690/article/details/131683846
https://blog.csdn.net/Javachichi/article/details/131826212