当前位置: 首页 > news >正文

【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) 会对字典做两件事:
    1. 将 Python 字典 data 转换为 JSON 字符串(JSON 格式要求键值对用双引号,且支持中文),转换后的数据类似:{"name": "张三", "age": 20}
    2. 自动设置响应头 Content-Type: application/json(告诉前端这是 JSON 格式的数据,你可以用 JSON 解析方法处理)。

我们运行下程序看看实际效果:

当你启动 Flask 应用(app.run())后,用浏览器访问 http://127.0.0.1:5000/api/data,会看到页面上显示:

{  "age": 20,  "name": "张三"  
}  

这就是 jsonify 处理后的结果——我们可以直接用 JSON.parse() 解析这段数据,拿到 nameage 的值。

在这里插入图片描述

但是从以上结果你会发现,本来应该是中文名称,如今确变成了一串编码,这个原因是啥呢?

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"  

以上程序的核心作用是在权限不足时,主动中断请求并返回‘禁止访问’的错误响应,从而去处理权限校验。

它的核心逻辑是:

  1. 访问 /secret 时,先通过 check_permission() 函数校验用户是否有权限(比如是否登录、是否是管理员等)。
  2. 如果 没有权限check_permission() 返回 False),就用 abort(403) 直接中断请求,返回403 禁止访问的错误。
  3. 如果 有权限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/ 时,会发生这几步:

  1. 浏览器请求根路径,Flask 触发 index 函数。
  2. index 函数准备 data 字典(name="Flask"mylist=[10,20])。
  3. render_template 找到 index.html,并把 namemylist 传递给模板。
  4. 模板中的 {{ name }} 被替换成 Flask{{ mylist[0] }} 被替换成 10{{ mylist[0] + mylist[1] }} 被替换成 30
  5. 最终生成的 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 }} 调用这个过滤器了。

完整示例如下:

  1. 后端程序(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)  
  1. 模板文件(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.idRole 表的 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()查询第一条记录(无数据返回 NoneUser.query.first() → 第一个 User 对象
filter_by()简单条件查询(等价于 SQL 的 WHEREUser.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()  

最后,对本章的小结,数据库操作核心,牢记相关的几个步骤,如下:

  1. 模型定义:通过 db.Model 子类描述表结构,用 db.Column 定义字段,db.ForeignKey 建立外键关联。
  2. 表创建
    • 代码内:with app.app_context(): db.create_all()(需上下文)。
    • 终端:flask shell 进入交互环境,导入依赖后执行创建。
  3. 数据操作
    • add()/add_all() + commit()
    • query 结合 all()/first()/filter() 等;
    • :修改属性或 update() + commit()
    • delete() + commit()

6.小结

本文作为 Flask 基础第二篇,先回顾了环境搭建、Hello World 示例等基础内容,接着详细讲解了路由定义(含动态路由)、请求方法指定,还深入介绍了响应处理(如用 jsonify 或手动构造返回 JSON 数据,解决中文显示问题)及异常管理相关知识,通过代码示例辅助讲解,助力学习者掌握 Flask 基础与进阶特性。

http://www.lryc.cn/news/606039.html

相关文章:

  • 在嵌入式系统或 STM32 平台中常见的外设芯片和接口
  • 《通信原理》学习笔记——第六章
  • 乱删文件,电脑不能开机,怎么办
  • 深入解析 Spring AI 系列:剖析OpenAI接口接入组件
  • 常见的中间件漏洞(tomcat,weblogic,jboss,apache)
  • 微信小程序中进行参数传递的方法
  • 5 种智能策略,从 iQOO 到 iQOO 转移照片
  • Apache RocketMQ 中 Topic 的概念、属性、行为约束和最佳实践
  • 【机器人+相机通讯】宇树科技相机通信
  • ChatGPT的下一站:从“答案引擎”到“思维教练”
  • 基于单片机胎压检测/锅炉蒸汽压力/气压检测系统
  • 从姑苏区人工智能大模型基础设施招标|学习服务器、AI处理器、GPU
  • 深度学习(鱼书)day07--误差反向传播(前四节)
  • 项目推进难的原因有哪些?问题及应对
  • TOML介绍
  • 14day-ai入门-人工智能基础学习-OpenCV-图像预处理4
  • 我在 Arch Linux Plasma 6 Wayland 下驯服 Chromium 输入法的完整记录
  • ACOSRAR改进连续蚁群算法用于优化复杂环境下无人机路径规划,Matlab代码实现
  • 智慧工地系统:建筑工程管理的智能化革新与实践
  • 淘宝 API HTTP/2 多路复用与连接优化实践:提升商品数据采集吞吐量
  • Vue3 + Electron 技术栈下 MAC 地址获取的方法、准确性优化与应对策略
  • Electron 作品【AI聊天】桌面应用 —— 系列教程(含开源地址)
  • 发票闪印 v3.9.17 免费电子PDF发票批量打印工具绿色版
  • 【未解决】STM32无刷电机驱动电路问题记录
  • Apache Camel 简介
  • 【Lua】题目小练6
  • JavaScript函数性能优化秘籍:基于V8引擎优化
  • 【STM32】HAL库中的实现(二):串口(USART)/看门狗(IWDG/WWDG)/定时器(TIM)
  • JavaScript 框架语法特性对比-中文版
  • 39.MySQL索引