Flask 基础
Flask 基础
Django Web框架
优点:大而全,组件非常丰富 admin,model,Forms,session 由Django组织定制开发
缺点:资源浪费,慢
Flask Web框架
优点:小,精致,节省资源开销,Session组件,第三方组件数不胜数 Flask-admin Flask-SQLAlchemy WTForms
缺点:稳定性相对较差(因为依赖第三方组件,FLask更新后三方组件可能更新不及时)
Tornado 框架
优点:主要特点是原生异步非阻塞,在IO密集型应用和多任务处理上占据绝对性的优势
缺点:诸多内容需要自己实现
Flask
-
创建项目,直接创建python项目
-
启动 Flask
-
from flask import Flask app = Flask(__name__) app.run()
-
-
Flask 路由视图
-
@app.route('/index') def index(): return 'Hello I am Flask'
-
Flask 三剑客 + 特殊返回值
-
字符串 HttpResponse
-
模板 render_template
-
重定向 redirect
-
打开并返回文件内容 send_file
-
.标准JSON格式的Response jsonify
-
from flask import Flask, render_template, redirect, send_file, jsonify import json app = Flask(__name__) @app.route('/index') def index(): return 'Hello I am Flask' @app.route('/page') def page(): return render_template('page.html') @app.route('/re') def re(): return redirect('/page') @app.route('/get-file') def get_file(): return send_file('app.py') @app.route('/get-json') def get_json(): return json.dumps({'name': '233'}) @app.route('/get-jsonify') def get_json2(): return jsonify({'name': '233'}) # 相较于json在header里面增加了 Content-Type: application/json app.run()
Flask Request
-
from flask import request - request Flask 请求上下文管理 request.method # 获取请求方式 GET、POST request.args.get("id") # URL中的参数数据 request.args.to_dict() # 转化为字典格式 request.form.get("username") # # FromData中的数据 request.form.to_dict() # 转换为字典格式 request.values # args from 中所有数据,看数据在哪里 request.values.to_dict() # 神坑(如果key重复,就凉凉) request.url # 访问的地址 http://127.0.0.1:9999/get-req?id=2 request.base_url # 不带url中的参数 http://127.0.0.1:9999/get-req request.path # 访问的路径 /get-req request.host # ip+端口 127.0.0.1:9999 request.host_url # 协议+ip+端口 http://127.0.0.1:9999/ request.json # 请求头:Content-type:application/json ,如果请求头中有json,则会序列化到这 request.data # 存放原始数据(当数据格式无法被解析时) Byte类型 , Content-type:application/json 也会保留原始数据 request.headres # 请求头中的数据 request.cookies # cookies中的数据,
-
file 上传文件
-
@app.route('/get_file', methods=('GET', 'POST')) def get_file(): print(request.files) print(request.files.get('file')) if request.method == 'GET': return render_template('file.html') elif request.method == 'POST': file = request.files.get('file') # 'file' 是input中的 name # 保存时,需要对文件名做安全校验 file.save(file.filename) # 保存文件,file.filename 上传文件的文件名 return '提交成功' else: return 'error' # ImmutableMultiDict([('file', <FileStorage: '1.jpg' ('image/jpeg')>)]) # <FileStorage: '1.jpg' ('image/jpeg')>
-
jinja2
jinja模板语言
{{}} ,取值执行
{% %} ,逻辑代码
for
{% for foo in g %}
{% endfor %}
if
{% if g %}
{% elif g %}
{% else %}
{% endif %}
后端传值到template中
直接在 render_template
中加参数就可,也可以传递多个参数
person = []
@app.route('/info')
def info():
return render_template('info.html', person=PERSON)
在template中数据显示
-
用过
{{ data }}
直接获取值,传递过去的值是对象也可以通过.
获取值 -
如果传递的数据是字典格式,以下三种方式都可以,像极了python字典取值,并且key不存在不会报错(keyError)
{{ person.name }}
{{ person['age'] }}
{{ person.get('gender') }}
-
如果传递的参数是列表格式,通过
for
循环可以获得-
{% for person in person_list %} <tr> <td>{{ person.name }}</td> <td>{{ person['age'] }}</td> {% if person['gender']=='未知' %} <td>男</td> {% else %} <td>{{ person.get('gender') }}</td> {% endif %} </tr> {% endfor %}
-
-
如果要循环获得字典
dict
的参数,直接for i in dict
将获得字典的 key
正常显示后端传递的便签
如果直接传递一个标签,会在前端直接显示源代码,这是Flask为了防止XSS攻击,默认设置,如果需要正常显示需要做一些操作
可以通过两种方式,使标签在前端正常显示
- 前端
{{ inp |safe }}
- 后端
Markup_tag = Markup(inp)
后在传递前端
后端
from flask import Markup
inp = '<input type="button" name="button" id="">'
Markup_tag = Markup(inp)
@app.route('/safe')
def safe():
return render_template('safe.html', inp=inp, Markup_tag=Markup_tag)
前端
<div>
<h3>这是一个按钮标签</h3>
{{ inp }}
{{ inp |safe }}
{{ Markup_tag }}
</div>
模板中执行函数
在render_template
中传递函数名,在模板中调用函数(参数1,参数2,...)
即可
后端
def two_sum(a, b):
return a + b
@app.route('/func')
def func():
return render_template('func.html', two_sum=two_sum)
前端
<div>
<h3>1+2={{ two_sum(1,2) }}</h3>
{#页面显示: 1+2=3#}
</div>
如果多个模板都需要用到一个函数,那么可以把这个函数改为全局函数,不需要传递,直接在模板中即可
@app.template_global() # 定义全局模板函数
def three_sum(a, b, c):
return a + b + c
过滤器 filter,如果在模板中需要对一参数进行处理后再显示可以采用过滤器
后端
@app.template_filter() # 默认全局
def add(a, n):
return a + n
模板
<div>
<h3>1+124={{ 1| add(124) }}</h3>
{# 页面显示:1+124=125#}
</div>
模板继承于引用
如果前端页面有大量重复页面,没必要每次都写,可以使用模板复用的方式复用模板,将整个页面作为一个模板,定义块区域,其他模板可以继承这个模板,填写块中内容即可
model1.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h3>这是model1,以下是特殊内容</h3>
{% block contnt %}
{% endblock %}
</body>
</html>
model2.html
{% extends 'mode1.html' %}
{% block contnt %}
<h3>这是model2,model2的特殊内容</h3>
{% endblock %}
如果在多个模板中有相似的代码,可以抽离出来,引用即可
model3.html
<div>
<h3>通用部分</h3>
</div>
model.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<h3>XXXX内容</h3>
{# 引用共同内容#}
{% include 'model3.html' %}
<h3>XXXX内容</h3>
</div>
</body>
</html>
宏指令
在模板中定义函数
宏类似于python中的函数,宏的作用就是在模板中重复利用代码,避免代码冗余(一次定义,多次使用)。
<div>
{% macro func_macro(type,name) %}
<input type="{{ type }}" name="{{ name }}">
{% endmacro %}
{{ func_macro('text','username') }}
{{ func_macro('password','pwd') }}
{{ func_macro('submit','') }}
</div>
session
原理: 类似JWT ,所有数据都将写入 cookies 发送到客户端存储。通过 secret_key
签名,他人可以查看信息但无法修改信息,不能存储敏感信息
推荐阅读 客户端 session 导致的安全问题
使用 session
- 导入 session
form flask import session
- 配置 secret_key
app.secret_key = "#$%^&*(FJHGVJB%^&&*OJGFHGH"
- 取值,
session.get('username')
- 设置值,
session['username'] = request.form.get('username')
from flask import Flask, render_template, redirect, request, session
from functools import wraps
app = Flask(__name__)
app.secret_key = '#$%^&*(FJHGVJB%^&&*OJGFHGH'
def auth(func):
"""认证装饰器"""
@wraps(func)
def auth_inner(*args, **kwargs):
if session.get('username'):
ret = func(*args, **kwargs)
return ret
else:
return redirect("/login")
return auth_inner
@app.route('/index')
@auth
def index():
return 'index page'
@app.route('/login', methods=('GET', 'POST'))
def login():
if request.method == "GET":
return render_template('login.html', msg='')
elif request.method == "POST":
# 验证用户
if request.form.get('username') == '123' and request.form.get('pwd') == '123':
session['username'] = request.form.get('username')
session['pwd'] = request.form.get('pwd')
return redirect('/index')
else:
return render_template('login.html', msg='用户名或密码错误')
else:
return 'error'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7777, debug=True)
路由配置
详解 @app.route('/index')
中的参数配置
@app.route('/index')
def index():
return 'index'
pass
# 等效于
app.add_url_rule(rule='/index', endpoint='/index', view_func=index)
-
rule,路由地址
-
methods 允许进入当前视图函数的请求方式
-
endpoint,反向url地址,默认为视图函数名 (url_for可以根据endpoint 解析出路由)
-
view_func,视图函数
-
defaults : 视图函数的参数默认值{"nid":1} ,传递给视图函数
-
redirect_to : url地址重定向,永久重定向 301
-
strict_slashes : url地址结尾符"/"的控制 False : 无论结尾 "/" 是否存在均可以访问 , True : 结尾必须不能
False,http://127.0.0.1:9999/index/ ,http://127.0.0.1:9999/index 都可以访问
True, 只有 http://127.0.0.1:9999/index 可以访问
-
subdomain : 子域名前缀 subdomian="abc" 这样写可以得到 abc.example.com 前提是app.config["SERVER_NAME"] = "example.com"
-
动态路由参数
-
@app.route('/news/<int:id>') def infos(id): return f'news: {id}' # 访问地址 http://127.0.0.1:9999/news/1 # 响应 news: 1
-
支持的有,
str,any,path,int,float,uuid
-
如果在CBV中配置动态路由,需要在每个方法上加 **kwargs 接收参数
-
class Login(views.MethodView): methods = ["GET", "POST"] # 允许的请求类型 # decorators = (auth,) # CBV添加装饰器 def get(self, *args, **kwargs): print(args, kwargs) # id 将以key:value形式存在于 kwargs 中 return render_template("login.html") def post(self, *args, **kwargs): return "123" app.add_url_rule("/play/<int:id>", view_func=Login.as_view(name="play"))
-
-
-
路由正则,默认不支持,可以通过自定义规则实现
werkzeug.routing.BaseConverter
Flask实例化配置
FLask实例化时传递的参数,对Flask实例进行的初始配置
# 常用-------
static_folder = 'static', # 静态文件目录的路径 默认当前项目中的static目录
static_url_path = None, # 静态文件目录的url路径 默认不写是与static_folder同名,远程静态文件时复用
template_folder = 'templates' # template模板目录, 默认当前项目中的 templates 目录
# 常用-------
# 蓝图中,url 的 前缀
url_prefix = "/bp"
static_host = None, # 远程静态文件所用的Host地址,默认为空
# host_matching是否开启host主机位匹配,是要与static_host一起使用,如果配置了static_host, 则必须赋值为True
# 这里要说明一下,@app.route("/",host="localhost:5000") 就必须要这样写
# host="localhost:5000" 如果主机头不是 localhost:5000 则无法通过当前的路由
host_matching = False, # 如果不是特别需要的话,慎用,否则所有的route 都需要host=""的参数
subdomain_matching = False, # 理论上来说是用来限制SERVER_NAME子域名的,但是目前还没有感觉出来区别在哪里
instance_path = None, # 指向另一个Flask实例的路径
instance_relative_config = False # 是否加载另一个实例的配置
root_path = None # 主模块所在的目录的绝对路径,默认项目目录
Flask app对象配置
对app对象的配置,在app.config
中
有以下5种配置方式
app.config.from_envvar() # 通过虚拟环境
app.config.from_object() # 通过对象
app.config.from_mapping() # 直接填参数
app.config.from_pyfile() # 通过py文件,里面是 key=value 形式
app.config.from_json() # 通过JSON数据
参数
默认参数可以在 app.default_config
中查看
{
'DEBUG': False, # 是否开启Debug模式
'TESTING': False, # 是否开启测试模式
'PROPAGATE_EXCEPTIONS': None, # 异常传播(是否在控制台打印LOG) 当Debug或者testing开启后,自动为True
'PRESERVE_CONTEXT_ON_EXCEPTION': None, # 一两句话说不清楚,一般不用它
'SECRET_KEY': None, # 之前遇到过,在启用Session的时候,一定要有它
'PERMANENT_SESSION_LIFETIME': 31, # days , Session的生命周期(天)默认31天
'USE_X_SENDFILE': False, # 是否弃用 x_sendfile
'LOGGER_NAME': None, # 日志记录器的名称
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None, # 服务访问域名
'APPLICATION_ROOT': None, # 项目的完整路径
'SESSION_COOKIE_NAME': 'session', # 在cookies中存放session加密字符串的名字
'SESSION_COOKIE_DOMAIN': None, # 在哪个域名下会产生session记录在cookies中
'SESSION_COOKIE_PATH': None, # cookies的路径
'SESSION_COOKIE_HTTPONLY': True, # 控制 cookie 是否应被设置 httponly 的标志,
'SESSION_COOKIE_SECURE': False, # 控制 cookie 是否应被设置安全标志
'SESSION_REFRESH_EACH_REQUEST': True, # 这个标志控制永久会话如何刷新
'MAX_CONTENT_LENGTH': None, # 如果设置为字节数, Flask 会拒绝内容长度大于此值的请求进入,并返回一个 413 状态码
'SEND_FILE_MAX_AGE_DEFAULT': 12, # hours 默认缓存控制的最大期限
'TRAP_BAD_REQUEST_ERRORS': False,
# 如果这个值被设置为 True ,Flask不会执行 HTTP 异常的错误处理,而是像对待其它异常一样,
# 通过异常栈让它冒泡地抛出。这对于需要找出 HTTP 异常源头的可怕调试情形是有用的。
'TRAP_HTTP_EXCEPTIONS': False,
# Werkzeug 处理请求中的特定数据的内部数据结构会抛出同样也是“错误的请求”异常的特殊的 key errors 。
# 同样地,为了保持一致,许多操作可以显式地抛出 BadRequest 异常。
# 因为在调试中,你希望准确地找出异常的原因,这个设置用于在这些情形下调试。
# 如果这个值被设置为 True ,你只会得到常规的回溯。
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http', # 生成URL的时候如果没有可用的 URL 模式话将使用这个值
'JSON_AS_ASCII': True,
# 默认情况下 Flask 使用 ascii 编码来序列化对象。如果这个值被设置为 False ,
# Flask不会将其编码为 ASCII,并且按原样输出,返回它的 unicode 字符串。
# 比如 jsonfiy 会自动地采用 utf-8 来编码它然后才进行传输。
'JSON_SORT_KEYS': True,
#默认情况下 Flask 按照 JSON 对象的键的顺序来序来序列化它。
# 这样做是为了确保键的顺序不会受到字典的哈希种子的影响,从而返回的值每次都是一致的,不会造成无用的额外 HTTP 缓存。
# 你可以通过修改这个配置的值来覆盖默认的操作。但这是不被推荐的做法因为这个默认的行为可能会给你在性能的代价上带来改善。
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
}
特殊装饰器,before_request ,after_requset
@app.before_request 在请求(request)进入视图函数之前执行
@app.before_first_request 它与 @app.before_request 极为相似或者说是一模一样,只不过它只会被执行一次
@app.after_request 在响应(response)返回客户端之前执行 , 结束视图函数之后
@app.errorhandler(404) 重定义错误提示
@app.errorhandler(404) # 404 code_or_exception
def error404(args):
print(args) # args错误信息
return '404,404,404...' # 若404返回新的错误信息
执行顺序
正常情况:before1 - before2 - before3 - views - after3 - after2 - after1 返回顺序是定义代码时的倒叙
异常情况:before1 - after3 - after2 - after1 异常阻塞请求的情况
@app.before_request
def auth():
"""利用@app.before_request 实现用户认证"""
WHITE_LIST = ['login']
if not session.get('username') and request.endpoint not in WHITE_LIST:
return redirect('/login')
Flask 蓝图Blueprint
推荐阅读:
Modular Applications with Blueprints
一个蓝图定义了可用于单个应用的视图,模板,静态文件等等的集合。举个例子,想象一下我们有一个用于管理面板的蓝图。这个蓝图将定义像/admin/login和/admin/dashboard这样的路由的视图。它可能还包括所需的模板和静态文件。你可以把这个蓝图当做你的应用的管理面板,管它是宇航员的交友网站,还是火箭推销员的CRM系统。
在Flask中可以用Blueprint (蓝图) 实现模块化的应用,使用蓝图让应用层次清晰,开发者可以更容易的开发和维护项目。蓝图通常作用于相同的URL前缀,如/user/:id、/user/profile这样的地址,都以/user开头,它们是一组用户相关的操作,那么就可以放在一个模块中。在大型项目中,基本上有经验的开发者一看路由就能很快的找到对应的视图了。
将一个庞大的网站模块化,将不同应用、功能分开,并以不同url前缀区分。
应用蓝图
目录结构
yourapp/
__init__.py
static/
...
templates/
...
views/
__init__.py
home.py
admin.py
user.py
models.py
manager.py
# app01/views/user.py
from flask import Blueprint
u_bp = Blueprint("user",__name__,url_prefix='/bp')
@u_bp.route("/user") # users访问路径是 /bp/user
def users():
return "I am users bp"
# app01/__init__.py
from flask import Flask
from .views.user import user_bp
def create_app():
app = Flask(__name__)
app.register_blueprint(user_bp)
return app
# manager.py
from app01 import create_app
my_app = create_app()
if __name__ == '__main__':
my_app.run('0.0.0.0', 4654, debug=True)
CBV
from flask import views, render_template
class Login(views.MethodView):
methods = ["GET", "POST"] # 允许的请求类型
decorators = (auth,) # CBV添加装饰器
def get(self):
return render_template("login.html")
def post(self):
return "123"
app = Flask(__name__)
app.add_url_rule("/play", view_func=Login.as_view(name="play"))
Flask-session
使用 flask_session 插件将 session 信息存储到服务端
推荐阅读
Flask-Session
Flask之扩展flask-session
使用 Flask-session
from flask import Flask, render_template, session, request
from flask_session import Session
import redis
app = Flask(__name__)
app.config['SESSION_TYPE'] = 'redis' # 设置会话接口
app.config['SESSION_REDIS'] = redis.Redis(host='127.0.0.1', port=6379) # 配置连接redis
Session(app) # 装载app到Session
@app.route('/login', methods=['GET', "POST"])
def login():
if request.method == "POST":
# 验证身份
if request.form.get('username') == '123':
# 存入redis
session['user'] = request.form.get('username')
return 'ok'
else:
return render_template('login.html')
else:
return render_template('login.html')
if __name__ == '__main__':
app.run('127.0.0.1', 6518, debug=True)
源码分析
源码:
在redis中获取session,Session-->RedisSessionInterface-->open_session
在redis中设置session,Session-->RedisSessionInterface-->save_session
WTForms
WTForms
表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。当然还包括一些其他的功能:CSRF保护
,文件上传等。
与Django中的 form 极为相似
推荐阅读
登录注册demo
views
from flask import Flask, redirect, render_template, request
from wtforms import fields, validators, Form
app = Flask(__name__)
class LoginForm(Form):
username = fields.StringField(
label='用户名',
validators=[validators.InputRequired()],
)
pwd = fields.StringField('密码', [validators.InputRequired()])
class RegisterForm(Form):
username = fields.StringField('用户名', [validators.Length(min=6, max=16, message='长度应为6-16'),
validators.InputRequired(message='必填项')])
pwd = fields.StringField('密码', [validators.Length(min=6, max=16, message='长度应为6-16'),
validators.InputRequired(message='必填项')])
re_pwd = fields.StringField('确认密码', [validators.Length(min=6, max=16, message='长度应为6-16'),
validators.InputRequired(message='必填项'), validators.EqualTo('pwd', '密码不一致')])
email = fields.StringField('邮箱', [validators.Email('格式错误')])
age = fields.IntegerField('年龄', [validators.NumberRange(min=0, max=1000, message='错误')])
gender = fields.SelectField('性别', choices=[('1', '男',), ('2', '女')], default='1')
@app.route('/login', methods=['GET', 'POST'])
def login():
login_form = LoginForm()
if request.method == "POST":
login_form = LoginForm(request.form)
if login_form.validate():
# xx操作
return redirect('/index')
else:
return render_template('login2.html', form=login_form)
else:
return render_template('login2.html', form=login_form)
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if request.method == 'POST':
form = RegisterForm(request.form)
if form.validate():
# 保存数据
return redirect('/login')
else:
return render_template('register.html', form=form)
else:
return render_template('register.html', form=form)
@app.route('/index')
def index():
return 'index'
if __name__ == '__main__':
app.run('127.0.0.1', 5421, debug=True)
templates
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>register</title>
</head>
<body>
<div>
<h3>注册账号</h3>
<form action="" method="post">
<div>{{ form.username.label }}{{ form.username }}{{ form.username.errors[0] }}</div>
<div>{{ form.pwd.label }}{{ form.pwd }}{{ form.pwd.errors[0] }}</div>
<div>{{ form.re_pwd.label }}{{ form.re_pwd }}{{ form.re_pwd.errors[0] }}</div>
<div>{{ form.email.label }}{{ form.email }}{{ form.email.errors[0] }}</div>
<div>{{ form.age.label }}{{ form.age }}{{ form.age.errors[0] }}</div>
<div>{{ form.gender.label }}{{ form.gender }}{{ form.gender.errors[0] }}</div>
<div><input type="submit" name="" id="" value="注册"></div>
</form>
</div>
</body>
</html>
login2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login2</title>
</head>
<body>
<div>
<h3>登录</h3>
<form action="" method="post">
<div>{{ form.username.label }}{{ form.username }}{{ form.username.errors[0] }}</div>
<div>{{ form.pwd.label }}{{ form.pwd }}{{ form.pwd.errors[0] }}</div>
<div><input type="submit" name="" value="登录" id=""></div>
</form>
</div>
</body>
</html>
偏函数
函数在执行时,要带上所有必要的参数进行调用。但是,有时参数可以在函数被调用之前提前获知。这种情况下,一个函数有一个或多个参数预先就能用上,以便函数能用更少的参数进行调用。
偏函数是将所要承载的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数后续的参数,除非使用关键字参数。
from functools import partial
def mod(n, m):
return n % m
mod_by_100 = partial(mod, 100)
print(mod(100, 7)) # 2
print(mod_by_100(7)) # 2