【Flask基础②】 | 路由、响应与异常处理
0 序言
本文是Flask基础的第二篇,旨在对第一篇未介绍完的内容跟进行补充。
本文将围绕 Flask 路由及请求响应此类基础能力、模板、表单和数据库之类的进阶特性展开,结合代码示例与细节解释,帮助零基础学习者入门,或供有经验者复习查漏。
1. Flask 基础入门
本小节这里先对第一篇文章比较重要的内容进行回顾,第二小节开始引入新知识点会更好理解。
1.1 环境搭建与第一个应用
1.1.1 安装 Flask
通过 pip
安装 Flask:
pip install flask
提示:确保本地已配置 Python 环境(建议 3.6+)。
1.1.2 Hello World 示例
from flask import Flask # 导入 Flask 类
app = Flask(__name__) # 初始化应用,__name__ 标识当前模块 @app.route('/') # 定义路由:访问根路径(http://127.0.0.1:5000/)时触发
def hello(): return "Hello, Flask!" # 返回响应内容 if __name__ == '__main__': app.run() # 启动开发服务器(默认端口 5000)
- 逐行解释:
Flask(__name__)
:Flask 根据模块名定位静态文件、模板的目录。@app.route('/')
:装饰器将 URL 路径映射到视图函数hello
。app.run()
:启动调试服务器,开发阶段可用app.run(debug=True)
开启自动重载。
1.2 路由与请求方法
1.2.1 基本路由定义
路由通过 @app.route('/url')
定义,支持动态路由(如 /user/<username>
):
@app.route('/user/<username>')
def user_profile(username): return f"Hello, {username}!" # 接收 URL 中的变量 username
1.2.2 指定请求方法
通过 methods
参数限定请求类型(如 GET
/POST
):
from flask import request # 导入 request 对象 @app.route('/login', methods=['GET', 'POST'])
def login(): if request.method == 'POST': # 处理表单提交(POST 请求) return "Handle Login" else: # 显示登录页面(GET 请求) return "Show Login Form"
2. 响应处理与异常管理
在第一章中,我们对前文 Flask 路由的基本定义以及路由的相关操作进行了回顾。
但在实际开发中,客户端的需求往往更复杂,可能需要返回结构化的 JSON 数据(供前端解析),可能需要自定义响应头(如设置缓存策略),还可能遇到各种异常情况(如用户访问不存在的页面、权限不足等)。
因此,本章将深入学习响应的多样化处理,比如说返回 JSON 数据、手动构造响应和异常的规范化管理,如捕获错误、自定义错误页面等,让flask更符合实际业务场景。
2.1 返回 JSON 数据
2.1.1 便捷方式:jsonify
from flask import jsonify @app.route('/api/data')
def get_json(): data = {"name": "秦好", "age": 24} return jsonify(data) # 自动设置 Content-Type 为 application/json,支持中文
这段代码的核心作用是定义一个 Flask 接口,向前端返回 JSON 格式的数据。
下面依次进行分析:
1. 从 flask 库中导入 jsonify 工具
from flask import jsonify
jsonify
是 Flask 内置的一个工具函数,专门用于处理 JSON 格式的响应。
它的核心功能是:将 Python 字典转换为 JSON 字符串,并自动设置响应头(Content-Type: application/json
),让前端能正确识别这是 JSON 数据。
2. 定义路由:当用户访问 URL 为 /api/data 时,触发下面的 get_json 函数
@app.route('/api/data')
@app.route('/api/data')
是 Flask 的路由装饰器,作用是将 URL 路径 /api/data
与下面的 get_json
函数绑定。
当用户通过浏览器访问 http://127.0.0.1:5000/api/data
时,Flask 会自动执行 get_json
函数,并将函数的返回值作为响应返回给用户。
3. 定义视图函数:处理请求并返回数据
def get_json(): # 定义一个 Python 字典(存储要返回的数据) data = {"name": "秦好", "age": 24} # 用 jsonify 处理字典,返回 JSON 响应 return jsonify(data)
get_json
是视图函数,负责处理请求并生成响应。
- 第一步:定义一个 Python 字典
data
,里面存储了要返回的数据(name
为秦好
,age
为 24)。 - 第二步:
jsonify(data)
会对字典做两件事:- 将 Python 字典
data
转换为 JSON 字符串(JSON 格式要求键值对用双引号,且支持中文),转换后的数据类似:{"name": "张三", "age": 20}
。 - 自动设置响应头
Content-Type: application/json
(告诉前端这是 JSON 格式的数据,你可以用 JSON 解析方法处理
)。
- 将 Python 字典
我们运行下程序看看实际效果:
当你启动 Flask 应用(app.run()
)后,用浏览器访问 http://127.0.0.1:5000/api/data
,会看到页面上显示:
{ "age": 20, "name": "张三"
}
这就是 jsonify
处理后的结果——我们可以直接用 JSON.parse()
解析这段数据,拿到 name
和 age
的值。
但是从以上结果你会发现,本来应该是中文名称,如今确变成了一串编码,这个原因是啥呢?
Flask 内部使用 jsonify 处理响应时,默认开启了 JSON_AS_ASCII = True,这会强制将所有非 ASCII 字符(如中文)转义为 Unicode 编码,这也是为了避免乱码。
那要解决这个问题也很简单,关闭json转义即可。
from flask import Flask, jsonify app = Flask(__name__)
# 关键配置:禁止 JSON 转义非 ASCII 字符(如中文)
app.config['JSON_AS_ASCII'] = False @app.route('/api/data')
def get_json(): data = {"name": "秦好", "age": 24} return jsonify(data) # 现在中文会正常显示 if __name__ == '__main__': app.run()
后续需要返回 JSON 数据,就可以使用以上程序作为的标准写法,
这段程序的逻辑也同样适合用于开发 API 接口,是前后端分离场景的基础用法。
2.1.2 手动构造响应(细粒度控制)
当然,我们也可以选择手动自己去构造响应,程序如下:
from flask import make_response, json @app.route('/custom-json')
def custom_json(): data = {"name": "张三"} json_str = json.dumps(data, ensure_ascii=False) # 手动序列化,确保中文显示 response = make_response(json_str) response.mimetype = 'application/json' # 显式设置响应类型 return response
这段程序不依赖 Flask 内置的 jsonify 快捷函数,而是通过 Python 标准库和 Flask 的基础工具一步步手动拼接响应,适合一些需要精细化的操作场景。
以下分步讲解:
from flask import make_response, json # 导入基础工具
注意:这里导入的库变更了,并不是jsonify
这个库!!!
接下来,由于是手动配置,因此要五步走:
[1]定义数据
[2]手动序列化
[3]手动构造响应对象
[4]手动设置响应类型
[5]返回响应对象
@app.route('/custom-json')
def custom_json(): # 1. 定义数据(和jsonify场景一样) data = {"name": "张三"} # 2. 手动序列化:用 Python 标准库 json.dumps json_str = json.dumps(data, ensure_ascii=False) # 这里可以自由控制序列化参数: # 比如 indent=4(格式化 JSON,带缩进)、sort_keys=True(按键排序)等 # 例:json.dumps(data, ensure_ascii=False, indent=4) # 3. 手动构造响应对象:用 make_response 包装字符串 response = make_response(json_str) # make_response 是 Flask 最基础的响应构造工具,支持: # - 字符串(如这里的 JSON 字符串) # - 模板渲染结果(如 render_template 的返回值) # - 文件对象(如 return make_response(open('file.pdf', 'rb')) ) # 4. 手动设置响应类型:告诉客户端“这是 JSON 数据” response.mimetype = 'application/json' # 还可以添加其他响应头,比如: # response.headers['Cache-Control'] = 'no-cache' (控制缓存) # response.headers['Access-Control-Allow-Origin'] = '*' (解决跨域) # 5. 返回响应对象 return response
运行结果如下:
2.2 异常处理
此外,如果说我们在网站过程中,难免会因为一些错误操作或是其他情况,我们就可以用abort这个函数来中断请求,告诉用户出现了异常。
2.2.1 用 abort
中断请求
from flask import abort @app.route('/secret')
def secret_page(): if not check_permission(): # 权限校验函数 abort(403) # 直接返回 403 禁止访问 return "Secret Content"
以上程序的核心作用是在权限不足时,主动中断请求并返回‘禁止访问’的错误响应,从而去处理权限校验。
它的核心逻辑是:
- 访问
/secret
时,先通过check_permission()
函数校验用户是否有权限(比如是否登录、是否是管理员等)。 - 如果 没有权限(
check_permission()
返回False
),就用abort(403)
直接中断请求,返回403 禁止访问
的错误。 - 如果 有权限(
check_permission()
返回True
),才返回正常内容Secret Content
。
下面逐行进行分析:
from flask import abort # 1. 导入 Flask 的 abort 函数
abort
是 Flask 内置的中断请求
工具,作用是:立即终止当前视图函数的执行,并向客户端返回指定的 HTTP 错误状态码。
@app.route('/secret') # 2. 定义路由:访问 /secret 时触发下面的函数
def secret_page(): # 3. 视图函数:处理 /secret 的请求
这个解释很多次了,就不再赘述了。
if not check_permission(): # 4. 权限校验
check_permission()
是一个权限校验函数
,比如:
def check_permission(): # 示例:检查用户是否登录(实际可能从 session 或 token 中判断) return False # 这里返回 False 模拟无权限
abort(403) # 5. 无权限时,中断请求并返回 403 错误
一旦执行,会立即终止secret_page
函数,后面的 return "Secret Content"
不会被执行并 向客户端返回 HTTP 403 状态码。
return "Secret Content" # 6. 有权限时,返回正常内容
注意:这个权限校验函数需要自己写!!!
比如下面程序示例,给出了相关的完整程序:
from flask import Flask, abort, session app = Flask(__name__)
# 必须设置密钥,用于加密 session
app.secret_key = 'your_secret_key_here' # 模拟数据库中的用户数据
USER_DATA = { # 键:用户名;值:用户信息 'admin': {'password': 'admin123', 'role': 'admin'}, # 管理员角色 'user1': {'password': 'user123', 'role': 'user'} # 普通用户角色
} def check_permission(required_role='user'): """ 权限校验函数 :param required_role: 访问资源所需的角色(默认'user',可选'admin') :return: True(有权限)/ False(无权限) """ # 1. 检查用户是否已登录(session 中是否有用户名) # session 是 Flask 用于存储用户会话信息的工具,登录后会记录用户名 if 'username' not in session: return False # 未登录 → 无权限 # 2. 从 session 中获取当前登录用户名 current_username = session['username'] # 3. 从模拟数据库中获取用户信息(实际项目中查数据库) current_user = USER_DATA.get(current_username) if not current_user: return False # 用户不存在 → 无权限 # 4. 检查用户角色是否满足要求 # 例如:管理员(admin)可以访问所有资源,普通用户(user)只能访问普通资源 if current_user['role'] == 'admin': return True # 管理员拥有所有权限 elif current_user['role'] == 'user' and required_role == 'user': return True # 普通用户只能访问普通资源 else: return False # 角色不匹配 → 无权限 # 示例 1:普通用户可访问的资源(需登录即可)
@app.route('/user-page')
def user_page(): if not check_permission(required_role='user'): abort(403) # 无权限 → 返回 403 return "普通用户页面(已登录用户可见)" # 示例 2:仅管理员可访问的资源
@app.route('/admin-page')
def admin_page(): if not check_permission(required_role='admin'): abort(403) # 非管理员 → 返回 403 return "管理员页面(仅管理员可见)" # 登录接口(用于模拟用户登录,设置 session)
@app.route('/login/<username>/<password>')
def login(username, password): user = USER_DATA.get(username) if user and user['password'] == password: # 登录成功:将用户名存入 session(表示用户已登录) session['username'] = username return f"登录成功!当前用户:{username},角色:{user['role']}" return "用户名或密码错误" # 退出登录接口(清除 session)
@app.route('/logout')
def logout(): session.pop('username', None) # 移除 session 中的用户名 return "已退出登录" if __name__ == '__main__': app.run(debug=True)
接着我用图片注解的方式来对程序进行分析:
运行下程序,我们可以看到在没有登录之前,无论打开用户还是管理员,都是进不去的。
都是显示没有权限,只有我们登录接口那里登录后才能进入。
上面就是用户成功登录进去的画面。
那如果登录管理员,用户名正确但是密码错误,也同样设置了检查步骤,并返回错误信息。
最后,再补充一些常见 HTTP 状态码补充
abort
不仅能返回 403,还能返回其他常用状态码:
abort(404)
:页面未找到(用户访问了不存在的 URL)。abort(500)
:服务器内部错误(代码运行出错)。abort(401)
:未授权(需要登录但未登录)。
根据不同场景选择合适的状态码,能让客户端更清晰地理解错误原因(比如前端可以根据 401 自动跳转到登录页)。
2.2.2 自定义异常处理器
@app.errorhandler(404) # 捕获 404 错误
def page_not_found(error): return render_template('404.html'), 404 # 返回自定义模板和状态码
扩展:可处理其他状态码(如 500 服务器错误),模板中可通过
{{ error }}
显示异常信息。
3. 模板引擎与过滤器
在前面的章节中,我们学习了后端如何向客户端返回数据,它可以是简单的字符串、结构化的 JSON,也可以手动构造响应头。
但在另一种常见的开发场景中,后端不仅需要返回数据,还需要直接生成包含动态数据的 HTML 页面,比如说用户登录后显示欢迎您
的主页、商品列表页动态展示库存信息等场景。
如果直接用字符串拼接 HTML,会导致代码混乱,因为HTML 和 Python 逻辑混合且由于修改页面样式需要改 Python 代码,所以难以维护。
为了解决这个问题,我们可以利用Flask 集成的Jinja2 模板引擎将 HTML 代码单独存放,并通过特殊语法在 HTML 中嵌入后端数据,实现HTML 结构与动态数据的分离。
本章将详细介绍 Jinja2 模板引擎的基本使用(如渲染变量、表达式运算)、过滤器(格式化数据的工具),以及如何通过模板高效生成动态 HTML 页面。
3.1 模板基本使用
3.1.1 模板文件结构(以 index.html
为例)
<!DOCTYPE html>
<html>
<head><title>Flask Template</title></head>
<body> <h1>Hello {{ name }}</h1> <!-- 渲染变量 name --> <p>列表第一个元素:{{ mylist[0] }}</p> <!-- 列表索引 --> <p>计算:{{ mylist[0] + mylist[1] }}</p> <!-- 表达式运算 -->
</body>
</html>
- 语法:Jinja2 模板引擎通过
{{ 变量/表达式 }}
渲染后端数据。
3.1.2 传递数据到模板
from flask import render_template @app.route('/')
def index(): data = { "name": "Flask", "mylist": [10, 20] } return render_template('index.html', **data) # **data 解包字典为关键字参数
以上程序的核心作用是 “把后端的数据传递给前端模板,让HTML页面动态显示内容”,下面挑一些重点部分进行讲解:
1. 导入模板渲染工具
from flask import render_template
render_template
是 Flask 内置的核心函数,作用是:
① 找到项目中 templates
文件夹里的 HTML 模板文件(如 index.html
);
② 把后端的数据“注入”到模板中;
③ 生成最终的 HTML 页面并返回给浏览器。
2.渲染模板并传递数据
return render_template('index.html', **data)
这是最关键的一行,拆成两部分理解:
① 'index.html'
:告诉 render_template
要使用 templates
文件夹下的 index.html
模板文件(必须先在项目中创建 templates
文件夹和 index.html
文件)。
② ** data
:这是 Python 的字典解包
语法,作用是把 data
字典里的键值对拆成一个个关键字参数。
- 等价于:render_template('index.html', name="Flask", mylist=[10, 20])
- 好处是:当 data
里有很多键值对时(比如10个变量),不用手动写10个参数,简化代码。
比如我目前的index.html文件内容如下:
当访问 http://127.0.0.1:5000/
时,会发生这几步:
- 浏览器请求根路径,Flask 触发
index
函数。 index
函数准备data
字典(name="Flask"
,mylist=[10,20]
)。render_template
找到index.html
,并把name
和mylist
传递给模板。- 模板中的
{{ name }}
被替换成Flask
,{{ mylist[0] }}
被替换成10
,{{ mylist[0] + mylist[1] }}
被替换成30
。 - 最终生成的 HTML 页面返回给浏览器,显示效果:
如果不用模板和 render_template
,要显示动态内容可能需要这样写:
@app.route('/')
def index(): name = "Flask" mylist = [10, 20] # 用字符串拼接HTML,既繁琐又容易出错 html = f"<h1>Hello {name}</h1><p>列表第一个元素:{mylist[0]}</p><p>计算:{mylist[0]+mylist[1]}</p>" return html
这种方式的问题是:
HTML 代码和 Python 代码混在一起,修改页面样式(比如改 <h1>
为 <h2>
)需要改 Python 代码,非常麻烦。
而且当页面复杂时(比如有表格、表单),字符串拼接会变得极其混乱,难以维护,可读性也比较差。
而用 render_template
+ 模板文件的方式:
- HTML 代码单独放在
templates
文件夹里,改样式只需改 HTML,不用碰 Python 代码。 - 数据通过
{{ 变量 }}
嵌入,清晰区分“页面结构”和“动态数据”,会规范很多。
3.2 过滤器
接着来看一下过滤器,先理解:什么是过滤器?
过滤器是 Jinja2 模板引擎的数据加工工具
,语法是 {{ 变量|过滤器 }}
。作用是对变量进行处理后再显示。
3.2.1 内置过滤器(示例)
- 默认值:
{{ value|default('默认值') }}
(value 不存在时显示默认值)。 - 长度:
{{ mylist|length }}
(获取列表长度)。 - 日期格式化:需先将
strftime
导入模板环境,再使用{{ date|strftime('%Y-%m-%d') }}
。
3.2.2 自定义过滤器
# 1. 定义过滤器函数
def list_step(li): return li[::2] # 取列表步长为 2 的元素 # 2. 注册过滤器(两种方式)
# 方式一:add_template_filter
app.add_template_filter(list_step, 'list_step') # 方式二:装饰器
@app.template_filter('list_step')
def list_step_decorator(li): return li[::2]
这段程序,简单说,就是给模板引擎(Jinja2)添加一个数据处理工具
,功能是接收一个列表,返回列表中步长为 2 的元素。
下面对程序逐一进行分析:
1. 定义过滤器函数
def list_step(li): return li[::2] # 取列表步长为 2 的元素
这是一个普通的 Python 函数,作用是处理数据。
2. 注册过滤器(让模板能识别)
定义好函数后,必须注册
给 Flask 模板引擎,否则模板不认识这个过滤器。
有两种注册方式,效果完全一样:
方式一:用 app.add_template_filter
注册
app.add_template_filter(list_step, 'list_step')
第一个参数 list_step
:要注册的函数名(就是上面定义的 list_step
函数)。
第二个参数 'list_step'
:给过滤器起的模板中用的名字
,模板中通过这个名字调用。
方式二:用装饰器 @app.template_filter
注册
@app.template_filter('list_step') # 装饰器参数是“模板中用的名字”
def list_step_decorator(li): return li[::2] # 功能和上面的 list_step 函数完全一样
这是更简洁的写法,用装饰器直接绑定函数和过滤器名称,不用再调用 add_template_filter
。
这里的函数名 list_step_decorator
可以随便起,比如叫 filter_mylist
也可以,模板中只认装饰器里的 'list_step'
。
注册后,就能在模板中用 {{ 列表|list_step }}
调用这个过滤器了。
完整示例如下:
- 后端程序(app.py):
from flask import Flask, render_template app = Flask(__name__) # 定义过滤器函数
def list_step(li): return li[::2] # 注册过滤器(两种方式选一种即可)
app.add_template_filter(list_step, 'list_step')
# 或用装饰器:
# @app.template_filter('list_step')
# def list_step_decorator(li):
# return li[::2] @app.route('/')
def index(): mylist = [1, 2, 3, 4, 5, 6] # 准备一个列表 return render_template('index.html', mylist=mylist) # 传递给模板 if __name__ == '__main__': app.run(debug=True)
- 模板文件(templates/index.html):
<!DOCTYPE html>
<html>
<body> <p>原始列表:{{ mylist }}</p> <!-- 显示 [1,2,3,4,5,6] --> <p>步长为 2 的列表:{{ mylist|list_step }}</p> <!-- 调用自定义过滤器 -->
</body>
</html>
运行结果如下:
实际开发中需求多样,而内置过滤器只能处理常见场景,自定义过滤器让你能按自己的需求处理数据,让模板中的数据展示更灵活,所以学会自定义过滤器很重要。
4. 表单处理(Flask-WTF)
在前面的章节中,我们学习了如何通过模板展示动态数据,也掌握了用过滤器处理数据的技巧。但 Web 应用中,用户不仅需要看
数据,还需要提交
数据 —— 比如注册账号时填写用户名和密码、登录时输入账号信息、发表评论时提交内容等。这些用户输入并提交数据
的场景,都需要通过表单来实现。
原生的 HTML 表单(标签)虽然能接收用户输入,但存在两个核心问题:
数据验证繁琐:需要手动判断用户输入是否符合要求,容易遗漏边界情况。
安全风险:缺乏对 CSRF攻击的防护,恶意网站可能伪造用户请求提交数据。
为了解决这些问题,我们可以利用Flask 生态提供的 Flask-WTF 扩展,因为它不仅能简化表单的定义和渲染,还能自动处理数据验证、CSRF 保护等核心需求。
本章将详细介绍如何用 Flask-WTF 处理表单,从安装依赖到定义表单类、渲染表单、验证数据,覆盖表单处理的全流程。
4.1 表单模型类定义
4.1.1 安装依赖
pip install flask-wtf # 安装 Flask-WTF(集成 WTForms)
4.1.2 定义表单类
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, EqualTo class RegisterForm(FlaskForm): user_name = StringField( label='用户名', # 前端显示的标签 validators=[DataRequired('用户名不能为空')] # 非空验证 ) password = PasswordField( label='密码', validators=[DataRequired('密码不能为空')] ) password2 = PasswordField( label='确认密码', validators=[ DataRequired('确认密码不能为空'), EqualTo('password', '两次密码不一致') # 与 password 字段比较 ] ) submit = SubmitField(label='提交') # 提交按钮
4.2 表单的使用与验证
4.2.1 渲染表单到模板(register.html
)
<form method="POST"> {{ form.csrf_token }} <!-- 必须添加,防止 CSRF 攻击 --> <div> {{ form.user_name.label }}: {{ form.user_name() }} <!-- 渲染标签和输入框 --> </div> <div> {{ form.password.label }}: {{ form.password() }} </div> <div> {{ form.password2.label }}: {{ form.password2() }} </div> <div>{{ form.submit() }}</div>
</form>
4.2.2 后端处理表单请求
@app.route('/register', methods=['GET', 'POST'])
def register(): form = RegisterForm() # 创建表单对象 if form.validate_on_submit(): # 当表单是 POST 提交且验证通过时返回 True # 获取字段值 username = form.user_name.data password = form.password.data # 处理注册逻辑(如存入数据库) return "注册成功" # GET 请求或验证失败,重新渲染表单(错误信息自动绑定到 form 对象) return render_template('register.html', form=form)
运行下程序,结果如下:
5. 数据库配置(Flask-SQLAlchemy)
在前面的章节中,我们学习了如何用表单接收用户数据,但这些数据需要持久化存储,比如用户注册后,下次登录时系统能识别该用户—— 这就需要数据库。
原生的数据库操作(如用 sqlite3 模块写 SQL 语句)存在两个问题:
语法繁琐:需要手动写 SQL 语句(如 INSERT、SELECT),不同数据库(MySQL、SQLite)的语法还有差异。
代码耦合:SQL 语句与 Python 代码混合,维护困难。
Flask-SQLAlchemy 是 Flask 的一个扩展,它封装了 SQLAlchemy(一个强大的 ORM 工具),能让我们用 Python 类 定义数据库表结构,用 对象方法 替代 SQL 语句(如 user.save() 替代 INSERT 语句),极大简化数据库操作。
再加上前面也学过了SQL基础知识,所以下文就使用Flask-SQLAlchemy这个拓展。
5.1 安装与初始化
5.1.1 安装依赖
pip install flask-sqlalchemy # 安装 Flask-SQLAlchemy
pip install pymysql #之前用过MySQL的用这个更方便
5.1.2 配置数据库连接
from flask import Flask
from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) class Config: # MySQL 示例:数据库类型://用户名:密码@主机:端口/数据库名 SQLALCHEMY_DATABASE_URI = 'mysql://root:LKR1580708123@127.0.0.1:3306/flaskdb' SQLALCHEMY_TRACK_MODIFICATIONS = True app.config.from_object(Config) # 加载配置
db = SQLAlchemy(app) # 初始化 SQLAlchemy,关联 Flask 应用
5.1.3 定义模型(补充基础)
class User(db.Model): id = db.Column(db.Integer, primary_key=True) # 主键 username = db.Column(db.String(50), unique=True) # 唯一用户名 password = db.Column(db.String(50)) # 创建表(需确保数据库存在,表自动创建)
with app.app_context(): db.create_all()
合在一起的总程序如下:
# 1. 导入依赖
import pymysql
pymysql.install_as_MySQLdb() # 让 PyMySQL 兼容 MySQLdb 接口
from flask import Flask
from flask_sqlalchemy import SQLAlchemy # 2. 初始化 app 并配置数据库(5.1.2 小结内容)
app = Flask(__name__) class Config: # 注意:MySQL 连接需指定 pymysql 驱动(否则会报 MySQLdb 错误) ‘****’是你创建的密码SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:******@127.0.0.1:3306/flaskdb' SQLALCHEMY_TRACK_MODIFICATIONS = False # 建议关闭,节省内存 app.config.from_object(Config)
db = SQLAlchemy(app) # 初始化 SQLAlchemy,关联 app # 3. 定义模型 + 创建表(5.1.3 小结内容)
class User(db.Model): __tablename__ = 'users' # 显式指定表名(可选,不指定则默认用类名小写) id = db.Column(db.Integer, primary_key=True) # 主键(自增整数) username = db.Column(db.String(50), unique=True, nullable=False) # 唯一且非空的用户名 password = db.Column(db.String(50), nullable=False) # 密码(实际应加密存储) # 4. 启动程序时创建表
if __name__ == '__main__': with app.app_context(): db.create_all() # 自动创建所有模型对应的表(如 users 表) app.run(debug=True) # 启动 Flask 服务
5.2 模型类定义(关联表设计)
5.2.1 基础模型结构
Flask-SQLAlchemy 通过继承 db.Model
定义数据库表,类属性对应字段,支持多表关联(如角色与用户的一对多关系)。
示例:角色(Role)与用户(User)的关联模型
class Role(db.Model): __tablename__ = 'role' # 显式指定表名(默认类名小写,如 Role → role) id = db.Column(db.Integer, primary_key=True) # 主键,自动自增 name = db.Column(db.String(32), unique=True) # 角色名,唯一 class User(db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) # 主键,自动自增 name = db.Column(db.String(128), unique=True) # 用户名,唯一 password = db.Column(db.String(128)) # 外键关联:关联 Role 表的 id 字段(格式:表名.字段名) role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
- 关键细节:
__tablename__
:自定义表名,避免类名自动复数化(如User
类默认表名为users
,显式定义则为user
)。db.ForeignKey('role.id')
:role.id
指 Role 表的id
字段(字符串格式,表名.字段名),建立外键约束。
5.3 数据表创建(两种核心方式)
5.3.1 代码内创建
在 Flask 应用中,通过 db.create_all()
创建模型对应的表(需确保应用上下文已激活):
from flask import Flask
from flask_sqlalchemy import SQLAlchemy app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:password@localhost/flaskdb'
db = SQLAlchemy(app) # 定义 Role、User 模型(略)... # 方式 1:直接在代码中执行(需包裹应用上下文)
with app.app_context(): # 激活上下文,让 db 识别 app 配置 db.create_all() # 创建所有模型对应的表(Role、User)
5.3.2 终端交互创建
步骤 1:进入 Flask 专属 Shell(自动加载上下文):
# 确保环境变量 FLASK_APP 已设置(如 FLASK_APP=app.py)
flask shell
步骤 2:导入依赖并创建表:
# 假设 app、db、Role、User 已在 app.py 中定义
from app import app, db, Role, User with app.app_context(): db.create_all() # 创建表
- 扩展操作:
- 删除所有表:
db.drop_all()
(谨慎!会清空数据,测试环境可用)。 - 仅创建部分表:
db.create_all(Role, User)
(需手动传入模型类,不常用)。
- 删除所有表:
5.4 数据操作:增、查、改、删
5.4.1 新增数据
# 1. 创建角色对象
role_admin = Role(name='admin') # 角色:管理员
role_editor = Role(name='editor') # 角色:编辑 # 2. 将对象加入事务队列
db.session.add(role_admin)
db.session.add(role_editor)
# 或批量添加:db.session.add_all([role_admin, role_editor]) # 3. 提交事务(真正写入数据库)
db.session.commit() # 4. 创建用户并关联角色(需先获取角色 ID)
user_zhangsan = User(name='zhangsan', password='123', role_id=role_admin.id)
user_lisi = User(name='lisi', password='321', role_id=role_admin.id)
user_wangwu = User(name='wangwu', password='321', role_id=role_editor.id) db.session.add_all([user_zhangsan, user_lisi, user_wangwu])
db.session.commit()
- 核心逻辑:
db.session
是事务管理器,add()
/add_all()
仅将对象加入队列,commit()
才执行 SQL 语句。
5.4.2 查询数据
Flask-SQLAlchemy 提供 Model.query
对象,支持链式调用查询,常用方法:
方法 | 作用 | 示例 |
---|---|---|
Model.query.all() | 查询表中所有记录 | Role.query.all() → 获取所有 Role 对象 |
Model.query.first() | 查询第一条记录(无数据返回 None ) | User.query.first() → 第一个 User 对象 |
filter_by() | 简单条件查询(等价于 SQL 的 WHERE ) | User.query.filter_by(name='zhangsan').first() |
filter() | 复杂条件查询(支持 SQL 表达式) | User.query.filter(User.role_id == 1).all() |
示例:查询“管理员”角色的所有用户
# 方法 1:先查角色,再关联用户(推荐,利用 ORM 反向关联)
admin_role = Role.query.filter_by(name='admin').first()
admin_users = admin_role.users # 需定义反向关联(见扩展) # 方法 2:直接通过外键查询(手动关联)
admin_users = User.query.filter_by(role_id=admin_role.id).all()
- 扩展:反向关联(优化多表查询)
在Role
类中添加db.relationship
,让角色直接访问关联的用户:
class Role(db.Model): # ... 原有字段 ... # 反向关联:Role 实例可通过 `users` 属性访问所有关联的 User 对象 users = db.relationship('User', backref='role') # `backref` 给 User 加 `role` 属性
此时,查询更便捷:
admin_role = Role.query.filter_by(name='admin').first()
admin_users = admin_role.users # 直接获取该角色下的所有用户
5.4.3 修改数据(两种方式)
方式 1:修改对象属性(单条更新)
# 1. 查询用户
user = User.query.filter_by(name='zhangsan').first()
# 2. 修改属性
user.password = 'new_password'
# 3. 提交事务
db.session.commit()
方式 2:批量更新(高效)
# 更新所有“管理员”角色用户的密码
User.query.filter(User.role_id == 1).update({'password': 'updated'})
db.session.commit()
5.4.4 删除数据
# 1. 查询要删除的对象
user = User.query.filter_by(name='lisi').first() # 2. 加入删除队列,提交事务
db.session.delete(user)
db.session.commit()
最后,对本章的小结,数据库操作核心,牢记相关的几个步骤,如下:
- 模型定义:通过
db.Model
子类描述表结构,用db.Column
定义字段,db.ForeignKey
建立外键关联。 - 表创建:
- 代码内:
with app.app_context(): db.create_all()
(需上下文)。 - 终端:
flask shell
进入交互环境,导入依赖后执行创建。
- 代码内:
- 数据操作:
- 增:
add()
/add_all()
+commit()
; - 查:
query
结合all()
/first()
/filter()
等; - 改:修改属性或
update()
+commit()
; - 删:
delete()
+commit()
。
- 增:
6.小结
本文作为 Flask 基础第二篇,先回顾了环境搭建、Hello World 示例等基础内容,接着详细讲解了路由定义(含动态路由)、请求方法指定,还深入介绍了响应处理(如用 jsonify 或手动构造返回 JSON 数据,解决中文显示问题)及异常管理相关知识,通过代码示例辅助讲解,助力学习者掌握 Flask 基础与进阶特性。