Flask 基础
Flask 是一个使用 Python 编写的轻量级 Web 应用框架。基于 Werkzeug WSGI 工具箱和 Jinja2 模板引擎。
Flask 使用 BSD 授权。Flask 被称为“microframework”,因为它使用简单的核心,用 extension 增加其他功能。Flask 没有默认使用的数据库、窗体验证工具。然而,Flask 保留了扩增的弹性,可以用 Flask-extension 加入这些功能:ORM、窗体验证工具、文件上传、各种开放式身份验证技术。
1 安装
pip3 install flask
2 基本使用
2.1 werkzeug
与 Django 类似,Flask 是基于 werkzeug 实现的。通过 werkzeug 即可简单实现最基本的返回数据。
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple
def run(environ, strat_response):
return [b"asdf"]
if __name__ == '__main__':
run_simple("localhost", 4000, run)
亦可使用 Response 直接返回字符串,注意导入方式与 Django 不同。
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple
@Request.application
def hello(request):
return Response('Hello World')
if __name__ == '__main__':
run_simple("localhost", 4000, hello)
2.2 使用 Flask
可将 Flask 看作一个类,通过 app=Flask(__name__)
进行实例化,app.run()
用以启动 Flask 。
在函数上增加 @app.route('/index')
将函数变成视图函数,而在 route 中即可定义访问该视图函数的路由。
from flask import Flask
app = Flask(__name__)
@app.route('/index')
def hello():
return "Hello World"
if __name__ == '__main__':
app.run()
2.3 Flask 实现简单的登录功能
学习 Flask 时可以类比着 Django 的学习。Django 中有的 request redirect session ,Flask 中也有,不过, 同样是实现渲染功能的 render 函数在 Flask 中变成了 render_template 。Flask 的传参方式与 Django 一样,都是直接赋值 user=user
。
Flask 默认只支持 GET 方式传数据,如果想要支持更多的数据传输方式,可在 route 中加上 methods 属性来设置。@app.route('/login', methods=["GET", "POST"])
。如果存在 POST 请求,则需要设置 Flask 秘钥来确保传输安全。 app.secret_key = "fasd"
。
Flask 默认 HTML 文件放在 templates 文件中。
app.py
from flask import Flask, render_template, request, redirect, session
app = Flask(__name__)
# Flask的秘钥
app.secret_key = "fasd"
@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 == 'ban' and pwd == 'ban':
session['user'] = user
return redirect('/index')
return render_template('login.html', error="用户名或密码错误")
@app.route('/index')
def index():
user = session.get('user')
if not user:
redirect('/login')
return render_template('index.html', user=user)
if __name__ == '__main__':
app.run()
3 配置文件
3.1 复习:通过字符串找类
给你一个路径 “settings.Foo”,可以找到类并获取去其中的大写的静态字段。
首先,使用 rsplit 函数将字符串分成模块和类,再使用 importlib 导入模块。然后使用 getattr 函数获取模块中的类。最后,通过 dir 函数获取类中的字段。
settings.py
settings.py
class Foo:
DEBUG = True
TEST = True
xx.py
import importlib
path = "settings.Foo"
p,c = path.rsplit('.',maxsplit=1)
m = importlib.import_module(p)
cls = getattr(m,c)
# 找这个类中的字段
for key in dir(cls):
if key.isupper():
print(key,getattr(cls,key))
3.2 Flask 配置文件
Flask 中的配置文件是一个 flask.config.Config 对象(继承字典),默认配置为:
{
'DEBUG': get_debug_flag(default=False), 是否开启Debug模式
'TESTING': False, 是否开启测试模式
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False,
'LOGGER_NAME': None,
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False,
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
}
1. 配置方式一
通过 app.config['属性']=值
的方式配置。app.config['DEBUG'] = True
由于 Config 对象本质上是字典,所以还可以使用 app.config.update(...)
与更新字典一致的操作进行配置。
2. 配置方式二
直接将配置信息写在 Python 文件中,用 app.config.from_pyfile("python文件名称")
的方式进行配置。
settings.py
DEBUG = True
app.config.from_pyfile("settings.py")
或者 app.config.from_envvar("环境变量名称")
,其中环境变量的值为 Python 文件的名称。
3. 配置方式三
直接将配置信息写在 json 文件中,用 app.config.from_json("json文件名称")
的方式进行配置。且文件内必须是 json 格式,因为内部会执行 json.loads
。
以字典格式直接配置:app.config.from_mapping({'DEBUG':True})
。
4. 配置方式四(推荐)
Flask 支持以模块中类的方式引入配置信息。即将配置信息以字段与值的方式放到另外的文件里的类中,通过 app.config.from_object("python类或类的路径")
代码进行配置。
settings.py
from datetime import timedelta
class Config(object):
DEBUG = False
TESTING = False
SECRET_KEY = "asdfasdfas23"
DATABASE_URI = 'sqlite://:memory:'
SESSION_COOKIE_NAME = '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(hours=1)
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
app.py
app.config.from_object("settings.DevelopmentConfig")
这种导入配置信息的优点有:
- 更改配置信息方便,只需在运行文件中改一下配置信息类即可
例如,开发时的配置信息和上线时的配置信息有所不同,采用这种方式配置,只需在文件中改下配置类名即可。 - 借助类可以继承的特性,使得配置信息也可以继承,从而减少冗余。
4 路由系统
前面已经介绍了 Flask 的路由系统由装饰器构成。并且介绍到可以在其中设置支持的数据传输方式: @app.route('/login', methods=['GET', 'POST'])
4.1 别名与反向获取 URL
在 route 函数中加上 endpoint 字段设置路由别名,如果没有设置 endpoint 字段,默认别名为函数名。
通过别名反向获取 URL 需要使用 url_for 函数。此函数形式为 url_for(endpoint, *args)
其中第一个参数为路由别名,第二个参数为要为 URL 传的值。注意,url_for 需要导入。
from flask import url_for
url_for('endpoint')
url_for("index",nid=777)
4.2 动态路由
我们常常需要在 url 中进行传值操作,比如 uid 等。此时就需要用到动态路由。
使用方式为 route('url/<类型:变量名>')
,其中,类型和变量名之前的 ':' 不能有空格,如果省略类型则默认为字符串类型。
- @app.route('/user/
') - @app.route('/post/int:post_id')
- @app.route('/post/float:post_id')
- @app.route('/post/path:path')
- @app.route('/login', methods=['GET', 'POST'])
常用路由系统有以上五种,所有的路由系统都是基于一下对应关系来处理:
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid' UUIDConverter,
5 请求与响应
5.1 请求相关信息
# 请求相关信息
# request.method
# request.args
# request.form
# request.values
# request.cookies
# request.headers
# request.path
# request.full_path
# request.script_root
# request.url
# request.base_url
# request.url_root
# request.host_url
# request.host
# request.files
# obj = request.files['the_file_name']
# obj.save('/var/www/uploads/' + secure_filename(f.filename))
5.2 响应相关
1. 响应体相关信息
- 返回字符串
return “asdf"
- 返回 json 数据
from flask import jsonify
return jsonify({'k1':'v1'})
- 渲染模板
return render_template('xxx.html')
- 跳转链接
return redirect()
2. 定制响应头
响应由响应头和响应体组成,响应头可以先进行封装再返回。headers 字段来设置响应头中的字段值,此外还能设置 cookie 。
from flask import make_response
obj = make_response("asdf")
obj.headers['xxxxxxx'] = '123'
obj.set_cookie('key', 'value')
return obj
示例:
@app.route('/index')
def index():
obj = make_response(render_template('index.html', stu_dic=STUDENT_DICT))
obj.headers['xxxx'] = '123'
return obj
6 示例程序:学生管理
6.1 基本功能实现
对学生信息进行详情查看或者删除。
app.py
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect, session, url_for, make_response
app = Flask(__name__)
app.config.from_object("settings.DevelopmentConfig")
# Flask的秘钥
app.secret_key = "fasd"
STUDENT_DICT = {
1:{'name':'王龙泰','age':38,'gender':'中'},
2:{'name':'小东北','age':73,'gender':'男'},
3:{'name':'田硕','age':84,'gender':'男'},
}
@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 == 'ban' and pwd == 'ban':
session['user'] = user
return redirect('/index')
return render_template('login.html', error="用户名或密码错误")
@app.route('/index')
def index():
# obj = make_response(render_template('index.html', stu_dic=STUDENT_DICT))
# obj.headers['xxxx'] = '123'
# return obj
return render_template('index.html',stu_dic=STUDENT_DICT)
@app.route('/detail/<int:nid>')
def detail(nid):
info = STUDENT_DICT[nid]
return render_template('detail.html', info=info)
if __name__ == '__main__':
app.run()
idnex.html
<body>
<h1>学生列表</h1>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>选项</th>
</tr>
</thead>
<tbody>
{% for k,v in stu_dic.items() %}
<tr>
<td>{{k}}</td>
<td>{{v.name }}</td>
<td>{{v.age}}</td>
<td>{{v.gender}}</td>
<td>
<a href="/detail/{{k}}">查看详细</a>
|
<a href="/delete/{{k}}">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
detail.html
<body>
<h1>学生详细</h1>
<ul>
{% for item in info.values() %}
<li>{{item}}</li>
{% endfor %}
</ul>
login.html
<body>
<h1>欢迎登录</h1>
<form action="" method="post">
<input type="text" name="user">
<input type="password" name="pwd">
<input type="submit" value="提交">{{error}}
</form>
</body>
6.2 添加登录认证
1. 方式一:if 判断(不推荐)
在每个视图函数中添加判断,如果 session 中没有登录相关信息则跳转到登录页面。
@app.route('/index')
def index():
if not session.get('user'):
return redirect(url_for('login'))
return render_template('index.html',stu_dic=STUDENT_DICT)
这种方式必须得在每一个需要登录认证的视图函数中添加,太 low ,不推荐,也别用。
2. 方式二:装饰器
(1) 装饰器回顾
示例:
def auth(func):
def inner(*args,**kwargs):
ret = func(*args,**kwargs)
return ret
return inner
@auth
def index():
print('index')
@auth
def detail():
print('detail')
print(index.__name__)
print(detail.__name__)
此时打印两个函数名的结果全是 inner
,这是装饰器本身性质导致。
回到用装饰器对 Flask 视图函数进行登录认证上,因为 endpoint 默认为函数名,而函数名一致会使得反向 url 失效,怎么解决呢?对 inner 函数加上内置装饰器 @functools.wraps(func)
。
import functools
def auth(func):
@functools.wraps(func)
def inner(*args,**kwargs):
ret = func(*args,**kwargs)
return ret
return inner
@auth
def index():
print('index')
@auth
def detail():
print('detail')
print(index.__name__)
print(detail.__name__)
此时输出为:
index
detail
(2) 登录认证用的装饰器
import functools
def auth(func):
@functools.wraps(func)
def inner(*args,**kwargs):
if not session.get('user'):
return redirect(url_for('login'))
ret = func(*args,**kwargs)
return ret
return inner
此时的认证方法为:
@app.route('/index')
@auth
def index():
return render_template('index.html',stu_dic=STUDENT_DICT)
注意:
Flask 的路由系统也是以装饰器方式来实现的。那么,路由系统装饰器和登录认证装饰器哪个在前,哪个在后呢?答案是登录认证装饰器在后。因为登录装饰器用到了路由系统装饰器中的内容。
该方法的应用场景:比较少的函数中需要额外添加功能。如果是所有函数都需要添加额外功能,那么推荐使用接下来所说明的第三种方式。
3. 方式三:before_request
before_request 也是一种 Flask 自带的装饰器,它用来在请求时进行一些操作,如果返回 None ,则继续进入视图函数做相应处理,如果返回其他,则作为响应返回结果。
用在登录认证时代码如下:
@app.before_request
def xxxxxx():
if request.path == '/login':
return None
if session.get('user'):
return None
return redirect('/login')
7 模板渲染
7.1 数据操作
1. 基本数据类型
在 Flask 的模板中,对于传过来的数据可以执行 Python 语法,例如:dict.get()
list['xx']
。
2. 传 HTML 代码
由于安全性考虑,从后端传来的 HTML 代码字符串不能直接在前端编译,而是显示为字符串,想要其编译有两种方式:
(1) 前端管道符加持: {{arg_html|safe}}
(2) 后端安全封装:MarkUp("<input ... />")
7.2 函数
1. 传入函数
def func(arg):
return arg + 1
@app.route('/tpl')
def tpl():
context = {
'users':['longtai','liusong','zhoahuhu'],
'txt':Markup("<input type='text' />"),
'func':func
}
return render_template('tpl.html',**context)
{{func(6)}}
**值得注意的是: **
- Django 传过来的数据是自动执行的,所有不用加括号,而 Flask 不执行,所以需要加上括号。
- 当函数参数为多个时,前端传参数的不同。
两个参数:{{sb(1,9)}}
三个参数:{{ 1|db(2,3) }}
管道符前面的参数是第一个参数。
2. 全局定义函数
全局定义函数可以不经过任何视图函数传送即可在前端使用,需要用到 @app.template_global()
和 @app.template_filter()
两个装饰器。
@app.template_global()
def sb(a1, a2):
# {{sb(1,9)}}
return a1 + a2
@app.template_filter()
def db(a1, a2, a3):
# {{ 1|db(2,3) }}
return a1 + a2 + a3
7.3 模板继承
在 Flask 中,模板的继承与 Django 中的使用方法一致。
1. extends
layout.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>模板</h1>
{% block content %}{% endblock %}
</body>
</html>
tpl.html
{% extends "layout.html"%}
{% block content %}
{{users.0}}
{% endblock %}
2. include
form.html
<form>
asdfasdf
asdfasdf
asdf
asdf
</form>
在 xxx.html 调用:{% include "form.html" %}
。
7.4 宏
Flask 中的宏相当于将某些有特征功能的 HTML 代码封装起来,并能进行传值操作,使之在前端能适应不同场景的应用。
定义:
{% macro ccccc(name, type='text', value='') %}
<h1>宏</h1>
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
<input type="submit" value="提交">
{% endmacro %}
调用:
{{ ccccc('n1') }}
{{ ccccc('n2') }}
8 Session
当请求刚到来:Flask 读取 cookie 中 session 对应的值(如eyJrMiI6NDU2LCJ1c2VyIjoib2xkYm95),将该值解密并反序列化成字典,放入内存以便视图函数使用。
设置 session:session['k1'] = 123
删除 session:del session['k1']
当请求结束时,Flask 会读取内存中字典的值,进行序列化 + 加密,写入到用户 cookie 中。
9 闪现
闪现可以理解为一次性的 session ,只要有一次请求获取该值,则该值就会被删除。原理为,在 session 中存储一个数据,读取时通过 pop 将数据移除。
通过 flash 函数来添加闪现,并且可以指定闪现的类型。flash('临时数据存储','error')
通过 get_flashed_messages 来获取闪现中的值,在该函数中可以指定 category_filter 字段来过滤闪现类型,获取自己需要类型的闪现。
from flask import Flask,flash,get_flashed_messages
@app.route('/page1')
def page1():
flash('临时数据存储','error')
flash('sdfsdf234234','error')
flash('adasdfasdf','info')
return "Session"
@app.route('/page2')
def page2():
print(get_flashed_messages(category_filter=['error']))
return "Session"
10 中间件
Flask 也支持与 Django 类似的中间件功能。
通过查看源码可以得知,Flask 中间件功能类似于 flask 类中的 call 方法,而实现中间件则是重写 call 方法即可。
而 call 方法在用户发起请求时,才执行。
from flask import Flask
app = Flask(__name__)
@app.route('/index')
def index():
print('index')
return "Index"
class Middleware(object):
def __init__(self,old):
self.old = old
def __call__(self, *args, **kwargs):
ret = self.old(*args, **kwargs)
return ret
if __name__ == '__main__':
app.wsgi_app = Middleware(app.wsgi_app)
app.run()
11 特殊装饰器
-
before_request
-
after_request
示例:
from flask import Flask
app = Flask(__name__)
@app.before_request
def x1():
print('before:x1')
return '滚'
@app.before_request
def xx1():
print('before:xx1')
@app.after_request
def x2(response):
print('after:x2')
return response
@app.after_request
def xx2(response):
print('after:xx2')
return response
@app.route('/index')
def index():
print('index')
return "Index"
@app.route('/order')
def order():
print('order')
return "order"
if __name__ == '__main__':
app.run()
- before_first_request
from flask import Flask
app = Flask(__name__)
@app.before_first_request
def x1():
print('123123')
@app.route('/index')
def index():
print('index')
return "Index"
@app.route('/order')
def order():
print('order')
return "order"
if __name__ == '__main__':
app.run()
-
template_global
-
template_filter
-
errorhandler
出现错误时返回的内容。
@app.errorhandler(404)
def not_found(arg):
print(arg)
return "没找到"