Flask简介
Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架。
Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL。
其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。
Flask常用第三方扩展包:
- Flask-SQLalchemy:操作数据库,ORM;
- Flask-script:终端脚本工具,脚手架;
- Flask-migrate:管理迁移数据库;
- Flask-Session:Session存储方式指定;
- Flask-WTF:表单;
- Flask-Mail:邮件;
- Flask-Bable:提供国际化和本地化支持,翻译;
- Flask-Login:认证用户状态;
- Flask-OpenID:认证, OAuth;
- Flask-RESTful:开发REST API的工具;
- Flask JSON-RPC: 开发rpc远程服务[过程]调用
- Flask-Bootstrap:集成前端Twitter Bootstrap框架
- Flask-Moment:本地化日期和时间
- Flask-Admin:简单而可扩展的管理接口的框架
安装
先创建虚拟环境
mkvirtualenv flask -p python3
pip install flask==0.12.5
创建flask项目
与django不同,flask不会提供任何的自动操作,所以需要手动创建项目目录,需要手动创建启动项目的管理文件
例如,创建项目目录 flaskdemo,在目录中创建manage.py.在pycharm中打开项目并指定上面创建的虚拟环境
创建一个flask框架的主程序。名字可以是app.py/run.py/main.py/index.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello World'
if __name__ == '__main__':
app.run()
路由的基本定义
第一种方式:
# 指定访问路径为 demo1
@app.route('/demo1')
def demo1():
return 'demo1'
第二种方式:
# 路由传递参数[没有限定类型]
@app.route('/user/<user_id>')
def user_info(user_id):
return 'hello %s' % user_id
第三种方式:
# 路由传递参数[限定数据类型]
@app.route('/user/<int:user_id>')
def user_info(user_id):
return 'hello %d' % user_id
自定义路由参数转换器
在 web 开发中,可能会出现限制用户访问规则的场景,那么这个时候就需要用到正则匹配,根据自己的规则去限定请求参数再进行访问
具体实现步骤为:
- 导入转换器基类:在 Flask 中,所有的路由的匹配规则都是使用转换器对象进行记录
- 自定义转换器:自定义类继承于转换器基类
- 添加转换器到默认的转换器字典中
- 使用自定义转换器实现自定义匹配规则
from werkzeug.routing import BaseConverter
class RegexConverter(BaseConverter):
def __init__(self,map,*args):
super().__init__(map)
# 正则参数
self.regex = args[0]
# 将自定义转换器添加到转换器字典中,并指定转换器使用时名字为: re
app.url_map.converters['re'] = RegexConverter
完整代码展示:
from flask import Flask,request
# 初始化
app = Flask(import_name=__name__)
# 编写路由视图
@app.route(rule='/')
def index():
return "<h1>hello world!</h1>"
# 关于路由参数的限制,flask内置的类型不够具体,在开发中,我们经常接受参数,需要更加精确的限制
# 这时候,可以使用正则匹配路由参数
# 正则匹配路由参数,其实就是扩展flask内置的路由限定类型,需要完成4个步骤
# 1. 引入flask的路由转换器
from werkzeug.routing import BaseConverter
# 2. 创建自定义路由转换器
class MobileConverter(BaseConverter):
"""手机号码类型限制"""
def __init__(self,map,*args):
super().__init__(map)
self.regex = "1[3-9]\d{9}"
# 3. 把自定义转换器添加到flask默认的转换器字典中,也就是和原来的int,float等放在一块
app.url_map.converters['mob'] = MobileConverter
# 4. 类似原来的路由参数限制一样,调用自定义转换器名称即可
@app.route(rule='/user/<mob:mobile>')
def user(mobile):
return mobile
# 1. 引入flask的路由转换器
from werkzeug.routing import BaseConverter
# 2. 创建自定义路由转换器
class RegexConverter(BaseConverter):
"""根据正则进行参数限制"""
def __init__(self,map,*args):
super().__init__(map)
self.regex = args[0]
# 3. 把自定义转换器添加到flask默认的转换器字典中,也就是和原来的int,float等放在一块
app.url_map.converters['re'] = RegexConverter
# 4. 类似原来的路由参数限制一样,调用自定义转换器名称即可
@app.route(rule='/user/<re("\w+@\w+\.\w+"):email>')
def user2(email):
print(app.url_map) # 获取所有的路由列表
return email
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
if __name__ == '__main__':
# 运行flask
app.run(host="0.0.0.0")
http的请求
常用的属性如下:
属性 | 说明 | 类型 |
---|---|---|
data | 记录请求体的数据,并转换为字符串只要是通过其他属性无法识别转换的请求体数据最终都是保留到data属性中 | bytes类型 |
form | 记录请求中的html表单数据 | MultiDict |
args | 记录请求中的查询字符串,也可以是query_string | MultiDict |
cookies | 记录请求中的cookie信息 | Dict |
headers | 记录请求中的请求头 | EnvironHeaders |
method | 记录请求使用的HTTP方法 | GET/POST |
url | 记录请求的URL地址 | string |
files | 记录请求上传的文件列表 | * |
json | 记录ajax请求的json数据 | json |
代码:
from flask import Flask,request
# 初始化
app = Flask(import_name=__name__)
# 编写路由视图
@app.route(rule='/')
def index():
return "<h1>hello world!</h1>"
"""== 获取查询字符串 =="""
@app.route(rule="/args",methods=["post","get"])
def args():
print(request.args) # 获取查询字符串
"""
请求地址:
http://127.0.0.1:5000/args?name=xiaoming&password=123&lve=swimming&lve=shopping
打印效果:
ImmutableMultiDict([('name', 'xiaoming'), ('password', '123')])
ImmutableMultiDict是一个由flask封装的字典类,在字典的基础上,提供了一些其他的方法而已。
格式:
ImmutableMultiDict([('键', '值'), ('键', '值')])
字典本身不支持同名键的,ImmutableMultiDict类解决了键同名问题
操作ImmutableMultiDict,完全可以操作字典操作,同时还提供了get,getlist方法,获取指定键的1个值或多个值
还提供了2个方法,一个是to_dict,另一个是to_list
"""
print(request.args["name"]) # xiaoming
print(request.args.get("name")) # xiaoming
print(request.args.getlist("lve")) # ['swimming', 'shopping']
# 把ImmutableMultiDict转换成普通字典
print(request.args.to_dict(flat=False)) # {'name': ['xiaoming'], 'password': ['123'], 'lve': ['swimming', 'shopping']}
print(request.args.to_dict(flat=True)) # {'name': 'xiaoming', 'password': '123', 'lve': 'swimming'}
return "ok"
"""== 获取请求体数据 =="""
@app.route(rule="/data",methods=["post","put","patch"])
def data():
"""接受客户端发送过来的请求体数据,是request.json,request.form,request.files等无法接受的数据,全部会保留到这里"""
print(request.data) #
# 接受表单提交的数据
print(request.form) # ImmutableMultiDict([('username', 'root'), ('password', '123456')])
# 接受ajax或其他客户端提交过来的json数据
print(request.json) # {'username': 'root', 'password': '123456'}
# 接受上传文件
avatar = request.files["avatar"] # ImmutableMultiDict([('avatar', <FileStorage: '123.jpg' ('image/jpeg')>)])
print(avatar) # <FileStorage: '123.jpg' ('image/jpeg')>
# 获取请求头信息
print( request.headers ) # 获取全部的而请求头信息
print( request.headers.get("Host") )
# 获取自定义请求头
print( request.headers.get("company") ) # oldboy
print( request.headers["company"] ) # oldboy
# 本次请求的url地址
print( request.url) # http://127.0.0.1:5000/data
print( request.path ) # /data
return "ok"
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
if __name__ == '__main__':
# 运行flask
app.run(host="0.0.0.0")
http的响应
flask默认支持2种响应方式:
数据响应: 默认响应html文本,也可以返回 JSON格式,或其他格式
页面响应: 重定向
url_for 视图之间的跳转
响应的时候,flask也支持自定义http响应状态码
响应html文本
from flask import make_response
@app.route("/")
def index():
# [默认支持]响应html文本
return "<img src='http://flask.pocoo.org/static/logo.png'>"
return make_response("<h1>hello user</h1>") # 等同于上面的一段
返回JSON数据
from flask import Flask, request, jsonify
# jsonify 就是json里面的jsonify
@app.route("/")
def index():
# 也可以响应json格式代码
data = [
{
"id":1,"username":"liulao","age":18},
]
return jsonify(data)
重定向
from flask import redirect
# 页面跳转响应
@app.route("/user")
def user():
# 页面跳转 redirect函数就是response对象的页面跳转的封装
# Location: http://www.baidu.com
return redirect("http://www.baidu.com")
也可以使用 url_for 生成指定视图函数所对应的 url
from flask import url_for
@app.route("/")
def index():
# [默认支持]响应html文本
# return "<img src='http://flask.pocoo.org/static/logo.png'>"
# 也可以响应json格式代码
data = [
{
"id":1,"username":"liulao","age":18},
]
return jsonify(data)
#使用url_for可以实现视图方法之间的内部跳转
# url_for("视图方法名")
@app.route("/login")
def login():
return redirect( url_for("index") )
重定向到带有参数的视图函数,在 url_for 函数中传入参数
@app.route('/user/<user_id>')
def user_info(user_id):
return 'hello %d' % user_id
# 重定向
@app.route('/demo4')
def demo4():
# 使用 url_for 生成指定视图函数所对应的 url
return redirect( url_for(endpoint="user",user_id=100) )
cookie操作
设置cookie
from flask imoprt Flask,make_response
@app.route('/set_cookie')
def set_cookie():
resp = make_response('this is to set cookie')
resp.set_cookie('username', 'xiaoming', max_age=3600)
return resp
获取cookie
from flask import Flask,request
@app.route('/get_cookie')
def resp_cookie():
resp = request.cookies.get('username')
return resp
Session操作
设置session
from flask import session
@app.route('/set_session')
def set_session():
session['username'] = 'xiaoming'
return 'ok!'
获取session
from flask import session
@app.route('/get_session')
def get_session():
return session.get('username')
请求钩子
请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:
before_first_request
- 在处理第一个请求前执行[项目初始化时的钩子
before_request
- 在每次请求前执行
- 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
after_request
- 如果没有抛出错误,在每次请求后执行
- 接受一个参数:视图函数作出的响应
- 在此函数中可以对响应值在返回之前做最后一步修改处理
- 需要将参数中的响应在此参数中进行返回
teardown_request:
- 在每次请求后执行
- 接受一个参数:错误信息,如果有相关错误抛出
- 需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。
代码:
from flask import Flask,request
# 初始化
app = Flask(import_name=__name__)
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
@app.before_first_request
def before_first_request():
"""
这个钩子会在项目启动后第一次被用户访问时执行
可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置
"""
print("----before_first_request----")
print("系统初始化的时候,执行这个钩子方法")
print("会在接收到第一个客户端请求时,执行这里的代码")
@app.before_request
def before_request():
"""
这个钩子会在每次客户端访问视图的时候执行
# 可以在请求之前进行用户的身份识别,以及对于本次访问的用户权限等进行判断。..
"""
print("----before_request----")
print("每一次接收到客户端请求时,执行这个钩子方法")
print("一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据")
@app.after_request
def after_request(response):
print("----after_request----")
print("在处理请求以后,执行这个钩子方法")
print("一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作")
response.headers["Content-Type"] = "application/json"
response.headers["Company"] = "python oldboy..."
# 必须返回response参数
return response
@app.teardown_request
def teardown_request(exc):
print("----teardown_request----")
print("在每一次请求以后,执行这个钩子方法")
print("如果有异常错误,则会传递错误异常对象到当前方法的参数中")
# 在项目关闭了DEBUG模式以后,则异常信息就会被传递到exc中,我们可以记录异常信息到日志文件中
print(exc)
# 编写路由视图
@app.route(rule='/')
def index():
print("-----------视图函数执行了---------------")
return "<h1>hello world!</h1>"
if __name__ == '__main__':
# 运行flask
app.run(host="0.0.0.0")
异常捕获
主动抛出异常
abort 方法
- 抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)
参数:
-
code – HTTP的错误状态码
abort(404)
捕获错误
errorhandler 装饰器
- 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法
参数:
- code_or_exception – HTTP的错误状态码或指定异常
from flask import Flask
# 创建flask应用
app = Flask(__name__)
"""加载配置"""
class Config():
DEBUG = True
app.config.from_object(Config)
"""
flask中内置了app.errorhander提供给我们捕获异常,实现一些在业务发生错误时的自定义处理。
1. 通过http状态码捕获异常信息
2. 通过异常类进行异常捕获
"""
"""1. 捕获http异常[在装饰器中写上要捕获的异常状态码也可以是异常类]"""
@app.errorhandler(404)
def error_404(e):
return "<h1>您访问的页面失联了!</h1>"
# return redirect("/")
"""2. 捕获系统异常或者自定义异常"""
class APIError(Exception):
pass
@app.route("/")
def index():
raise APIError("api接口调用参数有误!")
return "个人中心,视图执行了!!"
@app.errorhandler(APIError)
def error_apierror(e):
return "错误: %s" % e
if __name__ == '__main__':
app.run(host="localhost",port=8080)
context
执行上下文:即语境,语意,在程序中可以理解为在代码执行到某一行时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。
Flask中上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息[变量、函数、类与对象等信息]。
Flask中有两种上下文,请求上下文(request context)和应用上下文(application context)。
请求上下文(request context)
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session
应用上下文(application context)
它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中操作当前falsk应用对象 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的flask应用相关的信息,它是伴 request 而生,随 request 而灭的。
应用上下文对象有:current_app,g
current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:
- 应用的启动脚本是哪个文件,启动时指定了哪些参数
- 加载了哪些配置文件,导入了哪些配置
- 连接了哪个数据库
- 有哪些可以调用的工具类、常量
- 当前flask应用在哪个机器上,哪个IP上运行,内存多大
from flask import Flask,request,session,current_app,g
# 初始化
app = Flask(import_name=__name__)
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
# 编写路由视图
@app.route(rule='/')
def index():
# 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
# 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
print(current_app.config) # 获取当前项目的所有配置信息
print(current_app.url_map) # 获取当前项目的所有路由信息
return "<h1>hello world!</h1>"
if __name__ == '__main__':
# 运行flask
app.run(host="0.0.0.0")
g变量
g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
from flask import Flask,request,session,current_app,g
# 初始化
app = Flask(import_name=__name__)
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
@app.before_request
def before_request():
g.name = "root"
def get_two_func():
name = g.name
print("g.name=%s" % name)
def get_one_func():
get_two_func()
# 编写路由视图
@app.route(rule='/')
def index():
# 请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
# 请求上下文里面信息来源于每次客户端的请求,所以每个视图中请求上下文的信息都不一样
# print(session)
# 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
# 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
print(current_app.config) # 获取当前项目的所有配置信息
print(current_app.url_map) # 获取当前项目的所有路由信息
get_one_func()
return "<h1>hello world!</h1>"
if __name__ == '__main__':
# 运行flask
app.run(host="0.0.0.0")
两者区别:
- 请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端。
- 应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等
Flask-Script 扩展
这个模块的作用可以让我们通过终端来控制flask项目的运行,类似于django的manage.py
安装命令
pip install flask-script
代码:
from flask import Flas
app = Flask(__name__)
"""使用flask_script启动项目"""
from flask_script import Manager
manage = Manager(app)
@app.route('/')
def index():
return 'hello world'
if __name__ == "__main__":
manager.run()
Flask-Script 为当前应用程序添加脚本命令
1. 引入Command命令基类
2. 创建命令类必须直接或间接继承Command,并在内部实现run方法,同时如果有自定义的其他参数,则必须实现__init__
3. 使用flask_script应用对象manage.add_command对命令类进行注册,并设置调用终端别名。
"""自定义flask_script终端命令"""
from flask_script import Command
class HelloCommand(Command):
"""命令的相关描述"""
def run(self):
with open("text.txt","w") as f:
f.write("hello\r\nhello")
pass
print("这是执行了hello命令")
manage.add_command('hello', HelloCommand() )
Jinja2模板引擎
Flask内置的模板语言,它的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能。
渲染模版函数
- Flask提供的 render_template 函数封装了该模板引擎
- render_template 函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值。
模板基本使用
在flask应用对象创建的时候,设置或者保留template_folder参数,创建模板目录
app = Flask(__name__,template_folder='templates')
在项目下创建 templates 文件夹,用于存放所有的模板文件,并在目录下创建一个模板html文件 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{
{title}}</h1>
</body>
</html>
在视图函数设置渲染模板并设置模板数据
from flask import Flask, render_template
# 初始化
app = Flask(import_name=__name__,template_folder='templates')
# 配置终端脚本运行项目
from flask_script import Manager
manager = Manager(app)
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
# 编写路由视图
@app.route(rule='/')
def index():
data={
}
data["title"] = "我的flask项目"
return render_template("index.html",**data)
if __name__ == '__main__':
# 运行flask
manager.run()
流程控制
主要包含两个:
- if/elif /else / endif
- for / endfor
{# for循环 #}
{% for book in book_list %}
<tr>
<td>{
{ book.id }}</td>
<td>{
{ book.title }}</td>
<td>{
{ book.price }}</td>
</tr>
{% endfor %}
</table>
{# 判断一个参数是否是奇数 #}
{% if name % 2 == 0 %}
偶数<br>
{% else %}
奇数<br>
{% endif %}
过滤器
常见的内建过滤器
safe:禁用转义
<p>{
{ '<em>hello</em>' | safe }}</p>
capitalize:把变量值的首字母转成大写,其余字母转小写
<p>{
{ 'hello' | capitalize }}</p>
lower:把值转成小写
<p>{
{ 'HELLO' | lower }}</p>
upper:把值转成大写
<p>{
{ 'hello' | upper }}</p>
reverse:字符串反转
<p>{
{ 'olleh' | reverse }}</p>
案例·
from flask import Flask,render_template,request
from settings.dev import Config
from flask_script import Manager
"""创建flask应用"""
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
"""加载配置"""
app.config.from_object(Config)
@app.template_filter("mobile")
def do_mobile(data,string):
return data[:3]+string+data[7:]
@app.route("/")
def index():
data = {
}
data["user_list"] = [
{
"id":1,"name":"张三","mobile":"13112345678"},
{
"id":2,"name":"张三","mobile":"13112345678"},
{
"id":3,"name":"张三","mobile":"13112345678"},
{
"id":4,"name":"张三","mobile":"13112345678"},
]
return render_template("index2.html",**data)
if __name__ == '__main__':
manage.run()```
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1" align="center" width="680">
<tr>
<th>ID</th>
<th>姓名</th>
<th>手机</th>
</tr>
{
% for user in user_list %}
<tr>
<td>{
{
user.id }}</td>
<td>{
{
user.name }}</td>
<td>{
{
user.mobile | mobile(string="****") }}</td>
</tr>
{
% endfor %}
</table>
</body>
</html>
模板继承
模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。
父模板代码:
{% block top %}
顶部菜单
{% endblock top %}
{% block content %}
{% endblock content %}
{% block bottom %}
底部
{% endblock bottom %}
子模版代码:
{% extends 'base.html' %}
{% block content %}
需要填充的内容
{% endblock content %}
模板继承使用时注意点:
- 不支持多继承
- 为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
- 不能在一个模板文件中定义多个相同名字的block标签。
- 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好
在 Flask 项目中解决 CSRF 攻击
pip install flask_wtf
- 设置应用程序的 secret_key,用于加密生成的 csrf_token 的值
# 1. session加密的时候已经配置过了.如果没有在配置项中设置,则如下:
app.secret_key = "#此处可以写随机字符串#"
# 2. 也可以写在配置类中。
class Config(object):
DEBUG = True
SECRET_KEY = "dsad32DASSLD*13%^32"
"""加载配置"""
app.config.from_object(Config)
- 导入 flask_wtf.csrf 中的 CSRFProtect 类,进行初始化,并在初始化的时候关联 app
from flask.ext.wtf import CSRFProtect
CSRFProtect(app)
在表单中使用 CSRF 令牌:
<form method="post" action="/">
<input type="hidden" name="csrf_token" value="{
{ csrf_token() }}" />
</form>
数据库操作
Flask-SQLAlchemy
flask默认提供模型操作,但是并没有提供ORM,所以一般开发的时候我们会采用flask-SQLAlchemy模块来实现ORM操作。
SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。
安装:
pip install flask-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple
如果连接的是 mysql 数据库,需要安装 mysqldb 驱动
pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
安装flask-mysqldb时,注意
安装 flask-mysqldb的时候,python底层依赖于一个底层的模块 mysql-client模块
如果没有这个模块,则会报错如下:
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-21hysnd4/mysqlclient/
解决方案:
sudo apt-get install libmysqlclient-dev python3-dev
运行上面的安装命令如果再次报错如下:
dpkg 被中断,您必须手工运行 ‘sudo dpkg --configure -a’ 解决此问题。
则根据提示执行命令以下命令,再次安装mysqlclient
sudo dpkg --configure -a
apt-get install libmysqlclient-dev python3-dev
解决了mysqlclient问题以后,重新安装 flask-mysqldb即可。
pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
数据库连接设置
在 Flask-SQLAlchemy 中,数据库使用URL指定,而且程序使用的数据库必须保存到Flask配置对象的 SQLALCHEMY_DATABASE_URI 键中。
class Config(object):
DEBUG = True
SECRET_KEY = "*(%#4sxcz(^(#$#8423"
# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/students?charset=utf8mb4"
其他设置:
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
#查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
配置完成需要去 MySQL 中创建项目所使用的数据库
$ mysql -uroot -p123
mysql > create database students charset=utf8mb4;
常用的SQLAlchemy字段类型
模型字段类型名 | python中数据类型 | 说明 |
---|---|---|
Integer | int | 普通整数,一般是32位 |
SmallInteger | int | 取值范围小的整数,一般是16位 |
BigInteger | int或long | 不限制精度的整数 |
Float | float | 浮点数 |
Numeric | decimal.Decimal | 普通数值,一般是32位 |
String | str | 变长字符串 |
Text | str | 变长字符串,对较长或不限长度的字符串做了优化 |
Unicode | unicode | 变长Unicode字符串 |
UnicodeText | unicode | 变长Unicode字符串,对较长或不限长度的字符串做了优化 |
Boolean bool | 布尔值 | |
Date | datetime.date | 日期 |
Time | datetime.datetime | 日期和时间 |
LargeBinary | str | 二进制文件内容 |
常用的SQLAlchemy列约束选项
选项名 | 说明 |
---|---|
primary_key | 如果为True,代表表的主键 |
unique | 如果为True,代表这列不允许出现重复的值 |
index | 如果为True,为这列创建索引,提高查询效率 |
nullable | 如果为True,允许有空值,如果为False,不允许有空值 |
default | 为这列定义默认值 |
数据库基本操作
定义模型类
class Student(db.Model):
# 表结构声明
__tablename__ = "tb_student"
# 字段声明
id = db.Column(db.Integer, primary_key=True, comment="主键")
name = db.Column(db.String(64), index=True, comment="姓名")
sex = db.Column(db.Boolean, default=True, comment="性别")
age = db.Column(db.SmallInteger, nullable=True, comment="年龄")
email = db.Column(db.String(128), unique=True, comment="邮箱地址")
money = db.Column(db.Numeric(8,2), default=0, comment="钱包")
# 自定义方法
def __repr__(self):
return 'Student:%s' % self.name
class Teacher(db.Model):
# 表结构声明
__tablename__ = 'tb_teacher'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师")
def __repr__(self):
return 'Teacher:%s' % self.name
class Course(db.Model):
# 定义表名
__tablename__ = 'tb_course'
# 定义字段对象
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
price = db.Column(db.Numeric(6,2))
# repr()方法类似于django的__str__,用于打印模型对象时显示的字符串信息
def __repr__(self):
return 'Course:%s'% self.name
创建表
db.create_all() # 注意,create_all()方法执行的时候,需要放在模型的后面
# 上面这段语句,后面我们需要转移代码到flask-script的自定义命令中。
# 执行了一次以后,需要注释掉。
删除表
db.drop_all()
代码:
from flask import Flask
# 初始化
app = Flask(import_name=__name__)
# 声明和加载配置
class Config():
DEBUG = True
# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/students?charset=utf8"
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
# 初始化SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() # 初始化数据库操作对象
db.init_app(app) # 初始化数据库链接
class Student(db.Model):
# 表结构声明
__tablename__ = "tb_student"
# 字段声明
id = db.Column(db.Integer, primary_key=True, comment="主键")
name = db.Column(db.String(64), index=True, comment="姓名")
sex = db.Column(db.Boolean, default=True, comment="性别")
age = db.Column(db.SmallInteger, nullable=True, comment="年龄")
email = db.Column(db.String(128), unique=True, comment="邮箱地址")
money = db.Column(db.Numeric(8,2), default=0, comment="钱包")
# 自定义方法
def __repr__(self):
return 'Student:%s' % self.name
class Teacher(db.Model):
# 表结构声明
__tablename__ = 'tb_teacher'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师")
def __repr__(self):
return 'Teacher:%s' % self.name
class Course(db.Model):
# 定义表名
__tablename__ = 'tb_course'
# 定义字段对象
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
price = db.Column(db.Numeric(6,2))
# repr()方法类似于django的__str__,用于打印模型对象时显示的字符串信息
def __repr__(self):
return 'Course:%s'% self.name
@app.route(rule='/')
def index():
return "ok"
if __name__ == '__main__':
with app.app_context():
# db.drop_all() # 删除所有的数据表
db.create_all() # 创建所有的数据表
# 运行flask
app.run(debug=True)
添加一条数据
student1 = Student(name="小明", sex=True, age=17, email="[email protected]", money=100)
db.session.add(student1)
db.session.commit()
#再次插入 一条数据
student2 = Student(name='小红', sex=False, age=13, email="[email protected]", money=600)
db.session.add(student2)
db.session.commit()
一次插入多条数据
st1 = Student(name='wang',email='[email protected]',age=22)
st2 = Student(name='zhang',email='[email protected]',age=22)
st3 = Student(name='chen',email='[email protected]',age=22)
st4 = Student(name='zhou',email='[email protected]',age=22)
st5 = Student(name='tang',email='[email protected]',age=22)
st6 = Student(name='wu',email='[email protected]',age=22)
st7 = Student(name='qian',email='[email protected]',age=22)
st8 = Student(name='liu',email='[email protected]',age=22)
st9 = Student(name='li',email='[email protected]',age=22)
st10 = Student(name='sun',email='[email protected]',age=22)
db.session.add_all([st1,st2,st3,st4,st5,st6,st7,st8,st9,st10])
db.session.commit()
删除数据
# 方法1
student = Student.query.first()
db.session.delete(student)
db.session.commit()
# 方法2【事务中使用,就是乐观锁】
ret = Student.query.filter(Student.name=='sun').delete()
db.session.commit()
更新数据
# 方法1
student = Student.query.first()
student.name = 'dong'
db.session.commit()
# 方法2【事务中使用,就是乐观锁】
ret = Student.query.filter(Student.name == 'liu').update({
'money': 1000})
db.session.commit()
# 方法3【批量操作, 实现类似django里面F函数的效果】
ret = Student.query.filter(Student.age == 22).update({
Student.money: Student.money+'200'})
db.session.commit()
数据基本查询
常用的SQLAlchemy查询过滤器
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit() | 使用指定的值限定原查询返回的结果 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
常用的SQLAlchemy查询结果的方法
方法 | 说明 |
---|---|
all() | 以列表形式返回查询的所有结果 |
first() | 返回查询的第一个结果,如果未查到,返回None |
first_or_404() | 返回查询的第一个结果,如果未查到,返回404 |
get() | 返回指定主键对应的行,如不存在,返回None |
get_or_404() | 返回指定主键对应的行,如不存在,返回404 |
count() | 返回查询结果的数量 |
paginate() | 返回一个Paginate分页器对象,它包含指定范围内的结果 |
having | 返回结果中符合条件的数据,必须跟在group by后面,其他地方无法使用。 |
get():参数为数字,表示根据主键查询数据,如果主键不存在返回None
Student.query.get()
all()返回查询到的所有对象
Student.query.all()
first()返回查询到的第一个对象【first获取一条数据,all获取多条数据】
Student.query.first()
多条件查询
逻辑非,返回名字不等于wang的所有数据
Student.query.filter(Student.name!='wang').all()
not_ 相当于取反
from sqlalchemy import not_
Student.query.filter(not_(Student.name=='wang')).all()
逻辑与,需要导入and,返回and()条件满足的所有数据
from sqlalchemy import and_
Student.query.filter(and_(Student.name!='wang',Student.email.endswith('163.com'))).all()
逻辑或,需要导入or_
from sqlalchemy import or_
Student.query.filter(or_(Student.name!='wang',Student.email.endswith('163.com'))).all()
in_范围查询
"""查询id为2, 3, 5, 7, 8这几个学生信息"""
student_list = Student.query.filter(Student.id.in_([2, 3, 5, 7, 8])).all()
print(student_list)
order_by 排序
# 查询所有学生,并按年龄进行倒序排列
ret = Student.query.order_by(Student.age.desc()).all()
# 查询所有学生,并按年龄进行倒序排列,年龄相同,则按id进行降序排序.
ret = Student.query.order_by(Student.age.desc(),Student.id.desc()).all()
count统计
# 查询age>=19的男生的数量
from sqlalchemy import and_
# ret = Student.query.filter( and_(Student.age>=19,Student.sex==True) ).count()
ret = Student.query.filter( Student.age>=19, Student.sex==True ).count()
分页器的使用:
from flask import Flask,request,jsonify,render_template
from config import Config
from models import db,Student,Course,Teacher
# 初始化
app = Flask(import_name=__name__,template_folder='templates')
app.config.from_object(Config)
db.init_app(app) # 初始化数据库链接
"""分页器使用"""
@app.route(rule="/list")
def list():
pagination = Student.query.paginate(per_page=3)
# 获取当前页面所有数据
# print( pagination.items )
# data = {
# "items": [],
# "pages": pagination.pages,
# "page": pagination.page,
# "has_prev": pagination.has_prev,
# "has_next": pagination.has_next,
# }
# for item in pagination.items:
# data["items"].append({
# "id": item.id,
# "sex": "男" if item.sex else "女",
# "age": item.age,
# "name": item.name,
# })
#
# if pagination.has_prev:
# print( pagination.prev() ) # 上一页数据的分页器对象
# print( pagination.prev().items ) # 上一页数据
#
# if pagination.has_next:
# print( pagination.next() ) # 下一页数据的分页器对象
# print( pagination.next().items ) # 下一页数据
return render_template("list.html",pagination=pagination)
if __name__ == '__main__':
# 运行flask
app.run(debug=True)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.page a,.page span{
padding: 2px 6px;
color: #fff;
background: #6666ff;
text-decoration: none;
}
.page span{
color: #fff;
background: orange;
}
</style>
</head>
<body>
<table border="1" align="center" width="600">
<tr>
<th>ID</th>
<th>age</th>
<th>name</th>
<th>sex</th>
<th>money</th>
</tr>
{% for student in pagination.items %}
<tr>
<td>{
{ student.id }}</td>
<td>{
{ student.age }}</td>
<td>{
{ student.name }}</td>
<td>{
{ "男" if student.sex else "女" }}</td>
<td>{
{ student.money }}</td>
</tr>
{% endfor %}
<tr align="center">
<td colspan="5" class="page">
{% if pagination.has_prev %}
<a href="?page=1">首 页</a>
<a href="?page={
{ pagination.page-1 }}">上一页</a>
<a href="?page={
{ pagination.page-1 }}">{
{ pagination.page-1 }}</a>
{% endif %}
<span>{
{ pagination.page }}</span>
{% if pagination.has_next %}
<a href="?page={
{ pagination.page+1 }}">{
{ pagination.page+1 }}</a>
<a href="?page={
{ pagination.page+1 }}">下一页</a>
<a href="?page={
{ pagination.pages }}">尾 页</a>
{% endif %}
</td>
</tr>
</table>
</body>
</html>
分组查询和分组查询结果过滤
from sqlalchemy import func
函数名 | 说明 |
---|---|
func.count | 统计总数 |
func.avg | 平均值 |
func.min | 最小值 |
func.max | 最大值 |
func.sum | 和 |
from sqlalchemy import func
# ret = db.session.query(Student.sex,func.count(Student.id)).group_by(Student.sex).all()
# 查询当前不同年龄的学生数量
ret = db.session.query(Student.age,func.count(Student.id)).group_by(Student.age).having(Student.age>19).all()
# 查询男生和女生中,年龄最小的是几岁?
ret = db.session.query(Student.sex,func.min(Student.age)).group_by(Student.sex).all()
# 读取多条数据
ret = db.session.execute("select * from tb_student").fetchall()
# 读取一条数据
ret = db.session.execute("select * from tb_student").fetchone()
# 添加/修改/删除
db.session.execute("UPDATE tb_student SET money=(tb_student.money + %s) WHERE tb_student.age = %s" % (200, 22))
db.session.commit()
关联查询
常用的SQLAlchemy关系选项
选项名 | 说明 |
---|---|
backref | 在关系的另一模型中添加反向引用,用于设置外键名称,在1查多的 |
primary join | 明确指定两个模型之间使用的连表条件 |
lazy | 指定如何加载关联模型数据的方式。参数值: select(立即加载,查询所有相关数据显示,相当于lazy=True) subquery(立即加载,但使用子查询) dynamic(不加载记录,但提供加载记录的查询对象) |
uselist | 如果为False,不使用列表,而使用标量值。 一对一关系中,需要设置relationship中的uselist=Flase,其他数据库操作一样。 |
secondary | 指定多对多关系中关系表的名字。 多对多关系中,需建立关系表,设置 secondary=关系表 |
secondary join | 在SQLAlchemy中无法自行决定时,指定多对多关系中的二级连表条件 |
一对一:
class Student(db.Model):
"""个人信息主表"""
....
# 关联属性,这个不会被视作表字段,只是模型的属性。
# 因为StudentInfo和Student是一对一的关系,所以uselist=False表示关联一个数据
info = db.relationship("StudentInfo",uselist=False,backref="own")
class StudentInfo(db.Model):
"""个人信息附加表"""
# 外键,
# 如果是一对一,则外键放在附加表对应的模型中
# 如果是一对多,则外键放在多的表对象的模型中
uid = db.Column(db.Integer, db.ForeignKey(Student.id),comment="外键")
from flask import Flask,request,jsonify,render_template
from config import Config
from models2 import db,Student,Course,Teacher,StudentInfo
# 初始化
app = Flask(import_name=__name__,template_folder='templates')
app.config.from_object(Config)
db.init_app(app) # 初始化数据库链接
@app.route(rule='/')
def index():
"""1对1模型操作"""
# 获取数据[从主表读取数据,获取附加表数据]
# student = Student.query.get(3)
# print( student.info.address )
# print( student.info.edu )
# 获取数据[从附加表读取数据,获取主表数据]
# student_info = StudentInfo.query.filter(StudentInfo.address=="北京市昌平区沙河地铁站对面").first()
# print(student_info.own.name)
# 添加数据[添加数据,把关联模型的数据也一并添加]
# student = Student(name="liu", sex=True, age=22, email="[email protected]", money=100)
# student.info = StudentInfo(address="深圳市宝安区创业2路103号", edu="本科")
# db.session.add(student)
# db.session.commit()
# 修改数据[通过主表可以修改附加表的数据,也可以通过附加表模型直接修改主表的数据]
# student = Student.query.get(4)
# student.info.address = "广州市天河区天河东路103号"
# db.session.commit()
return "ok"
if __name__ == '__main__':
# 运行flask
app.run(debug=True)
一对多
class Teacher(db.Model):
...
# 关联属性,一的一方添加模型关联属性
course = db.relationship("Course", uselist=True, backref="teacher",lazy='dynamic')
class Course(db.Model):
...
# 外键,多的一方模型中添加外间
teacher_id = db.Column(db.Integer, db.ForeignKey(Teacher.id))
其中realtionship描述了Course和Teacher的关系。第一个参数为对应参照的类"Course"
第二个参数backref为类Teacher申明新属性的方法
第三个参数lazy决定了什么时候SQLALchemy从数据库中加载数据
- lazy=‘subquery’,查询当前数据模型时,采用子查询(subquery),把外键模型的属性也瞬间查询出来了。
- lazy=True或lazy=‘select’,查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性时,才进行连表查询数据[执行SQL]
- lazy=‘dynamic’,查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性并操作外键模型具体属性时,才进行连表查询数据[执行SQL]
from flask import Flask,request,jsonify,render_template
from config import Config
from models2 import db,Student,Course,Teacher,StudentInfo
# 初始化
app = Flask(import_name=__name__,template_folder='templates')
app.config.from_object(Config)
db.init_app(app) # 初始化数据库链接
@app.route(rule='/more')
def more():
"""一对多/多对一模型操作"""
# 从1的一方的模型中获取多的一方模型的数据
# teacher = Teacher.query.get(1)
# print(teacher)
# # ret = teacher.course
# for course in teacher.course:
# print(course.name,course.price)
# 从多的一方获取1的一方数据
# course = Course.query.get(1)
# print(course.teacher)
# print(course.teacher.name)
# 添加数据
# 从1的一方添加数据,同时给多的一方也添加
# teacher = Teacher(name="蓝老师",option="讲师")
# teacher.course = [Course(name="插画入门",price=199.00),Course(name="素描入门",price=129.00),]
# db.session.add(teacher)
# db.session.commit()
return "ok"
if __name__ == '__main__':
# 运行flask
app.run(debug=True)
多对多
achievement = db.Table('tb_achievement',
db.Column('student_id', db.Integer, db.ForeignKey('tb_student.id')),
db.Column('course_id', db.Integer, db.ForeignKey('tb_course.id')),
)
class Course(db.Model):
...
students = db.relationship('Student',secondary=achievement,
backref='courses',
lazy='dynamic')
class Student(db.Model):
...
-
查询老师授课的所有课程
#查询讲师表id为1的老师 teacher = Teacher.query.get(1) #查询当前老师的所有课程, 根据模型中关联关系来查询数据 print(teacher.courses)
-
查询课程所属讲师
course = Course.query.get(2) print(course) # 根据外键只能查询到ID数值, SQLAlchemy不会帮我们把ID转换成模型 print( course.teacher_id ) # 要获取外键对应的模型数据,需要找到主键模型里面的 db.relationship 里面的 backref print( course.teacher.name )
数据库迁移
- 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
- 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
- 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
- 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。
首先要在虚拟环境中安装Flask-Migrate。
pip install flask-migrate
创建迁移版本仓库
#这个命令会创建migrations文件夹,所有迁移文件都放在里面。
python main.py db init
创建迁移版本
自动创建迁移版本有两个函数
-
upgrade():函数把迁移中的改动应用到数据库中。
-
downgrade():函数则将改动删除。
-
自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。
python main.py db migrate -m 'initial migration' # 这里等同于django里面的 makemigrations,生成迁移版本文件
升级版本库的版本
python main.py db upgrade
降级版本库的版本
python main.py db downgrade
版本库的历史管理
可以根据history命令找到版本号,然后传给downgrade命令
python manage.py db history
输出格式:<base> -> 版本号 (head), initial migration
回滚到指定版本
python manage.py db downgrade # 默认返回上一个版本
python manage.py db downgrade 版本号 # 返回到指定版本号对应的版本
数据迁移的步骤:
1. 初始化数据迁移的目录
python manage.py db init
2. 数据库的数据迁移版本初始化
python manage.py db migrate -m 'initial migration'
3. 升级版本[创建表/创建字段/修改字段]
python manage.py db upgrade
4. 降级版本[删除表/删除字段/恢复字段]
python manage.py db downgrade
flask-session
允许设置session到指定存储的空间中, 文档:
安装命令: https://pythonhosted.org/Flask-Session/
pip install flask-Session
使用session之前,必须配置一下配置项:
SECRET_KEY = "*(%#4sxcz(^(#$#8423" # session秘钥
redis保存session的基本配置
import redis
class Config(object):
DEBUG = True
SECRET_KEY = "*(%#4sxcz(^(#$#8423"
# 数据库链接配置:
#数据类型://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称
SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/flask_students"
# 设置mysql的错误跟踪信息显示
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 打印每次模型操作对应的SQL语句
SQLALCHEMY_ECHO = True
# 把session保存到redis中
# session存储方式为redis
SESSION_TYPE="redis"
# 如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
SESSION_PERMANENT = False
# 是否对发送到浏览器上session的cookie值进行加密
SESSION_USE_SIGNER = False
# 保存到redis的session数的名称前缀
SESSION_KEY_PREFIX = "session:"
# session保存数据到redis时启用的链接对象
SESSION_REDIS = redis.Redis(host='127.0.0.1', port='6379') # 用于连接redis的配置
from flask import Flask
from config import Config
from flask_session import Session
from flask import session
app = Flask(__name__,template_folder='templates')
app.config.from_object(Config)
Session(app)
@app.route("/")
def index():
return "ok"
@app.route("/set_session")
def set_session():
"""设置session"""
session["username"] = "小明"
return "ok"
if __name__ == '__main__':
app.run()
SQLAlchemy存储session的基本配置
需要手动创建session表,在项目第一次启动的时候,使用db.create_all()来完成创建。
db = SQLAlchemy(app)app.config['SESSION_TYPE'] = 'sqlalchemy' # session类型为
sqlalchemyapp.config['SESSION_SQLALCHEMY'] = db # SQLAlchemy对象
app.config['SESSION_SQLALCHEMY_TABLE'] = 'session' # session要保存的表名称
app.config['SESSION_PERMANENT'] = True # 如果设置为True,则关闭浏览器session就失效。
app.config['SESSION_USE_SIGNER'] = False # 是否对发送到浏览器上session的cookie值进行加密
app.config['SESSION_KEY_PREFIX'] = 'session:' # 保存到session中的值的前缀Session(app)
蓝图 Blueprint
模块化
随着flask程序越来越复杂,我们需要对程序进行模块化的处理,之前学习过python的模块化管理,于是针对一个简单的flask程序进行模块化处理
简单来说,Blueprint 是一个存储视图方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理请求。
Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:
- 一个项目可以具有多个Blueprint
- 可以将一个Blueprint注册到任何一个未使用的URL下比如 “/”、“/sample”或者子域名
- 在一个应用中,一个模块可以注册多次
- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
- 在一个应用初始化时,就应该要注册需要使用的Blueprint
但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。
Blueprint对象用起来和一个应用/Flask对象差不多,最大的区别在于一个 蓝图对象没有办法独立运行,必须将它注册到一个应用对象上才能生效
使用蓝图可以分为四个步骤:
1.创建一个蓝图的包,例如users,并在__init__.py文件中创建蓝图对象
users=Blueprint('users',__name__)
2.在这个蓝图目录下, 创建views.py文件,保存当前蓝图使用的视图函数
@admin.route('/')
def home():
return 'user.home'
3.在users/init.py中引入views.py中所有的视图函数
from flask import Blueprint
# 等同于原来在 manage.py里面的 app = Flask()
users=Blueprint('users',__name__)
from .views import *
4.在主应用main.py文件中的app对象上注册这个users蓝图对象
from users import users
app.register_blueprint(users,url_prefix='/users')
运行机制
- 蓝图是保存了一组将来可以在应用对象上执行的操作,注册路由就是一种操作
- 当在app对象上调用 route 装饰器注册路由时,这个操作将修改对象的url_map路由表
- 然而,蓝图对象根本没有路由表,当我们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操作记录列表defered_functions中添加了一个项
- 当执行app对象的 register_blueprint() 方法时,应用对象将从蓝图对象的 defered_functions 列表中取出每一项,并以自身作为参数执行该匿名函数,即调用应用对象的 add_url_rule() 方法,这将真正的修改应用对象的usr_map路由表
注册蓝图中的静态文件的相关路由
和应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在 创建时指定 static_folder 参数。
下面的示例将蓝图所在目录下的static_users目录设置为静态目录
# users/__init__.py,代码:
user_blu = Blueprint("users",__name__,static_folder='static_users')
# 启动文件 main.py,代码:
from users import user_blu
app.register_blueprint(user_blu,url_prefix='/users')
设置蓝图中模版的目录
蓝图对象默认的模板目录为系统的模版目录,可以在创建蓝图对象时使用 template_folder 关键字参数设置模板目录
创建蓝图中的模板目录template_users :
admin = Blueprint('admin',__name__,template_folder='templates_users')
注:如果在 templates 中存在和 templates_users 有同名模板文件时, 则系统会优先使用 templates 中的文件