Flask教程

Flask基础

什么是Flask

Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架。

Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL。

其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。

官网: https://flask.palletsprojects.com

官方文档: http://docs.jinkan.org/docs/flask/

Flask常用第三方扩展包

扩展名称 功能
Flask-SQLAlchemy 操作数据库,提供对象关系映射 (ORM) 功能
Flask-Script 终端脚本工具,提供脚手架功能
Flask-Migrate 管理数据库迁移
Flask-Session 指定 Session 存储方式
Flask-WTF 表单处理
Flask-Mail 邮件处理
Flask-Babel 提供国际化和本地化支持,翻译
Flask-Login 认证用户状态
Flask-OpenID 认证,OAuth
Flask-RESTful 开发 REST API 的工具
Flask JSON-RPC 开发远程过程调用 (RPC) 服务
Flask-Bootstrap 集成前端 Twitter Bootstrap 框架
Flask-Moment 本地化日期和时间处理
Flask-Admin 简单且可扩展的管理接口框架

创建Flask项目

创建flask虚拟环境

virtualenv venv/flask -p python3

安装flask

virtualenv venv/flask -p python3

在项目目录下创建入口程序文件,名称可以是app.pyrun.pymain.pyindex.py

# 导入Flask类
from flask import Flask

"""
import_name Flask程序所在的包(模块),传 __name__ 就可以
其可以决定 Flask 在访问静态文件时查找的路径
static_path 静态文件访问路径(不推荐使用,使用 static_url_path 代替)
static_url_path 静态文件访问路径,可以不传,默认为:/ + static_folder
static_folder 静态文件存储的文件夹,可以不传,默认为 static
template_folder 模板文件存储的文件夹,可以不传,默认为 templates
"""
app = Flask(import_name=__name__)


# 编写路由视图
# flask的路由是通过给视图添加装饰器的方式进行编写的。当然也可以分离到另一个文件中。
# flask的视图函数,flask中默认允许通过return返回html格式数据给客户端。
@app.route('/')
def index():
return "<h1>hello world</h1>"

# 加载项目配置
class Config(object):
# 开启调试模式
DEBUG = True

# flask中支持多种配置方式,通过app.config来进行加载,我们会这里常用的是配置类
app.config.from_object( Config )


# 指定服务器IP和端口
if __name__ == '__main__':
# 运行flask
app.run(host="0.0.0.0", port=5000)

Flask路由

什么是Flask路由

在 Flask 中,路由用于定义 URL 与视图函数之间的映射关系。通过定义路由,可以将不同的 URL 请求分发到对应的视图函数来处理。

Flask路由命名规范

  1. 使用有意义的名称:为了增加代码的可读性和可理解性,建议为路由选择有意义的名称,以反映其功能或用途。例如,对于用户相关的路由,可以使用类似于 /users/<username>/profile/<user_id> 这样的命名。

  2. 使用动词表示操作:在 RESTful 风格的应用程序中,建议使用动词来表示操作。例如,使用 GET 方法获取资源的路由可以命名为 get_resource,使用 POST 方法创建资源的路由可以命名为 create_resource,以此类推。

  3. 使用名词表示资源:对于表示资源的路由,建议使用名词来命名。例如,如果应用程序提供了博客文章的路由,可以使用 /articles/posts 来表示博客文章的资源。

  4. 使用下划线分隔单词:为了提高可读性,建议在路由名称中使用下划线 _ 来分隔单词。例如,使用 user_profile 而不是 userprofile

  5. 避免使用动词或复杂的命名:在命名路由时,应避免使用过于复杂或包含动词的名称。路由应该更加关注资源和功能,而不是操作。例如,避免使用类似于 /get_user_profile/delete_user_by_id 这样的命名。

  6. 使用路由名称生成 URL:Flask 提供了 url_for() 函数,可以根据路由的名称生成对应的 URL。使用路由名称生成 URL 可以提高代码的可维护性,因为如果路由的 URL 发生更改,只需修改路由定义而不需要在整个应用程序中手动更新 URL。

基本语法

示例:访问路径为/;当浏览器访问根路径时处理函数为index函数

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
return 'Hello, World!'

限定路由参数

限定路由参数的类型,flask系统自带转换器编写在venv/flask/lib/python3.10/site-packages/werkzeug/routing/converters.py文件中。底部可以看到以下字典:

DEFAULT_CONVERTERS: t.Mapping[str, type[BaseConverter]] = {
"default": UnicodeConverter,
"string": UnicodeConverter,
"any": AnyConverter,
"path": PathConverter,
"int": IntegerConverter,
"float": FloatConverter,
"uuid": UUIDConverter,
}

转换器介绍:

转换器名称 描述
string 默认类型,接受不带斜杠的任何文本
int 接受正整数
float 接受正浮点值
path 接收 string 但也接受斜线
uuid 接受 UUID(通用唯一识别码)字符串 xxxx-xxxx-xxxxx-xxxxx

示例:接受int类型参数

# flask提供了路由转换器可以让我们对路由参数进行限定
# 路由传递参数[限定数据类型]
@app.route('/user/<int:user_id>')
def user_info(user_id):
return 'hello %d' % user_id

限定路由请求方式

请求方式 描述
GET 用于获取资源,不对服务器数据做修改
POST 用于向服务器提交数据,对数据进行新增操作
PUT 用于向服务器提交数据,对数据进行修改操作
PATCH 用于向服务器提交数据,对数据进行局部修改操作
DELETE 用于删除服务器上的资源
OPTIONS 用于获取目标资源支持的请求方法列表
HEAD 与 GET 类似,但只返回响应头部,不返回实体主体

示例:

from flask import Flask,request
# 限制客户端的http请求方法,注意这里与django不一样,flask并没有默认没有内置csrf攻击防范
@app.route(rule="/user", methods=["post","put","get","delete","patch"])
def user():

# 例如:地址栏中通过 http://127.0.0.1:5000/user?user=1 返回本视图

print(request.method) # 获取本次客户端的http请求方法 GET
print(request.query_string) # 获取本次客户端的查询字符串 b'user=1'
print(request.path) # 获取本次客户端请求的路由路径部分[去掉域名端口] /user
print(request.url) # 获取本次客户端请求的http url地址 http://127.0.0.1:5000/user?user=1
# 直接从请求中取到请求方式并返回
return request.method

自定义参数转换器

自定义参数转换器也叫正则匹配路由参数;在 web 开发中,可能会出现限制用户访问规则的场景,那么这个时候就需要用到正则匹配,根据自己的规则去限定请求参数再进行访问

具体实现步骤为:

  1. 导入转换器基类:在 Flask 中,所有的路由的匹配规则都是使用转换器对象进行记录
  2. 自定义转换器:自定义类继承于转换器基类
  3. 添加转换器到默认的转换器字典中
  4. 使用自定义转换器实现自定义匹配规则

示例:

  1. 定义了一个名为 MyConverter 的自定义转换器类,并在其 to_python 方法中将参数值转换为 Python 对象。
  2. 在 to_url 方法中将 Python 对象转换为 URL 字符串。
  3. 通过 app.url_map.converters 字典将自定义转换器注册到 Flask 应用中。
  4. 使用 @app.route() 装饰器指定了一个路由,其中的路由参数使用了我们自定义的转换器 <myconverter:date>
  5. 当访问类似于 /date/2022-01-01 的 URL 时,Flask 会自动匹配并调用我们定义的转换器,将参数转换为 Python 对象,并将其传递给视图函数 show_date。
from flask import Flask

app = Flask(__name__)

# 定义自定义转换器类
class MyConverter:
def __init__(self, url_map):
self.regex = r'\d{4}-\d{2}-\d{2}' # 自定义参数匹配的正则表达式
self.url_map = url_map

def to_python(self, value):
# 将参数值转换为 Python 对象(可选)
year, month, day = value.split('-')
return datetime.date(int(year), int(month), int(day))

def to_url(self, value):
# 将 Python 对象转换为 URL 字符串(可选)
return value.strftime('%Y-%m-%d')

# 注册自定义转换器
app.url_map.converters['myconverter'] = MyConverter

# 使用自定义转换器
@app.route('/date/<myconverter:date>')
def show_date(date):
return f"Date: {date}"

if __name__ == '__main__':
app.run()

示例:手机号码转换器

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")

请求与响应

请求

什么是请求?

在 Flask 中,请求是指客户端向服务器发送的 HTTP 请求。Flask 提供了一个 Request 对象,用于处理和访问当前请求的相关信息。Request 对象包含了请求的方法、URL、头部信息、表单数据等。

常用的 Flask 请求对象的属性和方法

属性/方法 描述
method 获取请求的方法
url 获取请求的完整 URL
args 获取请求的查询参数
form 获取请求的表单数据
files 获取请求中上传的文件
headers 获取请求的头部信息
cookies 获取请求中的 Cookie 数据
get(key) 获取指定键的查询参数值
getlist(key) 获取指定键的查询参数值列表
get_json() 获取请求的 JSON 数据
get_data() 获取请求的原始数据
get_file(key) 获取指定键对应的上传文件
get_header(key) 获取指定键的头部字段值
get_cookie(key) 获取指定键的 Cookie 值

示例:获取请求各项数据

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个值或多个值
"""
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")

响应

什么是flask响应

在 Flask 中,响应(Response)是服务器返回给客户端的数据。Flask 提供了一个 Response 对象来创建和设置响应。 Flask 响应对象包含了响应的内容、状态码、响应头等信息。你可以通过创建一个响应对象并设置相应的属性来构建自定义的响应。

常用的 Flask 响应对象的属性和方法

属性/方法 描述
data 响应的内容
status 响应的状态码
headers 响应的头部信息
content_type 响应的内容类型
set_cookie(key, value, **kwargs) 设置响应的 Cookie
delete_cookie(key, **kwargs) 删除响应中的指定 Cookie
make_response([response]) 创建一个响应对象
response Flask 视图函数返回的默认响应对象
response.data 获取响应的内容
response.status 获取响应的状态码
response.headers 获取响应的头部信息
response.content_type 获取响应的内容类型
response.set_cookie(key, value, **kwargs) 设置响应的 Cookie
response.delete_cookie(key, **kwargs) 删除响应中的指定 Cookie

响应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":"liulaoshi","age":18},
{"id":2,"username":"liulaoshi","age":17},
{"id":3,"username":"liulaoshi","age":16},
{"id":4,"username":"liulaoshi","age":15},
]
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")

重定向到index函数

# 内容响应
@app.route("/")
def index():
# [默认支持]响应html文本
# return "<img src='http://flask.pocoo.org/static/logo.png'>"

# 也可以响应json格式代码
data = [
{"id":1,"username":"liulaoshi","age":18},
{"id":2,"username":"liulaoshi","age":17},
{"id":3,"username":"liulaoshi","age":16},
{"id":4,"username":"liulaoshi","age":15},
]
return jsonify(data)

#使用url_for可以实现视图方法之间的内部跳转
# url_for("视图方法名")
@app.route("/login")
def login():
return redirect( url_for("index") )

重定向到带参数的视图函数

# 路由传递参数
@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) )

自定义状态码和响应头

自定义状态码:

from flask import Flask, make_response

app = Flask(__name__)

@app.route('/')
def custom_status_code():
response = make_response('Custom status code', 666)
return response

使用 make_response() 函数创建了一个响应对象,并指定了自定义的状态码为 666。

自定义响应头:

from flask import Flask, make_response

app = Flask(__name__)

@app.route('/')
def custom_response_header():
response = make_response('Custom response header')
response.headers['X-Custom-Header'] = 'Custom Value'
return response

会话控制

会话: 客户端浏览器和服务端网站之间一次完整的交互过程。会话的开始是在用户通过浏览器第一次访问服务端网站开始,会话的结束时在用户通过关闭浏览器以后,与服务端断开。
会话控制: 就是在客户端浏览器和服务端网站之间,进行多次http请求响应之间,记录、跟踪和识别用户的信息而已。 因为 http 是一种无状态协议,浏览器请求服务器是无状态的。
无状态: 指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。
无状态原因: 浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象。

实现状态保持主要有两种方式:

  1. 在客户端存储信息使用Cookie,token[jwt,oauth]
  2. 在服务器端存储信息使用Session

什么是Cookie

Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie的key/value可以由服务器端自己定义。Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用。Cookie基于域名安全,不同域名的Cookie是不能互相访问的。当浏览器请求某网站时,会将本网站下所有Cookie信息提交给服务器,所以在request中可以读取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

在 Flask 中,Session 是一种在客户端和服务器之间存储和共享数据的机制。它允许你在多个请求之间保持用户的状态,并在每个请求中访问和修改这些数据。 Flask 使用一个称为 Flask Session 的扩展来管理会话。该扩展提供了一个字典对象,你可以像操作字典一样操作会话数据。会话数据存储在服务器端,但在每个请求中,一个唯一的会话标识符会通过 Cookie 或 URL 参数发送给客户端,以便将会话与用户关联起来。 通过使用 Flask Session,你可以在会话中存储用户的认证状态、用户偏好设置、购物车内容等。会话数据被加密和签名,以确保数据的安全性和完整性。

常用的session方法和属性

功能/方法 描述
session 会话对象,用于访问和修改会话数据
session[key] 访问会话数据中指定键的值
session[key] = value 设置会话数据中指定键的值
session.get(key, default) 获取会话数据中指定键的值,如果键不存在则返回默认值
session.pop(key, default) 删除会话数据中指定键的值,并返回该值
session.clear() 清除会话数据,使会话变为空
session.modified 判断会话数据是否被修改过

设置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 Session

Flask Session是什么

Flask-Session是Flask框架的一个扩展,用于管理和处理会话(Session)数据。会话是一种在Web应用中存储和跟踪用户状态信息的机制。Flask-Session扩展提供了在Flask应用中使用会话的功能,包括会话数据的存储、读取和修改等操作。

Flask-Session扩展的主要功能

功能 描述
存储会话数据 将会话数据存储在不同的位置,如服务器端的内存、数据库或文件系统中,以便于后续的访问和使用。
加密会话数据 支持对会话数据进行加密,确保数据的安全性和完整性,防止数据被篡改或窃取。
会话数据的访问和操作 可以方便地读取和修改会话数据,比如获取当前用户的登录状态、存储用户的个人偏好设置等。
会话过期和超时处理 支持设置会话的过期时间和超时时间,可以根据需求自定义会话的有效期,并提供相应的处理机制。
会话的持久化 可以选择将会话数据持久化到数据库或文件系统中,以保证会话数据的持久性,即使应用程序重启或用户重新访问时仍能保持会话状态。
会话的共享 支持在多个应用实例之间共享会话数据,以实现分布式环境下的会话管理和状态同步。

安装使用Flask Session

安装

pip install flask-Session

配置secket key

SECRET_KEY = "*(%#4sxcz(^(#$#8423" # session秘钥

redis保存session的配置

import redis
class Config(object):
DEBUG = True
SECRET_KEY = "*(%#4sxcz(^(#$#8423"
# 数据库链接配置:
#数据类型://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称
SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1: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的配置

使用session视图函数代码

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类型为sqlalchemy
app.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)

调整session配置

  1. 分析SQLAlachemy的构造方式可以发现,初始化并非一定要传递app应用对象到内部,事实上它提供了init_app方法给我们后续调用。而 init_app 方法是flask框架要求任何的第三方组件都要实现这个方法。
  2. init_app方法内部就是要第三方组件开发者编写一些使用当前组建的默认配置项以及把当前组件设置成一个对象,加载到app对象内部extensions字典才能让开发者在flask框架内部配置和使用当前组件。
  3. 我们可以利用这种组件开发机制,那么把配置代码抽离出去。

配置文件

import redis
from flask_sqlalchemy import SQLAlchemy
# 创建db对象
db = SQLAlchemy()
class Config(object):
DEBUG = True
SECRET_KEY = "*(%#4sxcz(^(#$#8423"
# 数据库链接配置:
#数据类型://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称
SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1: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的配置

SESSION_TYPE= 'sqlalchemy' # session的存储方式为sqlalchemy
SESSION_SQLALCHEMY= db # SQLAlchemy对象
SESSION_SQLALCHEMY_TABLE= 'sessions' # session要保存的表名称
SESSION_PERMANENT= True # 如果设置为True,则关闭浏览器session就失效。
SESSION_USE_SIGNER= False # 是否对发送到浏览器上session的cookie值进行加密
SESSION_KEY_PREFIX= 'session:' # 保存到session中的值的前缀

视图函数代码

from flask import Flask
from config import Config,db
from flask_session import Session

from flask import session

app = Flask(__name__,template_folder='templates')
app.config.from_object(Config)

# 把app加载到db对象中
db.init_app(app)

Session(app)

@app.route("/")
def index():
return "ok"

@app.route("/set_session")
def set_session():
"""设置session"""
session["username"] = "小明"
return "ok"

if __name__ == '__main__':
# db.create_all()
print( app.url_map )
app.run()

数据库模型

SQLAlchemy

什么是SQLALchemy

flask默认提供模型操作,但是并没有提供ORM,所以一般开发的时候我们会采用flask-SQLAlchemy模块来实现ORM操作。 SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。 SQLAlchemy: https://www.sqlalchemy.org/

安装 flask-sqlalchemy【清华源】

pip install flask-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple

如果是mysql数据库

pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple

Flask支持的数据库类型

扩展包 支持的数据库
Flask-SQLAlchemy SQLite, MySQL, PostgreSQL, Oracle 等
Flask-MongoEngine MongoDB
Flask-PyMongo MongoDB
Flask-Redis Redis
Flask-CouchDB CouchDB
Flask-SQLite SQLite
其他原生 Python 数据库驱动程序 根据驱动程序和数据库类型而定

数据库连接设置

不同类型数据库连接字符串,参考:

数据库类型 配置示例
SQLite app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///path/to/database.db'
MySQL app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://username:password@host:port/database_name'
PostgreSQL app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@host:port/database_name'
MongoDB app.config['MONGODB_SETTINGS'] = 'db': 'database_name','host': 'mongodb://username:password@host:port/database_name'
Redis app.config['REDIS_URL'] = 'redis://:password@host:port/0'
  1. 使用配置文件:

在 Flask 的配置文件(例如 config.py)中添加数据库连接相关的配置项,如下所示:

# config.py

SQLALCHEMY_DATABASE_URI = '数据库连接字符串'
SQLALCHEMY_TRACK_MODIFICATIONS = False

在应用程序中导入配置文件,并加载配置:

# app.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.from_pyfile('config.py')

db = SQLAlchemy(app)
  1. 直接在应用程序中设置:

在应用程序中直接设置数据库连接相关的配置项,如下所示:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = '数据库连接字符串'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

SQLAlchemy字段类型

字段类型 示例
Integer Integer
BigInteger BigInteger
SmallInteger SmallInteger
Float Float
Numeric Numeric
String String(255)
Text Text
Boolean Boolean
Date Date
Time Time
DateTime DateTime
Interval Interval
Enum Enum('option1', 'option2')
JSON JSON
PickleType PickleType
LargeBinary LargeBinary
ForeignKey ForeignKey('other_table.id')
Relationship relationship('OtherModel')

SQLAlchemy字段约束条件

约束条件 示例
PrimaryKey primary_key=True
Unique unique=True
CheckConstraint CheckConstraint('column_name >= 0')
ForeignKeyConstraint ForeignKeyConstraint(['column1', 'column2'], ['other_table.column1', 'other_table.column2'])
Index Index('index_name', 'column')
UniqueConstraint UniqueConstraint('column1', 'column2', name='uq_column1_column2')
Default default='default_value'
Nullable nullable=False
AutoIncrement autoincrement=True
OnDelete ondelete='CASCADE'
OnUpdate onupdate='CASCADE'
ForeignKey ForeignKey('other_table.id', ondelete='CASCADE', onupdate='CASCADE')

定义模型类

from flask import Flask
# 初始化
app = Flask(import_name=__name__)

# 声明和加载配置
class Config():
DEBUG = True
# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1: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__':
# 运行flask
app.run(debug=True)

数据库表操作

创建表

db.create_all()

删除表

db.drop_all()

示例代码:

from flask import Flask
# 初始化
app = Flask(import_name=__name__)

# 声明和加载配置
class Config():
DEBUG = True
# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1: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)

数据操作

基本查询

查询数据

操作 示例
查询所有数据 Model.query.all()
根据主键查询单条数据 Model.query.get(id)
根据条件查询数据 Model.query.filter_by(column=value).all()
使用复杂条件查询数据 Model.query.filter(condition).all()
查询第一条数据 Model.query.first()
查询单个结果(只返回第一个结果) Model.query.filter_by(column=value).first()

插入数据

操作 示例
创建一个实例对象 new_data = Model(column1=value1, column2=value2, ...)
将实例对象添加到会话中 db.session.add(new_data)
提交会话以保存数据 db.session.commit()

更新数据

操作 示例
根据条件查询要更新的数据 data = Model.query.filter_by(condition).first()
更新数据的属性值 data.column = new_value
提交会话以保存更新 db.session.commit()

删除数据

操作 示例
根据条件查询要删除的数据 data = Model.query.filter_by(condition).first()
将数据从会话中删除 db.session.delete(data)
提交会话以保存删除 db.session.commit()

常用的SQLAlchemy查询过滤器

过滤器 描述
filter() 根据指定条件筛选结果,可以使用多个条件组合,条件之间是逻辑与关系。
filter_by() 根据指定的关键字参数筛选结果,适用于简单的等值条件筛选。
limit() 限制查询结果的数量,指定返回的记录数。
offset() 设置查询结果的偏移量,指定要跳过的记录数。
order_by() 按照指定的属性排序查询结果,可以使用多个属性和排序方式。
group_by() 根据指定的属性对查询结果进行分组。
distinct() 去除查询结果中的重复记录。
join() 进行表之间的连接查询,可以通过指定关联条件实现不同表之间的联接。
outerjoin() 进行外连接查询,包括左外连接和右外连接。
having() 对分组后的结果进行筛选,类似于 WHERE 条件但用于分组后的结果。
exists() 检查是否存在符合指定条件的记录。
any() 检查指定关联对象集合中是否至少存在一个符合条件的对象。
all() 检查指定关联对象集合中是否全部符合条件。
scalar() 返回查询结果的第一列值。
first() 返回查询结果的第一条记录。
one() 返回查询结果的唯一一条记录,如果结果不唯一则抛出异常。
count() 返回查询结果的记录数。
sum() 对指定属性的值进行求和计算。
avg() 对指定属性的值进行平均计算。
max() 返回指定属性的最大值。
min() 返回指定属性的最小值。

常用的SQLAlchemy查询结果的方法

方法 描述
all() 获取查询结果的所有记录,返回一个包含所有记录的列表。
first() 获取查询结果的第一条记录,返回一个单个记录对象或 None
one() 获取查询结果的唯一一条记录,如果结果不唯一则抛出异常。
scalar() 返回查询结果的第一列值。
get() 根据主键值获取查询结果的记录,返回一个单个记录对象或 None
count() 返回查询结果的记录数。
limit(n) 限制查询结果的数量,指定返回的记录数为 n
offset(n) 设置查询结果的偏移量,跳过前 n 条记录。
order_by(column) 按照指定的属性对查询结果进行排序。
distinct() 去除查询结果中的重复记录。
group_by(column) 根据指定的属性对查询结果进行分组。
having(condition) 对分组后的结果进行筛选。
join(table) 进行表之间的连接查询。
outerjoin(table) 进行外连接查询。
delete() 删除查询结果的记录。
update(values) 更新查询结果的记录属性值。

Filter模糊查询

# name姓名中以"g"结尾的学生
ret = Student.query.filter(Student.name.endswith("g")).all()
# name姓名中包含"u"的学生
ret = Student.query.filter(Student.name.contains("u")).all()
# name姓名中以"w"开头的学生
ret = Student.query.filter(Student.name.startswith("w")).all()


# 也可以使用filter进行精确查找,
# 则需要指定条件格式为: 模型.字段 比较运算符 值。
# 运算符可以是: ==表示相等,!=不相等,> 表示大于 < 表示小于,>=大于等于,<=小于等于
# ret = Student.query.filter(Student.age==22).all()

# 另一种写法的查询方式
# db.session.query(Student) 相当于 Student.query
# ret = db.session.query(Student).filter(Student.age==22).all()

逻辑条件查询

逻辑非,返回名字不等于wang的所有数据

"""
查询出名字不等于liu的所有男学生
"""
ret = Student.query.filter(Student.name!="liu",Student.sex==True).all()

not_ 取反

from sqlalchemy import not_
"""
查询出名字不等于liu的所有男学生
"""
ret = Student.query.filter(not_(Student.name=="liu"),Student.sex==True).all()

逻辑与,需要导入and,返回and()条件满足的所有数据

from sqlalchemy import and_
"""
and_ 且,与
查询出年龄在18-21之间的女生
"""
ret1 = Student.query.filter(and_(Student.age>=18,Student.age<=21,Student.sex==False)).all()
ret2 = Student.query.filter(Student.age>=18,Student.age<=21,Student.sex==False).all()
print(ret1)
print(ret2)

逻辑或,需要导入or_

from sqlalchemy import or_
"""
查询出大于21,小于20的男生
"""
ret = Student.query.filter(or_(Student.age>21,Student.age<20),Student.sex==True).all()

"""
查询大于21的男生和小于20的女生
"""
ret1 = Student.query.filter(or_(and_(Student.age>21,Student.sex==True),and_(Student.age<20,Student.sex==False))).all()
print(ret1) # [wang, zhang, chen, zhou, wu, qian, liu, li]

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()

对结果进行偏移量和数量的限制

# 查询年龄最大的3个学生
ret1 = Student.query.order_by(Student.age.desc()).limit(3).all()

# 查询年龄排第4到第7名的学生
ret2 = Student.query.order_by(Student.age.desc(),Student.id.desc()).offset(4).limit(4).all()
print(ret1,ret2)
# 查询名字和邮箱都以 li 开头的所有数据[2种方式]
ret = Student.query.filter(Student.name.startswith("li"),Student.email.startswith("li")).all()
# 查询age是 18 或者 `email` 以 `163.com` 结尾的所有学生
Student.query.filter(or_(Student.age==18,Student.email.endswith("163.com"))).all()
# 查询id为 [1, 3, 5, 7, 9] 的学生列表
student_list = Student.query.filter(Student.id.in_([1, 3, 5, 7, 9])).all()
print(student_list)
# 查询男生和女生的数量
ret = Student.query.filter_by(sex=True).count()
ret = Student.query.filter_by(sex=False).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)

分组查询

分组都会结合聚合函数来一起使用。SQLAlchemy中所有的聚合函数都在func模块中声明的

聚合函数 描述
func.count() 计算指定列的非空记录数量。
func.sum() 计算指定列的数值总和。
func.avg() 计算指定列的平均值。
func.min() 找出指定列的最小值。
func.max() 找出指定列的最大值。
func.distinct() 返回指定列的不重复值。
func.concat() 连接多个字符串列的值。
func.lower() 将指定列的值转换为小写。
func.upper() 将指定列的值转换为大写。
func.trim() 去除指定列值的前导和尾随空格。
func.coalesce() 返回第一个非空值。
func.group_concat() 将指定列的值连接为一个字符串,多个值之间使用指定的分隔符。仅适用于某些数据库(如 MySQL)特定的聚合函数。

示例代码

# 查询当前所有男生女生的数量
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()

关联查询

常用的SQLAlchemy关系选项

关系选项 描述
backref 在另一个模型中自动创建反向引用关系。
primaryjoin 指定两个模型之间的主键关系。
secondary 指定多对多关系中的中间表。
secondaryjoin 指定多对多关系中的联接条件。
uselist 定义关系是否返回单个对象或对象列表。
lazy 定义关系的加载方式。
cascade 定义关系的级联操作。
single_parent 定义关系中是否允许一个对象只有一个父对象。
order_by 指定关系中对象的排序方式。
back_populates 在另一个模型中手动设置反向引用关系。
post_update 定义关系中对象的更新行为。
remote_side 指定关系中另一侧的属性或列。
passive_deletes 定义关系中的被动删除行为。
passive_updates 定义关系中的被动更新行为。
foreign_keys 定义关系中的外键约束。
ondelete 定义关系中的删除行为。
onupdate 定义关系中的更新行为。
constraint 定义关系中的约束条件。
inherit_condition 定义关系中的继承条件。
viewonly 定义关系是否只读。
collection_class 指定关系的集合类。
order_by 定义关系中对象的排序方式。

一对一关联查询

models.py代码

# 初始化SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() # 初始化数据库操作对象

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="钱包")
# 关联属性,这个不会被视作表字段,只是模型的属性。
# 因为StudentInfo和Student是一对一的关系,所以uselist=False表示关联一个数据
info = db.relationship("StudentInfo",uselist=False,backref="own")
# 自定义方法
def __repr__(self):
return 'Student:%s' % self.name

class StudentInfo(db.Model):
# 表明
__tablename__ = "tb_student_info"
# 字段
id = db.Column(db.Integer, primary_key=True, comment="主键")
address = db.Column(db.String(299), comment="住址")
edu = db.Column(db.Enum("高中以下","大专高技","本科","硕士","博士以上"))
# uid = db.Column(db.Integer, db.ForeignKey("tb_student.id"),comment="外键")
# 外键,
# 如果是一对一,则外键放在附加表对应的模型中
# 如果是一对多,则外键放在多的表对象的模型中
uid = db.Column(db.Integer, db.ForeignKey(Student.id),comment="外键")

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

视图函数run.py代码

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="33523@qq.com", 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)

一对多关联查询

modes.py代码

# 初始化SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() # 初始化数据库操作对象

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="钱包")
# 关联属性,这个不会被视作表字段,只是模型的属性。
# 因为StudentInfo和Student是一对一的关系,所以uselist=False表示关联一个数据
info = db.relationship("StudentInfo",uselist=False,backref="own")
# 自定义方法
def __repr__(self):
return 'Student:%s' % self.name

class StudentInfo(db.Model):
# 表明
__tablename__ = "tb_student_info"
# 字段
id = db.Column(db.Integer, primary_key=True, comment="主键")
address = db.Column(db.String(299), comment="住址")
edu = db.Column(db.Enum("高中以下","大专高技","本科","硕士","博士以上"))
# uid = db.Column(db.Integer, db.ForeignKey("tb_student.id"),comment="外键")
# 外键,
# 如果是一对一,则外键放在附加表对应的模型中
# 如果是一对多,则外键放在多的表对象的模型中
uid = db.Column(db.Integer, db.ForeignKey(Student.id),comment="外键")

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="讲师")
# 关联属性,一的一方添加模型关联属性
course = db.relationship("Course", uselist=True, backref="teacher",lazy='dynamic')
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))
# 外键,多的一方模型中添加外间
teacher_id = db.Column(db.Integer, db.ForeignKey(Teacher.id))
# repr()方法类似于django的__str__,用于打印模型对象时显示的字符串信息
def __repr__(self):
return 'Course:%s'% self.name

run.py视图函数代码

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)

多对多关联查询

modes.py模型代码

from flask import Flask,render_template

app = Flask(__name__)
class Config(object):
DEBUG = True
SECRET_KEY = "dsad32DASSLD*13%^32"
# 数据库链接配置
SQLALCHEMY_DATABASE_URI="mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4"
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True

app.config.from_object(Config)

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
db.init_app(app)

class Student(db.Model):
"""学生信息"""
# 1. 表相关属性
__tablename__ = "tb_student" # 设置表明
# 2. 字段类型
# db.Column 表示当前属性对应数据库字段
id = db.Column(db.Integer, primary_key=True, comment="ID")
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="钱包")
# 1. 在主键中设置关联模型属性,这个属性不是数据库的表字段,所以不会在数据库的表中显示出来.
# 主要是提供给模型操作的.
# 在一对一或者,多对一的时候, lazy的值不能是 dynamic.否则报错
info = db.relationship("StudentInfo", uselist=False, backref="student",lazy="subquery")
course_list = db.relationship("Achievement", backref="student", lazy=True)
# 3. 模型方法
def __repr__(self):
return "%s" % self.name

class StudentInfo(db.Model):
"""学生信息附加表"""
__tablename__ = "tb_student_info" # 设置表名
id = db.Column(db.Integer, primary_key=True, comment="ID")
# 数据库中的外键,数据库的表中是存在的
sid = db.Column(db.Integer, db.ForeignKey("tb_student.id"),comment="学生ID")
address = db.Column(db.String(250), nullable=True, comment="家庭地址")
mobile = db.Column(db.String(15), nullable=True, comment="紧急联系人")
def __repr__(self):
return "%s" % self.student.name

class Teacher(db.Model):
# 表结构声明
__tablename__ = 'tb_teacher'

id = db.Column(db.Integer, primary_key=True, comment="ID")
name = db.Column(db.String(64), comment="姓名")
option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师", comment="身份")
course_list = db.relationship("Course", backref="teacher",lazy=True)

def __repr__(self):
return '%s[%s]' % (self.option,self.name)

class Course(db.Model):
# 定义表名
__tablename__ = 'tb_course'
# 定义字段对象
id = db.Column(db.Integer, primary_key=True)
teahcer_id = db.Column(db.Integer, db.ForeignKey(Teacher.id), comment="老师ID")
name = db.Column(db.String(64), unique=True)
price = db.Column(db.Numeric(6,2))
student_list = db.relationship("Achievement", backref="course",lazy=True)
def __repr__(self):
return '%s' % self.name

class Achievement(db.Model):
"""学生和课程之间的成绩关系模型"""
__tablename__ = 'tb_achievement'
id = db.Column(db.Integer, primary_key=True)
student_id = db.Column(db.Integer,db.ForeignKey(Student.id), comment="学生")
course_id = db.Column(db.Integer,db.ForeignKey(Course.id), comment="课程")
def __repr__(self):
return '%s-%s' % (self.student,self.course)

@app.route("/")
def index():
"""
多对多
"""
"""1. 添加主键模型,给外键模型添加数据"""
# course1 = Course(name="3天Python入门",price=99.99)
# course2 = Course(name="7天Python入门",price=399.99)
# db.session.add_all([course1,course2])
#
# student = Student(name="xiaoming",age=17,sex=True, email="64123@qq.com",money=10000)
# student.course_list = [
# Achievement(course=course1),
# Achievement(course=course2),
# ]
# db.session.add(student)
# db.session.commit()
#
"""2. 查询操作"""
student = Student.query.filter(Student.name=="xiaoming").first()
print(student.course_list)
for achievement in student.course_list:
print(achievement.course.name)

return "ok"

if __name__ == '__main__':
# with app.app_context():
# db.drop_all()
# db.create_all()
app.run(debug=True)

数据库迁移

  1. 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
  2. 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
  3. 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
  4. 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。

首先要在虚拟环境中安装Flask-Migrate

pip install flask-migrate

示例代码:

from flask import Flask
from config import Config
from flask_migrate import Migrate,MigrateCommand
from flask_script import Manager,Command

app = Flask(__name__,template_folder='templates')
app.config.from_object(Config)

manage = Manager(app)

"""模型的创建"""
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)



#第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例
migrate = Migrate(app,db)

#manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
manage.add_command('db',MigrateCommand)

# 多对多的关系
# 关系表的声明方式
achieve = 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):
# 定义表名
__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))
teacher_id = db.Column(db.Integer, db.ForeignKey('tb_teacher.id'))
students = db.relationship('Student', secondary=achieve, backref='courses', lazy='subquery')
# repr()方法类似于django的__str__,用于打印模型对象时显示的字符串信息
def __repr__(self):
return 'Course:%s'% self.name

class Student(db.Model):
__tablename__ = 'tb_student'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
email = db.Column(db.String(64),unique=True)
age = db.Column(db.SmallInteger,nullable=False)
sex = db.Column(db.Boolean,default=1)

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)
# 课程与老师之间的多对一关联
courses = db.relationship('Course', backref='teacher', lazy='subquery')

def __repr__(self):
return 'Teacher:%s' % self.name


@app.route("/")
def index():
return "ok"

if __name__ == '__main__':
manage.run()

创建迁移版本仓库

#这个命令会创建migrations文件夹,所有迁移文件都放在里面。
python main.py db init

创建迁移版本

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

蓝图 Blueprint

什么是蓝图 Blueprint

在Flask框架中,蓝图(Blueprint)是一种组织和管理路由、视图函数和静态文件等的机制。它可以将应用程序的不同功能模块划分为独立的蓝图,从而使代码结构更清晰、模块化,并且便于维护和扩展。

蓝图 Blueprint主要功能

  1. 路由和视图函数的组织:通过蓝图,可以将相关联的路由和视图函数进行组织和管理。每个蓝图可以定义自己的路由规则和处理函数,使代码结构更清晰和可读。
  2. URL前缀和子域名:蓝图可以定义URL前缀,用于对蓝图下的路由进行命名空间的管理。同时,蓝图还支持通过子域名来进行路由的分组和管理。
  3. 静态文件的处理:蓝图可以定义自己的静态文件目录,使静态文件的访问路径与蓝图关联起来。这样可以方便地组织和管理静态文件,避免冲突和混淆。
  4. 错误处理和异常处理:蓝图可以定义自己的错误处理和异常处理函数,用于处理在蓝图范围内发生的错误和异常情况。这样可以提供更灵活和定制化的错误处理机制。
  5. 中间件的使用:蓝图可以应用中间件,对请求和响应进行预处理和后处理。中间件可以用于认证、授权、日志记录等功能,增强蓝图的功能和灵活性。

蓝图使用步骤

使用蓝图可以分为四个步骤

  1. 创建一个蓝图的包,例如users,并在__init__.py文件中创建蓝图对象
users=Blueprint('users',__name__)
  1. 在这个蓝图目录下, 创建views.py文件,保存当前蓝图使用的视图函数
@admin.route('/')
def home():
return 'user.home'
  1. 在users/init.py中引入views.py中所有的视图函数
from flask import Blueprint
# 等同于原来在 manage.py里面的 app = Flask()
users=Blueprint('users',__name__)

from .views import *
  1. 在主应用main.py文件中的app对象上注册这个users蓝图对象
from users import users
app.register_blueprint(users,url_prefix='/users')
  1. 当这个应用启动后,通过/users/可以访问到蓝图中定义的视图函数

蓝图的运行机制

  1. 蓝图是保存了一组将来可以在应用对象上执行的操作,注册路由就是一种操作
  2. 当在app对象上调用 route 装饰器注册路由时,这个操作将修改对象的url_map路由表
  3. 然而,蓝图对象根本没有路由表,当我们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操作记录列表defered_functions中添加了一个项
  4. 当执行app对象的 register_blueprint() 方法时,应用对象将从蓝图对象的 defered_functions 列表中取出每一项,并以自身作为参数执行该匿名函数,即调用应用对象的 add_url_rule() 方法,这将真正的修改应用对象的usr_map路由表

蓝图的url前缀

  1. 当我们在应用对象上注册一个蓝图时,可以指定一个url_prefix关键字参数(这个参数默认是/)
  2. 在应用最终的路由表 url_map中,在蓝图上注册的路由URL自动被加上了这个前缀,这个可以保证在多个蓝图中使用相同的URL规则而不会最终引起冲突,只要在注册蓝图时将不同的蓝图挂接到不同的自路径即可
  3. url_for在使用时,如果要生成一个蓝图里面的视图对应的路由地址,则需要声明当前蓝图名称+视图名称
url_for('users.home') # /users/home

注册蓝图中的静态文件的相关路由

和应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在 创建时指定 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')

现在就可以使用/admin/static_admin/ 访问static_admin目录下的静态文件了 定制静态目录URL规则 :可以在创建蓝图对象时使用 static_url_path 来改变静态目录的路由。示例将为 static_admin 文件夹的路由设置为 /lib

admin = Blueprint("admin",__name__,static_folder='static_admin',static_url_path='/lib')
app.register_blueprint(admin,url_prefix='/admin')

设置蓝图中模版的目录

蓝图对象默认的模板目录为系统的模版目录,可以在创建蓝图对象时使用 template_folder 关键字参数设置模板目录。创建蓝图中的模板目录template_users :

admin = Blueprint('admin',__name__,template_folder='templates_users')
文章作者: 慕容峻才
文章链接: https://www.acaiblog.top/flask教程/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿才的博客
微信打赏
支付宝打赏