一、简介
Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
本篇文章将对比与django介绍flask的基本组件以及相关使用方法。
-
Django功能大而全,Flask只包含基本的配置 Django的一站式解决的思路,能让开发者不用在开发之前就在选择应用的基础设施上花费大量时间。Django有模板,表单,路由,认证,基本的数据库管理等等内建功能。与之相反,Flask只是一个内核,默认依赖于两个外部库: Jinja2 模板引擎和 Werkzeug WSGI 工具集,其他很多功能都是以扩展的形式进行嵌入使用。
-
Flask 比 Django 更灵活 用Flask来构建应用之前,选择组件的时候会给开发者带来更多的灵活性 ,可能有的应用场景不适合使用一个标准的ORM(Object-Relational Mapping 对象关联映射),或者需要与不同的工作流和模板系统交互。
- 组件对比:
django:无socket、依赖第三方模块wsgi、中间件、路由系统、视图、ORM、cookie、session、Admin、Form、缓存、信号、序列化。
Flask: 无socket、中间件(扩展)、路由系统、视图(第三方模块,依赖jinja2)、cookie、session。
二、快速开始
安装
pip3 install flask
简单使用
#!/usr/bin/env python3
#_*_ coding:utf-8 _*_
#Author:wd
from flask import Flask
app = Flask(__name__) # 实例化Flask对象
@app.route('/') #添加路由
def hello_world():
return 'Hello World!' # 返回
if __name__ == '__main__':
app.run() # 运行服务器
三、配置处理
配置方式
flask的配置文件是通过Flask对象的config.Configs进行配置,本质是字典其中配置文件有以下实现方式:
方式一:
app.config['DEBUG'] = True
# PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...)
方式二:
app.config.from_pyfile("python文件名称")
如:
settings.py
DEBUG = True
app.config.from_pyfile("settings.py")
app.config.from_envvar("环境变量名称")#环境变量的值为python文件名称名称,内部调用from_pyfile方法
app.config.from_json("json文件名称") #JSON文件名称,必须是json格式,因为内部会执行json.loads
app.config.from_mapping({'DEBUG':True}) ###字典格式
app.config.from_object("python类或类的路径”) ###推荐
###示例:
app.config.from_object('pro_flask.settings.TestingConfig')
settings.py
class Config(object):
DEBUG = False
TESTING = False
DATABASE_URI = 'sqlite://:memory:'
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
#PS: 从sys.path中已经存在路径开始写
#更多配置参数参考:http://flask.pocoo.org/docs/1.0/config/#builtin-configuration-values
四、路由系统
flask的路由系统可能区别与django之一在于其路由的实现是由装饰器完成的。
使用方式
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from flask import Flask
app=Flask(__name__)
#####方式一,装饰器方式,endpoint相当于别名,用于反向生成url
@app.route(rule='/index',endpoint='a1')
def index():
return 'index'
def index1():
return 'index1'
####方式二,使用函数方式
app.add_url_rule(rule='/index1',endpoint='a2',view_func=index1)
常用参数
- rule URL规则
- view_func 视图函数名称
- defaults=None 默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数
- endpoint=None url别名,用于反向生成URL,即: url_for(‘别名’)
- methods=None 允许的请求方式,如:methods=["GET","POST"]
- strict_slashes=None 对URL最后的 / 符号是否严格要求,默认严格,设置为False即最后带/或不带都能匹配
- redirect_to=None 重定向到指定新地址如:redirect_to='/<user>/login’,当跳转的url有参数时候,也需要加上参数
路由规则
通过把 URL 的一部分标记为 <variable_name> 就可以在 URL 中添加变量。标记的 部分会作为关键字参数传递给函数。通过使用 <converter:variable_name> ,可以 选择性的加上一个转换器,为变量指定规则。
@app.route('/user/<username>') # 普通规则,username相当于变量,在url中指定例如/user/wd,username就是wd
def show_user_profile(username):
return 'User %s' % username
@app.route('/post/<int:post_id>') # 整型转换器,post后面只能是int类型,否则404
def show_post(post_id):
return 'Post %d' % post_id
@app.route('/path/<path:subpath>') # 路径转换器
def show_subpath(subpath):
return 'Subpath %s' % subpath
内置转换器
DEFAULT_CONVERTERS = {
'default': UnicodeConverter, #默认数据类型,但不包含斜杠的文本
'string': UnicodeConverter, #默认数据类型
'any': AnyConverter, #多个路径
'path': PathConverter, #类似字符串,可以包含斜杠
'int': IntegerConverter, #只能是int类型
'float': FloatConverter, #只能是float类型
'uuid': UUIDConverter, #只能是uuid字符串
}
URL反向生成
在django中我们可以通过reverse方法反向生成url,同样在flask也可以通过url_for反向生成。
# -*- coding:utf-8 -*-
# Author:wd
from flask import Flask,url_for
app = Flask(__name__)
@app.route('/user/<username>',endpoint='a1')
def show_user_profile(username):
print(url_for('a1',username='jack', next='/')) #可根据需要传递参数,第一个参数是视图函数或者endpoint,第二个参数是需要传递的参数,next根据需求
print(url_for('a1', username='jack'))
return 'User %s' % username
if __name__ == '__main__':
app.run()
#结果
#/user/jack?next=%2F
#/user/jack
自定义URL规则
扩展自己的自定义URL规则需要继承BaseConverter,重写to_url方法。
from flask import Flask,url_for
app = Flask(__name__)
# 定义转换的类
from werkzeug.routing import BaseConverter
class RegexConverter(BaseConverter):
"""
自定义URL匹配正则表达式
"""
def __init__(self, map, regex):
super(RegexConverter, self).__init__(map)
self.regex = regex
def to_python(self, value):
"""
路由匹配时,匹配成功后传递给视图函数中参数的值
:param value:
:return:
"""
return int(value)
def to_url(self, value):
"""
使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
:param value:
:return:
"""
val = super(RegexConverter, self).to_url(value)
return val
# 添加到converts中
app.url_map.converters['regex'] = RegexConverter
# 进行使用
@app.route('/index/<regex("\d+"):nid>',endpoint='xx')
def index(nid):
url_for('xx',nid=123) #反向生成,就会去执行to_url方法
return "Index"
if __name__ == '__main__':
app.run()
五、视图
CBV和FBV
在django中视图分为CBV和FBV,当然Flask视图也分CBV和FBV
FBV
###方式一:
@app.route('/index',endpoint=‘a1')
def index(nid):
return "Index"
###方式二:
def index(nid):
return "Index"
app.add_url_rule('/index',index)
CBV
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from flask import url_for, views, Flask
app = Flask(__name__)
def auth(func): # 装饰器
def inner(*args, **kwargs):
print('require auth')
result = func(*args, **kwargs)
return result
return inner
class IndexView(views.MethodView):
methods = ['GET', 'POST'] # 只允许GET、POST请求访问
decorators = [auth, ] # 如果想给所有的get,post请求加装饰器,就可以这样来写,也可以单个指定
def get(self): # 如果是get请求需要执行的代码
v = url_for('index')
print(v)
return "GET"
def post(self): # 如果是post请求执行的代码
return "POST"
app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name指定的是别名,会当做endpoint使用
if __name__ == '__main__':
app.run()
使用装饰器
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from flask import url_for, views, Flask
app = Flask(__name__)
def auth(func): # 装饰器
def inner(*args, **kwargs):
print('require auth')
result = func(*args, **kwargs)
return result
return inner
####FBV装饰器
@app.route(‘/login', methods=['GET', 'POST'])
@auth # 注意如果要给视图函数加装饰器,一点要加在路由装饰器下面,才会被路由装饰器装饰
def login():
return 'login'
class IndexView(views.MethodView):
methods = ['GET', 'POST'] # 只允许GET、POST请求访问
####CBV装饰器
decorators = [auth, ] # 如果想给所有的get,post请求加装饰器,就可以这样来写,也可以单个指定
def get(self): # 如果是get请求需要执行的代码
v = url_for('index')
print(v)
return "GET"
def post(self): # 如果是post请求执行的代码
return "POST"
app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name指定的是别名,会当做endpoint使用
if __name__ == '__main__':
app.run()
六、请求与响应
在django中通过request获取请求信息通过render、httpresponse等响应数据,同样在flask中也是通过request来获取请求数据,requset需要导入。
请求相关
#导入 from flask import request
####请求相关信息
request.method:获取请求方法
request.json.get("json_key"):获取json数据 **较常用
request.argsget('name') :获取get请求参数
request.form.get('name') :获取POST表单请求参数
request.form.getlist('name_list'):获取POST表单请求参数列表(如多选)
request.values.get('age') :获取GET和POST请求携带的所有参数(GET/POST通用)
request.cookies.get('name'):获取cookies信息
request.headers.get('Host'):获取请求头相关信息
request.path:获取用户访问的url地址,例如(/,/login/,/ index/);
request.full_path:获取用户访问的完整url地址+参数 例如(/login/?name=wd)
request.url:获取访问url地址,例如http://127.0.0.1:5000/?name=18
request.base_url:获取访问url地址,例如 http://127.0.0.1:5000/;
request.url_root:不带参数的根url,例如 http://127.0.0.1:5000/;
request.host_url:不带参数的根host,当使用ip方式访问的时候同url_root例如 http://127.0.0.1:5000/;
request.host:获取主机地址
request.files:获取用户上传的文件
obj = request.files['the_file_name']
obj.save('/var/www/uploads/' + secure_filename(f.filename)) 直接保存文件
响应相关
################基本响应类型
return "字符串" :响应字符串
return render_template('html模板路径',**{}):响应模板
return redirect('/index.html'):跳转页面
##############JSON格式响应
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from flask import url_for, views, Flask, request, Response
import json
app = Flask(__name__)
@app.route('/login', methods=['GET', 'POST'])
def login():
response = Response(json.dumps({"status":"ok"}), mimetype="application/json;charset=utf-8")
return response
if __name__ == '__main__':
app.run()
##################设置响应信息
#导入
from flask import make_response
response = make_response(render_template('index.html’)) #也可以相应模版
response.delete_cookie('key’) #删除cookie中的一个key
response.set_cookie('key', 'value’) #设置cookie
response.headers['username'] = ‘wd’ #设置响应头信息
return response
七、模版
flask的模版引擎和django一样都采用Jinja2(参考:http://jinja.pocoo.org/docs/2.10/templates/)
获取单个数据
{{ key }}
for循环
{% for item in item_list %}
<a>{{ item }}</a>
{% endfor %}
demo:
<html>
<body>
<!-- 列表循环 -->
{% for i in k1 % }
<h1>{{ i }}</h1>
{% endfor % }
<!-- 字典循环 -->
{% for k in k2.keys % }
<h1>{{ k }}</h1>
{% endfor % }
{% for v in k2.values % }
<h1>{{ v }}</h1>
{% endfor % }
{% for k,v in k2.items % }
<h1>{{ k }}-{{ v }}</h1>
{% endfor % }
</body>
</html>
判断
{% if ordered_warranty %} {% else %} {% endif %}
模版继承(extends)
母模版:
{% block name %}
{% endblock %}
使用:
先声明:
{% extends 'html模版' %}
{% block name %}
{% endblock %}
宏
当某个元素多次使用时候,我们可以定义宏
{% macro input(name, type='text', value='') %}
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}
{{ input('n1') }}
解除XSS
如果防止HTML显示为字符串,flask采用Markup实现,而django采用make_safe。
###后端
return Markup("<input type='text' value='wd'>")
###前端
{{"<input type='text'>"|safe}}
过滤器
Flask的Jinjia2可以通过Context 把视图中的函数传递把模板语言中执行,这就是Django中的simple_tag和simple_fifter功能。
simple_tag(只能传2个参数,支持for、if)
#!/usr/bin/env python3
#_*_ coding:utf-8 _*_
#Author:wd
from flask import Flask,render_template
app = Flask(__name__)
@app.route('/index')
def index():
return render_template('index.html')
@app.template_global() #simple_tag
def foo(arg):
return "<input type='text' value='{}'>".format(arg)
if __name__ == '__main__':
app.run()
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>{{foo('wd')|safe }}</div>
</body>
</html>
simple_fifter(对参数个数无限制,不支持for、if)
#!/usr/bin/env python3
#_*_ coding:utf-8 _*_
#Author:wd
from flask import Flask,render_template,Markup
app = Flask(__name__)
@app.route('/login')
def login():
return render_template('login.html')
@app.template_filter() #simple_fifter
def bar(arg1,arg2,arg3):
return Markup("<input type='text' value='{}'>".format(arg1+arg2+arg3))
if __name__ == '__main__':
app.run()
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>{{ 'wd'|bar('name','is ')}}</div>
</body>
</html>
八、session
除请求对象之外,还有一个 session 对象,本质上来说就是一个字典。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,需要设置一个密钥。
相关操作
#设置session:session[‘username'] = ‘wd’
#删除session:del session[‘username’]或者session.pop(‘username’)
#清空session:session.clear()
简单的登录验证demo:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from flask import session, Flask, request, redirect, render_template
app = Flask(__name__)
app.secret_key = 'adsadq2dq' # 设置加密的盐
app.config['SESSION_COOKIE_NAME'] = 'session_wd' # 设置session的名字
def auth(func): # 登录认证装饰器
def inner(*args, **kwargs):
if not session.get('username'): return redirect('/login')
return func(*args, **kwargs)
return inner
@app.route('/index', methods=['GET', 'POST'])
@auth
def index():
return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == "GET":
return render_template('login.html')
if request.form.get('username') == 'wd' and request.form.get('password') == '123':
session['username'] = 'wd'
return redirect('/index')
@app.route('/logout')
def logout():
session.clear()
return redirect('/login')
if __name__ == '__main__':
app.run()
session配置
配置方法在之前已经提到,如 app.config['SESSION_COOKIE_NAME'] = 'session_wd'
'SESSION_COOKIE_NAME': 'session’, # session 名称配置
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_REFRESH_EACH_REQUEST': True, #是否每次都跟新
'PERMANENT_SESSION_LIFETIME': timedelta(days=31) #设置seesion超时时间
九、 blueprint(蓝图)
正如上面介绍,flask简便易用,一个py文件就可以完成一个小项目,当项目相对大时候,我们就需要将目录进行结构划分,蓝图的功能就在于此。
使用
目录结果
monitor #项目主目录
├── __init__.py
├── runserver.py #启动脚本
├── statics #静态文件
├── templates #模版目录
└── views #视图目录
├── account.py
└── permission.py
各个py文件:
from flask import Flask
from .views import account
from .views import permission
app = Flask(__name__, template_folder='templates', static_folder='statics', static_url_path='/static')
app.register_blueprint(blueprint=account.login_bp,url_prefix='/account’) #注册一个蓝图
app.register_blueprint(blueprint=permission.permit_bp, url_prefix='/permit')
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from flask import Blueprint
login_bp=Blueprint('login',__name__) #实例化一个蓝图
@login_bp.route('/login',methods=['GET','POST'])
def login():
return '登录页'
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from monitor import app
if __name__=='__main__':
app.run()
十、消息闪现(flash)
Flask 的闪现系统提供了一个良好的反馈方式。闪现系统的基 本工作方式是:在且只在下一个请求中访问上一个请求结束时记录的消息。一般我们结合布局模板来使用闪现系统。消息闪现原理是flask的 session组件而该组件是基于cookie的,浏览器会限制 cookie 的大小,有时候网络服 务器也会。这样如果消息比会话 cookie 大的话,那么会导致消息闪现静默失败。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from flask import Flask, flash, get_flashed_messages, request, render_template, redirect, url_for
app = Flask(__name__)
app.secret_key = '123addqe1'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == "POST":
if request.form.get('username') == 'wd' and request.form.get('password') == '123':
flash(message='wd') # 设置消息
return redirect(url_for('index'))
else:
flash(message='用户名或者密码错误')
return render_template('login.html')
@app.route('/index', methods=['GET', 'POST'], endpoint='index')
def index():
messge = get_flashed_messages() # 获取消息
return 'ok username {}'.format(''.join(messge))
if __name__ == "__main__":
app.run()
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post">
<div><input type="text" name="username"></div>
<div><input type="password" name="password"></div>
<div><input type="submit" name="password"></div>
<div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
</div>
</form>
</body>
</html>
消息分类
闪现消息还可以指定类别,如果没有指定,那么缺省的类别为 'message' 。不同的类别可以给用户提供更好的反馈,获取改级别的时候需要加参数with_categories=True。例如错误消息可以error,此时的消息是一个tuple。
示例:
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == "POST":
if request.form.get('username') == 'wd' and request.form.get('password') == '123':
flash(message='wd') # 设置消息
return redirect(url_for('index'))
else:
flash(message='用户名或者密码错误',category='ERROR')
print(get_flashed_messages(with_categories=True)) # 结果[('ERROR', '用户名或者密码错误')]
return render_template('login.html')
消息过滤
如果想得到某个指定类别或者多个类别的消息则在获取的时候使用参数category_filter
messge = get_flashed_messages(category_filter=["ERROR",]) # 获取指定类型的消息
十一、请求扩展(内置请求钩子)
flask的请求钩子是通过装饰器实现的,通过这些钩子函数我们可以在请求的每个阶段执行自己的业务逻辑。
以下是常用请求扩展装饰器:
- @app.before_first_request :请求第1次到来执行1次,之后都不执行;
- @app.before_request:请求到达视图之前执行,如果此函数有返回值则直接返回(不执行视图函数),如果此方法定义了多个则最上面的开始执行;
- @app.after_request:请求经过视图之后执行,如果改方法从上到下定义了多个,则最下面的先执行;
- @app.errorhandler: 请求错误时候的处理,error_code代表http状态码;
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from flask import Flask, Request, render_template
app = Flask(__name__, template_folder='templates')
@app.before_first_request # 第1个请求到来执行
def before_first_request1():
print('before_first_request1')
@app.before_request # 中间件2
def before_request1():
Request.nnn = 123
print('before_request1') # 不能有返回值,一旦有返回值在当前返回
@app.before_request
def before_request2():
print('before_request2')
@app.errorhandler(404) # 404错误处理
def page_not_found(error):
return 'This page does not exist', 404
@app.route('/')
def index():
return "Index"
@app.after_request # 中间件 执行视图之后
def after_request1(response):
print('after_request1', response)
return response
@app.after_request # 中间件 执行视图之后 先执行 after_request2
def after_request2(response):
print('after_request2', response)
return response
if __name__ == '__main__':
app.run()
#结果:
#before_request1
#before_request2
#after_request2 <Response 5 bytes [200 OK]>
#after_request1 <Response 5 bytes [200 OK]>