快速使用
该篇博客简单介绍下Flask的快速使用!
# Flask与Django
Q: Flask与Django有何区别?
我们最直观的感受是, Django很大,依赖很多,迁移后会生成一大堆的表; Flask一开始很简单,但项目越来越大,东西也会越来越多!
1. Django是一个大而全的框架,Flask是一个轻量级的框架.
2. Django的内部为我们提供了非常多的组件:
ORM、Session、Cookie、admin、form、modelform、路由、视图、模版、中间价、分页、auth、contenttype、缓存、信号、数据库连接
3. Flask框架本身没有太多的功能: 路由、视图、session、中间件
- Flask本身没有模版,用的是三方的jinja2
- Flask的Session 不会像Django一样, 不会将 随机字符串:用户相关信息 放在数据库中
- Flask有非常齐全的第三方组件!!
4. ★ Django和Flask两者最大的不同在于:
> 请求一来,Django对请求是逐一封装和传递的;在代码中体现为每个视图函数都有一个request参数;
> 请求一来,Flask的请求是利用上下文的管理来实现的!!(简单来说,就是将请求放在某个特殊的地方,想用时自己去那里拿就行..
2
3
4
5
6
7
8
9
10
# Flask的安装
pip install flask
Flask 1.1.1
Click 7.0
itsdangerous 1.1.0
Jinja2 2.10.3 # - 模版
MarkupSafe 1.1.1
Werkzeug 0.16.0 # - wsgi,本质就是soket套接字层
"""
为了方便学习,我安装的是Flask1.1.1版本,安装时 它会配套安装一些依赖,默认安装的依赖版本是最新的.
这导致了这些依赖版本跟Flask1.1.1版本不匹配,运行程序时会报错.
需要按照上方的版本号重新安装这些依赖!!
pip install Flask==1.1.1
pip install Click==7.0
pip install itsdangerous==1.1.0
pip install Jinja2==2.10.3
pip install MarkupSafe==1.1.1
pip install Werkzeug==0.16.0
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Hello World!
from flask import Flask
app = Flask(__name__) # - 创建flask对象,里面随便写个字符串也可以,但通常会写__name__代表模块
# - 路由匹配和视图函数 (Django的路由匹配和视图函数是分开的
@app.route('/index') # http://127.0.0.1:5000/index 能访问的; http://127.0.0.1:5000/index/ 访问不到!
def index(): # ★视图函数为啥没有request参数?因为flask的请求不像Django的请求是传递过来的,它请求是放在某地,自取
return "hello world" # 返回一个字符串,相当于Django里的HttpResponse
if __name__ == '__main__':
app.run()
2
3
4
5
6
7
8
9
10
11
12
13
# Werkzeug
探究下 Werkzeug 这个wsgi (web服务网关接口)在flask中的作用.. <flask的源码流程也是这样的!>.
下方截图的Werkzeug程序, 仅是一个socket套接字层(完成传输层及以下的功能)
网站没有依赖flask, 但是却执行起来了, 无路由匹配, 无视图函数的处理.. 就是简单的接收请求,返回请求..
看下Flask源码, 它就是像上面这样构造的.
# - t1.py
from werkzeug.serving import run_simple
def func(environ, start_response):
start_response('200 ok', [('Content-Type', 'text/html')])
return [b'<h6>hello world!</h6>']
if __name__ == "__main__":
run_simple('localhost', 5001, func)
# - t2.py
from werkzeug.serving import run_simple
class Flask:
def __call__(self, environ, start_response):
# To do:执行flask源码里的功能.
start_response('200 ok', [('Content-Type', 'text/html')])
return [b'<h6>hello world!</h6>']
app = Flask()
if __name__ == "__main__":
run_simple('localhost', 5002, app)
# - t3.py
from werkzeug.serving import run_simple
class Flask:
def __call__(self, environ, start_response):
# To do:执行flask源码里的功能.
start_response('200 ok', [('Content-Type', 'text/html')])
return [b'<h6>hello world!</h6>']
def run(self):
run_simple('localhost', 5003, self)
app = Flask()
if __name__ == "__main__":
app.run()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
★ So, 注意两点:
-1- flask框架是基于Werkzeug这个wsgi实现的, flask本身跟Django一样是没有wsgi的.
-2- 用户请求一来, 就会执行 flask对象所在的Flask类中的 __call__
方法, 该方法中会进行 路由匹配、运行视图函数等步骤..
# Flask四把斧
from flask import Flask, render_template, jsonify, redirect
# template_folder默认值就是templates,意味着jinja2默认会去templates目录下找对应的html文件,当然你可以自定义
app = Flask(__name__, template_folder="templates")
@app.route('/test1')
def test1():
return "hello world" # 返回一个字符串,相当于Django里的HttpResponse
@app.route('/test2')
def test2():
return render_template('login.html') # 返回一个模版,相当于Django里的render
@app.route('/test3')
def test3():
return jsonify({'code': 200, 'data': [1, 2, 3]}) # 返回一个json字符串,相当于Django里的JsonResponse
@app.route('/test4')
def test4():
return redirect('/test2') # 重定向,相当于Django里的redirect
if __name__ == '__main__':
app.run()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 用户登陆&用户管理
这是个最最最简单的程序,工作中不会遇到比这个还要简单的需求了..emmm
需求:
-1- GET请求访问登陆页面, 填写表单信息后, POST请求到当前路由/url, 对用户名密码进行验证.
若正确, 则登陆成功, 重定向跳转到首页; 若不正确, 携带错误信息重新访问登陆页面!!
-2- GET请求首页循环展示出所有用户信息
-3- 点击编辑按钮, GET请求携带 当前编辑的用户信息 访问修改页面 并将信息呈现在页面上..
在页面上修改后, 点击提交, POST请求到当前路由/url(依旧是这个视图函数), 视图执行修改逻辑, 重定向到首页..
-4- 点击删除按钮, 直接删除, 重定向到首页..
PS: 该示例没有用数据库, 用data_dict字典充当数据库了..
☆注意点:
- @app.route() 若不写methods参数,那么装饰的视图函数仅支持GET请求!当post请求时会报错: Method Not Allowed
- command+B点进去看render_template函数
>> def render_template(template_name_or_list, **context):...
你可以发现有个参数是 **context , 因而 我们传递错误信息时,若以字典形式传递则需要拆包!!还可以用关键字参数的形式传递 error=error
- 在模版中
字典取键值对, Django的模版 data_dict.items 不加括号; flask模版中 data_dict.items() 需要加括号
字典取值, Django的模版 v.name ; flask模版中 v.name、v["name"]、v.get('name',"未知") 皆可
- 编辑和删除时,构建a标签跳转的url,需传递nid表明编辑修改的是哪一条数据,有两种方式
1> ?params 视图函数中通过 request.args 取到的nid的值是 str 类型!(因为是通过网络传过来的嘛
2> 路由传参 <int:nid> 表明接收一个值,内部会转换成int类型
- request.form 对标 request.POST
request.args 对标 request.GET
- flask也有name反向解析 endpoint + url_for 搭配使用, endpoint参数默认值就是视图函数名,注意endpoint不要重名!!
- 在form标签里没有写action,表明向 当前页面/当前url 提交!
2
3
4
5
6
7
8
9
10
11
12
13
14
# ★ 小结
至此, 我们学到了哪些flask知识点?
◎ flask路由
@app.route('/login', methods=['GET', 'POST'], endpoint='login')
def login():pass
- ★ flask的路由是装饰器
- 三个参数 url methods endpoint
注意,endpoint不写,其默认值就是视图函数名; ★ endpoint不要重名!!
2
3
4
5
6
◎ 动态路由
@app.route('/delete/<nid>') # nid值的类型是str <a href="/delete/{{ k }}">删除</a>
def delete(nid):pass
@app.route('/delete/<int:nid>') # nid值的类型是int <a href="/delete/{{ k }}">删除</a>
def delete(nid):pass
2
3
4
5
◎ 获取用户传递的数据 - 两类: url传递 + 请求体传递
from flask import request
@app.route('/login')
def login():
request.args # GET形式传递的数据 - ?params <a href="/delete?nid={{ k }}">删除</a>
request.form # POST形式传递的数据 - 请求体数据
2
3
4
5
6
◎ 视图返回数据
from flask import Flask, render_template, jsonify, redirect, url_for
@app.route('/login')
def login():
return "hello world"
return render_template('login.html')
return redirect('/index') # return redirect(url_for('index'))
return jsonify({'code': 200, 'data': [1, 2, 3]})
2
3
4
5
6
7
8
◎ 模版处理
{{ x }}
{% for item in data_list %}
{{ item }}
{% endfor %}
flask的模版中的注释
<!-- <td>{{ key }}</td> --> 不生效!!
{# <td>{{ key }}</td> #} 生效!!
2
3
4
5
6
7
8
9
# Flask - Session
# ※ Token回顾
☆ 简单回顾下 Django的cookie、session、jwt
Http请求是无状态的短链接,一问一答.
需求:有些页面登录成功后才能访问,服务端怎么知道你是否已经登录呢?/怎么知道你是谁呢?
★ 首先明确一点:Token是一种思想,实现用户会话信息保存的技术都可叫作Token.
Token大抵分为两类,一类是将用户信息保存在浏览器里的cookie技术;一类是将用户信息保存在服务端里的Session技术!!
(jwt token可划分到cookie这一类中
- cookie
登陆,用户名密码匹配成功,将用户信息后返回给浏览器,浏览器将其保存在cookies中 >> name:dc
浏览器访问其他页面时,会携带cookies信息,服务端拿到cookies信息后,取出里面的用户信息就知道是谁登陆了.
- 缺点: cookie最多支持4k;用户信息在客户端,与session比相对不安全;
注意:
上述是最最简单的cookie流程,这么做我们很容易伪造其它人登陆,为了增加安全性,我们通常可以这么做:
1. 服务端 sha256(用户信息 + 盐) 生成 签名, base64(用户信息)和签名组成cookie给浏览器.
2. 服务端 取出cookie中的 用户信息解密后, 用同样的方式 生成一个签名 与 cookie中的签名进行比对!
>> 通过验证签名来确保 Cookie 的完整性和有效性!!!
PS:加盐 - 在密码任意固定位置插入特定字符串,有效防止黑客暴力破解用户ID和密码!!
- session
登陆,用户名密码匹配成功,执行语句 request.session['username'] = "dc"
该语句底层会让 服务端生成一个随机字符串 并保存到数据库中 >> 随机字符串:该用户的信息(会加密)
这个随机字符串还会一并返回给浏览器,浏览器会将其保存在cookies中 "session_id":随机字符串
浏览器访问其他页面时,会携带cookies信息; 服务端取到随机字符串,去数据库对比,将保存的对应用户信息解密并赋值给request.session
- 优点: 用户信息在服务端,相对更安全
- 缺点: 占有服务器存储空间
- jwt
简单来说, jwt有三段 base64(header)--base64(payload)--SHA256(header + "." + payload , 盐)
然后验证 拿到第一段和第二段后以同样的方式加盐加密,与第三段进行比较!!
- 优点: 计算换资源嘛 而且就算被截获了,黑客改了payload里的用户信息,验证时肯定也是通过不了的,安全性高!!
- 缺点: 不到期jwtToken不失效
- 前后端分离,前端存数据的三个地方
1. sessionStorage - 临时存储, 浏览器关闭或关闭标签页 就没啦!
2. localStorage - 永久存储, 除非手动清理/浏览器存储空间满了!
3. cookie - 有个过期时间, 过期时间一到就没啦! + 发送请求时会自动携带它.
思考:首先要清楚session也是基于cookie来使用的,浏览器的cookie何时失效?
- 若没有设置过期时间,即为会话cookie,关闭浏览器,浏览器上的cookie消失; 会话cookie一般保存在内存里.
- 若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再打开浏览器,这些cookie依然有效直到超过设定的过期时间.
思考:用户的浏览器禁用cookie,如何实现Token呢?
浏览器拿到后端给的Token,有三个地方可以存. 若禁用cookie,浏览器将Token传递给后端的方式有两种:
- 将Token放到url地址后面, 以参数的形式传递过去
- 将Token放到请求头里传递过去
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# ※ 装饰器回顾
改变func.__name__
的值; 多个装饰器的执行顺序..
清楚上面截图中的两点后, 代入Flask装饰器里!
from flask import Flask
app = Flask(__name__)
# ★ 示例代码从上往下执行,运行到@app.route('/index')
# 1. 会先取路由'/index' 2.再取装饰器装饰的函数 > 这两者作一个对应关系,路由匹配成功后,执行对应的函数
# 3. 获得被装饰函数的 __name__ 值作为反向解析的name
@app.route('/index')
def index():
return "hello world"
if __name__ == '__main__':
app.run()
2
3
4
5
6
7
8
9
10
11
12
13
14
若index有多个装饰器呢? 如何操作呢? 其实注意两点即可:
-1- @functools.wraps(func)
-2- 新增的装饰器写在 @app.route
下面
from flask import Flask
import functools
app = Flask(__name__)
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@app.route('/index')
@auth
def index():
return "hello world"
@app.route('/order')
@auth
def order():
return "wa o~"
if __name__ == '__main__':
app.run()
"""
若auth装饰器不加@functools.wraps(func),运行Flask程序,会报错
AssertionError: View function mapping is overwriting an existing endpoint function: inner
两个装饰器,先auth,后app.route, [可以理解app.route装饰的是auth里的inner函数] !
若auth装饰器不加@functools.wraps(func)
那么@app.route('/order')和@app.route('/index')的反向解析的name值皆为 inner !! 就会报错!
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Session示例
★ 首先要知道, Flask的session不同于Django的Session:
-1- Django的session, 服务端 - 数据库中随机字符串:某个用户相关信息
; 浏览器 - Cookie中 session_id:随机字符串
-2- Flask的session, 服务端没有; 浏览器Cookie中 session:用户信息+用户信息通过secret_key加盐加密后的签名 组成的随机字符串
一句话: flask的session信息在服务端不存储, 而是通过加密的方式放在浏览器端进行存储
接下来, 简单实现一下Flask的session功能..
需求: 未登陆不能访问首页和订单页面,会自动重定向登陆页面; 登陆后跳转首页. / 即访问首页和订单页面都需要session的状态.
from flask import Flask, render_template, redirect, request, url_for, session
import functools
app = Flask(__name__)
app.secret_key = "abcdefghijklmnop" # ★ flask使用session,必须设置secret_key这个盐
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
# print(session) # <SecureCookieSession {'username': 'dc'}>
username = session.get("username")
if not username:
return redirect(url_for('login'))
return func(*args, **kwargs)
return inner
@app.route('/order')
@auth
def order():
return "wa o~"
@app.route('/index')
@auth
def index():
return "Hello World!(*≧ω≦)"
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
user = request.form.get('user')
pwd = request.form.get('pwd')
if user == 'dc' and pwd == '123':
session['username'] = 'dc'
return redirect(url_for('index'))
error = "用户名或密码错误!"
return render_template('login.html', **{'error': error})
if __name__ == '__main__':
app.run()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 小蓝图(Blue Print)
如果视图很多, 怎么搞? 都写在一个py文件中? No. 用蓝图来解决.. (下面是小蓝图,用来写中小项目绰绰有余)
(*≧ω≦) 蓝图帮我们构建业务功能可以拆分的目录结构!!
- demo 在pycharm中创建一个python项目
- demo 创建一个目录,与项目同名
__init__.py
- static
- templates
- views 里面创建py文件,一个py文件就是一个小蓝图
- one.py
- two.py
- manage.py
- demo 创建一个目录,与项目同名
这样构建目录, 有助于我们使用flask的第三方组件!!
# ■ manage.py
from demo import create_app
app = create_app()
if __name__ == '__main__':
app.run()
# ■ __init__.py
from flask import Flask
from .views.one import one
from .views.two import two
def create_app():
app = Flask(__name__)
app.secret_key = "abcdefghijklmnop"
@app.route('/index') # http://127.0.0.1:5000/index
def index():
return 'index'
# 注册蓝图,还可加前缀
app.register_blueprint(one, url_prefix='/one')
app.register_blueprint(two, url_prefix='/two')
return app
# ■ one.py
from flask import Blueprint
one = Blueprint('one', __name__)
@one.route('/f1') # http://127.0.0.1:5000/one/f1
def f1():
return 'one-f1'
@one.route('/f2') # http://127.0.0.1:5000/one/f2
def f2():
return 'one-f2'
# ■ two.py
from flask import Blueprint
two = Blueprint('two', __name__)
@two.route('/f3') # http://127.0.0.1:5000/two/f3
def f3():
return 'two-f3'
@two.route('/f4') # http://127.0.0.1:5000/two/f4
def f4():
return 'two-f4'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
To do:
1. 使用蓝图,登陆保存会话,且用户表应放在mysql表中 - 创建数据库用pymysql连接.
2. 对数据库中的某张表进行CURD - eg:book表
3. 上传excel文件,将excel中的文件内容导入到数据库中 - 大概6行代码就可实现
4. 探究werkzeug这个wsgi的返回值,去源码中找,从Flask来的__call__开始找.
2
3
4
5