flask_quick_guide
Flask - 快速指南
Section titled “Flask - 快速指南”Flask – 概述
Section titled “Flask – 概述”什么是 Web 框架?
Section titled “什么是 Web 框架?”Web 应用框架 (Web Application Framework),或简称 Web 框架,提供了一系列库和工具,帮助开发者更高效地构建 Web 应用。它处理常见的低层细节,如路由 HTTP 请求、管理会话、与数据库交互,从而让开发者可以专注于应用的特定逻辑。
什么是 Flask?
Section titled “什么是 Flask?”Flask 是一个使用 Python 编写的轻量级 Web 应用框架。它由 Armin Ronacher 创建,作为 Pallets 项目组(前身为 Pocco)的一部分。Flask 以其简洁性和灵活性而闻名。它被认为是一个“微框架”,因为它提供了 Web 开发所需的核心组件(如请求处理、路由和模板),而将数据库交互或表单验证等其他方面留给了称为扩展 (extensions) 的外部库。Flask 建立在两个核心依赖之上:Werkzeug WSGI 工具包和 Jinja2 模板引擎,它们也都来自 Pallets 项目组。
WSGI (Web Server Gateway Interface)
Section titled “WSGI (Web Server Gateway Interface)”WSGI 是 Python Web 应用和 Web 服务器之间的标准接口。它定义了 Web 服务器如何与 Python 应用通信,确保使用不同框架编写的应用可以在不同的 WSGI 兼容服务器(如 Gunicorn 或 uWSGI)上运行。
Werkzeug
Section titled “Werkzeug”Werkzeug 是一个全面的 WSGI 实用工具库。它提供了必要的组件,如请求和响应对象、URL 路由、用于开发的交互式调试器,以及各种辅助函数。Flask 使用 Werkzeug 作为其处理 HTTP 请求和响应的基础。
Jinja2
Section titled “Jinja2”Jinja2 是一个现代且强大的 Python 模板引擎。Web 模板允许开发者通过在静态 HTML 标记中嵌入变量和控制结构(如循环和条件语句)来创建动态 HTML 页面。Jinja2 通过将占位符替换为 Flask 应用提供的数据来渲染这些模板。
作为一个微框架,Flask 旨在使其核心保持小巧且易于扩展。它不包含数据库抽象层或内置表单验证等组件。相反,Flask 的生态系统依赖于扩展来提供这些功能。这种方法让开发者可以自由选择最适合其项目需求的工具。流行的 Flask 扩展涵盖 ORM (对象关系映射)、表单处理、身份验证等领域。
Flask – 环境设置
Section titled “Flask – 环境设置”Flask 要求 Python 3.7 或更新版本。虽然旧版 Flask 支持 Python 2,但 Python 2 已于 2020 年停止支持,不再受支持。强烈建议使用最新的稳定版 Python 3,以获得安全性、性能和访问现代语言特性。
设置虚拟环境 (Virtual Environment)
Section titled “设置虚拟环境 (Virtual Environment)”强烈建议为任何 Python 项目使用虚拟环境。虚拟环境将项目依赖项与系统范围的 Python 安装和其他项目隔离开来,防止版本冲突。Python 3 包含内置的 venv 模块用于创建虚拟环境。
按照以下步骤创建并激活虚拟环境:
# 1. Create a project directory# 1. 创建项目目录mkdir my_flask_appcd my_flask_app
# 2. Create a virtual environment named 'venv'# 2. 创建名为 'venv' 的虚拟环境# On Windows:# 在 Windows 上:# python -m venv venv# On macOS/Linux:# 在 macOS/Linux 上:python3 -m venv venv
# 3. Activate the virtual environment# 3. 激活虚拟环境# On Windows (cmd.exe):# 在 Windows (cmd.exe) 上:# venv\Scripts\activate.bat# On Windows (PowerShell):# 在 Windows (PowerShell) 上:# venv\Scripts\Activate.ps1# On macOS/Linux (bash/zsh):# 在 macOS/Linux (bash/zsh) 上:source venv/bin/activate
# Your shell prompt should now indicate the active environment, e.g., (venv) $# 你的 Shell 提示符应该显示已激活的环境,例如 (venv) $虚拟环境激活后,你就可以安装特定于你项目的包了。
安装 Flask
Section titled “安装 Flask”在激活虚拟环境后,使用 pip 安装 Flask:
pip install Flask此命令将下载并安装 Flask 及其核心依赖项(Werkzeug、Jinja2、ItsDangerous 和 Click)。你现在就可以在这个隔离环境中开始构建你的 Flask 应用了。
Flask – 应用
Section titled “Flask – 应用”我们来创建一个最小的 Flask 应用来验证安装。创建一个名为 hello.py 的文件,并添加以下代码:
from flask import Flask
# Create an instance of the Flask class# 创建一个 Flask 类实例# __name__ tells Flask where to look for resources like templates and static files.# __name__ 告诉 Flask 在哪里寻找模板和静态文件等资源。app = Flask(__name__)
# Define a route using the route() decorator# 使用 route() 装饰器定义一个路由# This binds the URL '/' to the hello_world() function.# 这将 URL '/' 绑定到 hello_world() 函数。@app.route('/')def hello_world(): # The function returns the response text to be displayed in the browser. # 函数返回将在浏览器中显示的响应文本。 return 'Hello, World!'
# Check if the script is executed directly (not imported)# 检查脚本是否直接执行(而不是被导入)if __name__ == '__main__': # Run the application using the built-in development server # 使用内置的开发服务器运行应用 # debug=True enables the interactive debugger and reloader # debug=True 启用交互式调试器和自动重载器 app.run(debug=True)关键概念:
from flask import Flask: 导入主 Flask 类。app = Flask(__name__): 创建 Flask 应用实例。__name__是一个特殊的 Python 变量,它包含当前模块的名称。Flask 使用它来确定应用的根路径。@app.route('/'): 这是一个装饰器 (decorator),它将hello_world函数注册为处理根 URL (/) 的请求。def hello_world(): ...: 这是视图函数 (view function)。它处理请求并返回响应。if __name__ == '__main__':: 这是标准的 Python 构造,确保app.run()仅在脚本直接执行时才被调用。app.run(debug=True): 启动 Flask 内置的开发 Web 服务器。debug=True启用调试模式(更多详情见下文)。
确保你的虚拟环境已激活。在终端中运行脚本:
python hello.py你应该会看到类似以下的输出:
* Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: ...打开你的 Web 浏览器,导航到 http://127.0.0.1:5000/。你应该会看到文本 ‘Hello, World!’。
开发服务器选项
Section titled “开发服务器选项”app.run() 方法接受几个可选参数:
| 序号 | 参数 | 描述 |
|---|---|---|
| 1 | host | 监听的主机名。默认为 ‘127.0.0.1’ (localhost),仅从你的计算机可访问。设置为 ‘0.0.0.0’ 可使服务器从网络上的其他设备访问。 |
| 2 | port | 监听的端口号。默认为 5000。 |
| 3 | debug | 默认为 False。如果设置为 True,则启用调试器和自动代码重载。重要提示,切勿在生产环境中启用调试模式,因为存在安全风险和性能开销。 |
调试模式说明
Section titled “调试模式说明”启用调试模式 (app.run(debug=True) 或通过设置 FLASK_DEBUG=1 环境变量) 在开发期间提供两个关键功能:
- 重载器 (Reloader): 服务器在检测到代码文件更改时会自动重启,因此你在每次修改后无需手动停止和启动它。
- 调试器 (Debugger): 如果在请求期间发生错误,Flask 会在浏览器中显示一个交互式回溯 (traceback),允许你在错误上下文中检查变量并执行代码。
警告: 调试模式允许从浏览器执行任意 Python 代码。这是一个主要的安全漏洞,绝不能在对公众开放的生产服务器上启用。
Flask – 路由
Section titled “Flask – 路由”路由 (Routing) 是将用户输入的或客户端请求的 URL (Uniform Resource Locators) 映射到你的 Flask 应用中的特定 Python 函数(视图函数)的机制。这允许用户通过不同的 URL 访问应用的不同部分。
@app.route() 装饰器
Section titled “@app.route() 装饰器”在 Flask 中定义路由最常见的方式是使用 @app.route() 装饰器。你将其直接放置在你想要与特定 URL 路径关联的视图函数之上。
@app.route('/about')def about_page(): return 'This is the About page.' return '这是关于页面。'在此示例中,在浏览器中访问 http://your-app-domain/about 将执行 about_page() 函数,其返回值 (‘This is the About page.’) 将作为响应发送回。
add_url_rule() 方法
Section titled “add_url_rule() 方法”或者,你可以使用 app.add_url_rule() 方法实现相同的结果。这在某些场景下可能有用,例如需要编程方式生成路由时。
def contact_page(): return 'Contact us here.' return '在这里联系我们。'
# Equivalent to using the @app.route('/contact') decorator# 等同于使用 @app.route('/contact') 装饰器app.add_url_rule('/contact', 'contact_endpoint_name', contact_page)参数包括:
'/contact': URL 规则字符串。'contact_endpoint_name': 端点 (endpoint) 名称。这通常是视图函数的名称,并由url_for()(稍后介绍)使用。如果省略,Flask 将使用函数名。contact_page: 要调用的视图函数。
Flask – 变量规则
Section titled “Flask – 变量规则”你可以通过向路由定义添加变量部分来使 URL 的某些部分动态化。这允许一个视图函数处理多个相关的 URL。
变量部分使用 <variable_name> 标记。默认情况下,捕获到的值被视为字符串,并作为关键字参数传递给关联的视图函数。
示例:
from flask import Flask
app = Flask(__name__)
# The <username> part is a variable# <username> 部分是一个变量@app.route('/user/<username>')def show_user_profile(username): # The value captured from the URL is passed as the 'username' argument # 从 URL 捕获的值作为 'username' 参数传递 return f'User Profile: {username}' return f'用户简介:{username}'如果你运行此应用并导航到 http://127.0.0.1:5000/user/Alice,浏览器将显示:
User Profile: Alice用户简介:Alice类似地,访问 http://127.0.0.1:5000/user/Bob 将显示 用户简介:Bob。
转换器 (Converters)
Section titled “转换器 (Converters)”你可以使用 <converter:variable_name> 为变量部分指定一个转换器。这告诉 Flask 期望特定的数据类型,并在将其传递给视图函数之前相应地转换 URL 段。
| 序号 | 转换器 | 描述 |
|---|---|---|
| 1 | string | 接受不包含斜杠的任何文本(这是默认值)。 |
| 2 | int | 接受正整数。 |
| 3 | float | 接受正浮点值。 |
| 4 | path | 类似于 string,但也可以接受斜杠(用于捕获文件路径)。 |
| 5 | uuid | 接受 UUID 字符串。 |
带有转换器的示例:
from flask import Flask
app = Flask(__name__)
@app.route('/post/<int:post_id>')def show_post(post_id): # post_id will be an integer # post_id 将是一个整数 return f'Post Number: {post_id}' return f'帖子编号:{post_id}'
@app.route('/path/<path:subpath>')def show_subpath(subpath): # subpath can contain slashes # subpath 可以包含斜杠 return f'Subpath: {subpath}' return f'子路径:{subpath}'
if __name__ == '__main__': app.run(debug=True)运行此代码:
- 访问
http://127.0.0.1:5000/post/123。输出:帖子编号:123 - 访问
http://127.0.0.1:5000/post/abc。输出:404 Not Found(因为 ‘abc’ 不是一个整数)。 - 访问
http://127.0.0.1:5000/path/folder/file.txt。输出:子路径:folder/file.txt
规范 URL 和尾部斜杠
Section titled “规范 URL 和尾部斜杠”Flask 的路由(基于 Werkzeug)以特定方式处理 URL 中的尾部斜杠,以确保唯一性和一致性,这类似于 Apache 等 Web 服务器的行为:
from flask import Flaskapp = Flask(__name__)
@app.route('/projects/') # Note the trailing slash# 注意尾部的斜杠def projects(): return 'The project page.' return '项目页面。'
@app.route('/about') # Note: no trailing slash# 注意:没有尾部的斜杠def about(): return 'The about page.' return '关于页面。'
if __name__ == '__main__': app.run(debug=True)- 带有尾部斜杠的规则 (
/projects/): 这定义了一个以斜杠结尾的规范 URL。直接访问/projects/可以工作。访问/projects(不带斜杠)将导致 Flask 自动将浏览器重定向到/projects/。 - 不带尾部斜杠的规则 (
/about): 这定义了一个不带斜杠的规范 URL。直接访问/about可以工作。访问/about/(带有斜杠)将导致404 Not Found错误。
这种行为有助于防止搜索引擎的重复内容问题,并提供一致的 URL 结构。
Flask – URL 构建
Section titled “Flask – URL 构建”在你的应用中硬编码 URL(例如,在模板或重定向函数中)通常是一个不好的做法。如果你稍后重构路由,你将不得不查找并更新每一个硬编码的 URL。Flask 提供了 url_for() 函数,用于根据视图函数的端点名称动态构建 URL。
url_for() 函数
Section titled “url_for() 函数”url_for() 将视图函数的端点名称作为其第一个参数。默认情况下,端点名称就是视图函数本身的名称。它还接受对应于 URL 规则中定义的变量部分的关键字参数。
示例:
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/admin')def admin_dashboard(): # View function for the admin dashboard # 管理员仪表盘的视图函数 return 'Admin Dashboard' return '管理员仪表盘'
@app.route('/guest/<guest_name>')def guest_greeting(guest_name): # View function for greeting guests # 问候访客的视图函数 return f'Hello {guest_name}, welcome as Guest!' return f'你好 {guest_name},欢迎作为访客!'
@app.route('/user/<name>')def user_profile(name): # This function checks the name and redirects # 此函数检查名称并重定向 if name == 'admin': # Redirect to the admin dashboard using url_for() # 使用 url_for() 重定向到管理员仪表盘 # 'admin_dashboard' is the name of the function/endpoint # 'admin_dashboard' 是函数/端点的名称 return redirect(url_for('admin_dashboard')) else: # Redirect to the guest greeting page, passing the name # 重定向到访客问候页面,并传递名称 # 'guest_greeting' is the function/endpoint name # 'guest_greeting' 是函数/端点名称 # 'guest_name' is the variable part in the target route '/guest/<guest_name>' # 'guest_name' 是目标路由 '/guest/<guest_name>' 中的变量部分 return redirect(url_for('guest_greeting', guest_name=name))
if __name__ == '__main__': app.run(debug=True)工作原理:
user_profile(name)函数从 URL 接收一个名称(例如,/user/admin或/user/Alice)。- 如果
name是 ‘admin’,url_for('admin_dashboard')生成 URL/admin。然后redirect()函数向浏览器发送响应,告知其导航到/admin。 - 如果
name是其他任何值(例如,‘Alice’),url_for('guest_greeting', guest_name=name)通过匹配端点'guest_greeting'并用提供的值填充变量部分<guest_name>来生成 URL/guest/Alice。然后浏览器被重定向到/guest/Alice。
测试示例:
- 导航到
http://127.0.0.1:5000/user/admin。浏览器将被重定向到http://127.0.0.1:5000/admin并显示 ‘管理员仪表盘’。 - 导航到
http://127.0.0.1:5000/user/Bob。浏览器将被重定向到http://127.0.0.1:5000/guest/Bob并显示 ‘你好 Bob,欢迎作为访客!’。
使用 url_for() 使你的应用更健壮。如果你稍后更改 /admin 的 URL 路径(例如,更改为 /admin/panel),你只需要更新 @app.route 装饰器;url_for('admin_dashboard') 将在任何使用它的地方自动生成正确的新 URL。
Flask – HTTP 方法
Section titled “Flask – HTTP 方法”HTTP (HyperText Transfer Protocol) 是万维网上通信的基础。它定义了几种请求方法(通常称为“动词”),用于指示对由 URL 标识的资源执行所需的操作。
常见的 HTTP 方法:
| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | GET | 请求从指定资源获取数据。应仅检索数据,不应有其他影响。这是最常见的方法,当你用浏览器输入 URL 或点击链接时使用。 |
| 2 | POST | 提交数据到指定资源进行处理(例如,提交表单,上传文件)。通常会导致服务器上的状态改变或副作用。 |
| 3 | PUT | 用请求有效载荷替换目标资源的当前表示。常用于更新。 |
| 4 | DELETE | 删除指定的资源。 |
| 5 | HEAD | 与 GET 相同,但仅请求响应头,不包含响应体。可用于检查资源元数据,如最后修改时间或大小。 |
| 6 | OPTIONS | 描述目标资源的通信选项(例如,允许哪些 HTTP 方法)。 |
| 7 | PATCH | 对资源应用部分修改。 |
在 Flask 路由中指定方法
Section titled “在 Flask 路由中指定方法”默认情况下,Flask 路由只响应 GET 请求。你可以通过向 @app.route() 装饰器或 app.add_url_rule() 提供 methods 参数(一个字符串列表)来允许其他方法。
示例:处理 GET 和 POST 请求
Section titled “示例:处理 GET 和 POST 请求”我们来创建一个简单的登录表单,它使用 POST 提交数据。
- 在
templates文件夹中创建一个名为login.html的 HTML 文件:
<!DOCTYPE html><html><head> <title>Login</title> <title>登录</title></head><body> <h1>Please Log In</h1> <h1>请登录</h1> <form action="{{ url_for('login') }}" method="post"> <p>Username: <input type="text" name="username"></p> <p>用户名:<input type="text" name="username"></p> <p><input type="submit" value="Login"></p> <p><input type="submit" value="登录"></p> </form></body></html>注意:我们使用 url_for('login') 来生成表单的 action URL,并指定 method="post"。
- 创建 Flask 应用
app.py:
from flask import Flask, request, redirect, url_for, render_template
app = Flask(__name__)
# This route handles displaying the login form (GET) and processing the submission (POST)# 此路由处理显示登录表单 (GET) 和处理提交 (POST)@app.route('/login', methods=['GET', 'POST'])def login(): if request.method == 'POST': # User submitted the form, process the data # 用户提交了表单,处理数据 submitted_username = request.form['username'] # Access form data via request.form # 通过 request.form 访问表单数据 # In a real app, you would validate the username/password here # 在实际应用中,你会在此时验证用户名/密码 return redirect(url_for('welcome', user=submitted_username)) else: # User is requesting the page via GET, show the login form # 用户通过 GET 请求页面,显示登录表单 return render_template('login.html')
@app.route('/welcome/<user>')def welcome(user): return f'Welcome, {user}!' return f'欢迎,{user}!'
if __name__ == '__main__': app.run(debug=True)解释:
methods=['GET', 'POST']告诉 Flask/login路由应同时处理 GET 和 POST 请求。request.method: 在视图函数内部,我们检查用于当前请求的 HTTP 方法。- 如果
request.method == 'POST': 我们使用request.form访问提交的表单数据,它是一个类似字典的对象。request.form['username']检索名为 ‘username’ 的输入字段中输入的值。然后我们重定向到欢迎页面。 - 如果
request.method == 'GET'(或允许的任何其他方法,但这里是else情况):我们渲染login.html模板来显示表单。 request.args: 如果数据是作为 URL 查询字符串的一部分发送的(GET 请求常见,例如/search?query=flask),你将使用request.args.get('query')访问它。
运行 app.py 并导航到 http://127.0.0.1:5000/login。输入用户名并点击登录。表单数据将通过 POST 发送,由 login() 函数处理,你将被重定向到欢迎页面。
Flask – 模板
Section titled “Flask – 模板”直接在 Python 视图函数中生成 HTML 很快会变得复杂且难以管理,特别是对于非简单的页面。将表示逻辑(HTML 结构)与应用逻辑(Python 代码)混杂在一起会使代码更难阅读和维护。
相比于像这样从视图函数返回 HTML 字符串:
@app.route('/')def index_bad(): user = "Alice" # This is hard to read and maintain! # 这很难阅读和维护! html = f"""<!DOCTYPE html><html><head><title>Welcome</title></head><body> <h1>Hello, {user}!</h1> <p>This is generated directly in Python.</p></body></html>""" return htmlFlask 使用强大的 Jinja2 模板引擎来分离表示和逻辑。你创建包含特殊占位符和逻辑结构的 HTML 文件(模板),然后 Flask 使用你的视图函数提供的数据来渲染这些模板。
render_template() 函数
Section titled “render_template() 函数”Flask 提供了 render_template() 函数来渲染 Jinja2 模板。按照惯例,Flask 在与你的应用脚本相同的目录中查找名为 templates 的文件夹中的模板。
项目结构:
my_flask_app/├── app.py # Your Flask application script│ # 你的 Flask 应用脚本└── templates/ # Folder for HTML templates │ # HTML 模板文件夹 └── index.html示例:
- 创建
templates/index.html:
<!DOCTYPE html><html><head> <title>Welcome</title> <title>欢迎</title></head><body> <!-- {{ username }} is a Jinja2 variable placeholder --> <!-- {{ username }} 是一个 Jinja2 变量占位符 --> <h1>Hello, {{ username }}!</h1> <h1>你好,{{ username }}!</h1> <p>This page was rendered from a template.</p> <p>此页面由模板渲染生成。</p></body></html>- 更新
app.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')def index_good(): user = "Bob" # Data to pass to the template # 传递给模板的数据 # Render 'index.html' and pass 'user' as the 'username' variable # 渲染 'index.html' 并将 'user' 作为 'username' 变量传递 return render_template('index.html', username=user)
if __name__ == '__main__': app.run(debug=True)当你运行 app.py 并访问 / 时,Flask 会找到 templates/index.html,将 {{ username }} 替换为 user 变量的值(‘Bob’),并将生成的 HTML 返回给浏览器。
Jinja2 语法基础
Section titled “Jinja2 语法基础”Jinja2 使用特定的分隔符将逻辑嵌入到 HTML 中:
{{ ... }}: 用于直接输出值到 HTML 的表达式(例如,{{ variable_name }}、{{ user.name }}、{{ 2 + 2 }})。{% ... %}: 用于控制流语句,如条件语句 (if/elif/else)、循环 (for)、模板继承 (extends、block) 等。{# ... #}: 用于模板引擎忽略的注释,不会出现在最终输出中。
带条件语句的示例:
- 创建
templates/result.html:
<!DOCTYPE html><html><head><title>Result</title></head><head><title>结果</title></head><body> {% if score >= 60 %} <h1>Congratulations, you passed!</h1> <h1>恭喜,你通过了!</h1> {% else %} <h1>Sorry, you failed.</h1> <h1>抱歉,你未能通过。</h1> {% endif %} <p>Your score: {{ score }}</p> <p>你的分数:{{ score }}</p></body></html>- 在
app.py中添加一个路由:
@app.route('/result/<int:marks>')def show_result(marks): return render_template('result.html', score=marks)访问 /result/75 查看“通过”消息,访问 /result/40 查看“未能通过”消息。
带循环的示例:
- 创建
templates/items.html:
<!DOCTYPE html><html><head><title>Items</title></head><head><title>物品</title></head><body> <h1>Available Items:</h1> <h1>可用物品:</h1> <ul> {% for item in item_list %} <li>{{ item }}</li> {% else %} <li>No items available.</li> <li>暂无可用物品。</li> {% endfor %} </ul></body></html>- 在
app.py中添加一个路由:
@app.route('/items')def list_items(): items = ['Apple', 'Banana', 'Orange'] # items = [] # Try with an empty list to see the 'else' block # items = [] # 尝试使用空列表查看 'else' 块 return render_template('items.html', item_list=items)这展示了如何迭代从视图函数传递过来的列表。for 循环内的 {% else %} 块在序列 (item_list) 为空时执行。
模板提供了一种清晰的方式来构建网页,并将表示与 Python 逻辑分开。
Flask – 静态文件
Section titled “Flask – 静态文件”Web 应用几乎总是需要提供静态文件,例如 CSS 样式表、JavaScript 文件、图像、字体等。这些文件不会根据用户请求动态变化;它们被直接提供给浏览器。
static 文件夹
Section titled “static 文件夹”Flask 默认配置为从与应用脚本(根路径)相同的目录中的名为 static 的文件夹提供静态文件。
项目结构:
my_flask_app/├── app.py # Your Flask application script│ # 你的 Flask 应用脚本├── templates/ # Folder for HTML templates│ │ # HTML 模板文件夹│ └── index.html└── static/ # Folder for static files ├── css/ │ └── style.css ├── js/ │ └── script.js └── images/ └── logo.png生成静态文件 URL
Section titled “生成静态文件 URL”要在模板中链接到静态文件,应使用 url_for() 函数,并带有特殊的端点名称 'static'。filename 参数应该是文件相对于 static 文件夹的路径。
示例:
- 创建
static/css/style.css:
body { font-family: sans-serif; color: #333;}h1 { color: steelblue;}- 创建
static/js/alert.js:
function showWelcomeAlert() { alert('Welcome to the Static Files Example!'); alert('欢迎来到静态文件示例!');}- 创建
templates/static_example.html:
<!DOCTYPE html><html><head> <title>Static Files</title> <title>静态文件</title> <!-- Link to CSS file using url_for('static', ...) --> <!-- 使用 url_for('static', ...) 链接到 CSS 文件 --> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"></head><body> <h1>Static Files Example</h1> <h1>静态文件示例</h1> <p>This page uses CSS and JavaScript served by Flask.</p> <p>此页面使用了由 Flask 提供的 CSS 和 JavaScript。</p>
<button onclick="showWelcomeAlert()">Show Alert</button> <button onclick="showWelcomeAlert()">显示警告框</button>
<!-- Include JavaScript file --> <!-- 包含 JavaScript 文件 --> <script src="{{ url_for('static', filename='js/alert.js') }}"></script></body></html>- 在
app.py中添加一个路由:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/static-test')def static_test_page(): return render_template('static_example.html')
if __name__ == '__main__': app.run(debug=True)运行 app.py 并访问 http://127.0.0.1:5000/static-test。页面将加载 style.css 中定义的样式,点击按钮将触发 alert.js 中的 JavaScript 函数。在浏览器中检查页面源代码,你会看到 Flask 生成了像 /static/css/style.css 和 /static/js/alert.js 这样的 URL。
使用 url_for('static', ...) 可以确保即使将来更改了应用的根路径或配置了不同的静态文件路径,你的链接也能继续工作。
Flask – 请求对象
Section titled “Flask – 请求对象”当客户端(例如 Web 浏览器)向你的 Flask 应用发送请求时,Flask 会通过全局 request 对象提供所有传入的数据。你需要从 flask 模块导入它才能在视图函数中使用它。
from flask import requestrequest 对象提供对传入 HTTP 请求各个部分的访问。一些最重要的属性包括:
request.method: 用于请求的 HTTP 方法(例如,‘GET’、‘POST’)。request.form: 一个类似字典的对象,包含从提交的 HTML 表单中解析出的数据(通常用于 POST 或 PUT 请求)。键是表单元素的name属性。request.args: 一个类似字典的对象,包含 URL 查询字符串(?后面的部分)的解析内容。对 GET 请求很有用。request.values: 一个包含来自request.form和request.args的数据的组合字典。使用时请谨慎,因为它可能会模糊数据的来源。request.files: 一个类似字典的对象,包含作为请求一部分上传的文件(通常通过enctype="multipart/form-data"的表单)。request.cookies: 一个包含客户端发送的 Cookie 的字典。request.headers: 一个类似字典的对象,包含请求头。request.get_json(): 解析传入的 JSON 请求数据并将其作为 Python 字典或列表返回。如果请求体不是有效的 JSON 或Content-Type头不正确,则会引发错误。request.data: 包含原始传入的请求体作为字节串。如果数据不是表单数据或 JSON,则很有用。request.remote_addr: 发送请求的客户端的 IP 地址。(注意:如果你的应用位于代理后面,这可能是代理的地址)。
用法示例:
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/process', methods=['GET', 'POST'])def process_data(): if request.method == 'POST': name = request.form.get('name', 'Unknown') # Use .get() for safer access # 使用 .get() 方法进行更安全的访问 email = request.form.get('email') # Process the form data... # 处理表单数据... return f'POST Request Received! Name: {name}, Email: {email}' return f'收到 POST 请求!姓名:{name},电子邮件:{email}' else: # GET Request # GET 请求 search_query = request.args.get('q', 'None') # Get query param 'q' # 获取查询参数 'q' page = request.args.get('page', '1', type=int) # Get 'page', default to 1, type=int # 获取 'page' 参数,默认为 1,类型为 int # Process GET parameters... # 处理 GET 参数... return f'GET Request Received! Query: {search_query}, Page: {page}' return f'收到 GET 请求!查询:{search_query},页码:{page}'
# Example HTML form (save as templates/input_form.html)# HTML 表单示例(保存为 templates/input_form.html)# <!DOCTYPE html># <html><body># <form method="post" action="{{ url_for('process_data') }}"># Name: <input type="text" name="name"><br># Email: <input type="email" name="email"><br># <input type="submit" value="Submit POST"># <input type="submit" value="提交 POST"># </form># <hr># <a href="{{ url_for('process_data', q='flask tutorial', page=2) }}">Submit GET</a># <a href="{{ url_for('process_data', q='flask tutorial', page=2) }}">提交 GET</a># </body></html>
@app.route('/form')def show_form(): return render_template('input_form.html')
if __name__ == '__main__': app.run(debug=True, port=5005) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口运行此应用,访问 /form。提交表单会发送 POST 请求。点击链接会发送带查询参数的 GET 请求。process_data 函数会相应地使用 request 对象属性。
注意:直接访问字典键(例如,request.form['name'])如果键不存在会引发 KeyError。使用 .get('key', default_value) 方法通常更安全,因为如果键缺失,它会返回 None 或指定的默认值。
Flask – 将表单数据发送到模板
Section titled “Flask – 将表单数据发送到模板”Web 应用中常见的一种模式是:
- 向用户显示 HTML 表单(通常通过 GET 请求)。
- 用户填写并提交表单(通常通过 POST 请求)。
- 在服务器上处理提交的数据。
- 显示结果页面,通常显示提交的数据或确认消息。
我们可以使用 Flask 的 request 对象访问提交的表单数据,并使用 render_template() 将此数据传递给结果模板。
示例:
我们来创建一个应用,收集学生信息并在确认页面显示。
- 创建输入表单(
templates/student_form.html):
<!DOCTYPE html><html><head><title>Student Information</title></head><head><title>学生信息</title></head><body> <h2>Enter Student Details</h2> <h2>输入学生详细信息</h2> <form action="{{ url_for('submit_student_data') }}" method="post"> <p>Name: <input type="text" name="student_name" required></p> <p>姓名:<input type="text" name="student_name" required></p> <p>Major: <input type="text" name="major" required></p> <p>专业:<input type="text" name="major" required></p> <p>Email: <input type="email" name="email"></p> <p>电子邮件:<input type="email" name="email"></p> <p><input type="submit" value="Submit"></p> <p><input type="submit" value="提交"></p> </form></body></html>- 创建结果模板(
templates/student_result.html):
<!DOCTYPE html><html><head><title>Submission Result</title></head><head><title>提交结果</title></head><body> <h2>Student Data Submitted Successfully</h2> <h2>学生数据提交成功</h2> <p>Here is the information received:</p> <p>以下是收到的信息:</p> <table border="1"> <thead> <tr><th>Field</th><th>Value</th></tr> <tr><th>字段</th><th>值</th></tr> </thead> <tbody> <!-- Iterate over the submitted_data dictionary --> <!-- 遍历 submitted_data 字典 --> {% for field, value in submitted_data.items() %} <tr> <td>{{ field }}</td> <td>{{ value }}</td> </tr> {% endfor %} </tbody> </table> <p><a href="{{ url_for('show_student_form') }}">Enter another student</a></p> <p><a href="{{ url_for('show_student_form') }}">输入另一位学生</a></p></body></html>注意:我们使用 submitted_data.items() (Python 3) 来遍历传递给模板的字典的键值对。
- 创建 Flask 应用(
app.py):
from flask import Flask, render_template, request
app = Flask(__name__)
# Route to display the form# 显示表单的路由@app.route('/')def show_student_form(): return render_template('student_form.html')
# Route to handle the form submission# 处理表单提交的路由@app.route('/submit', methods=['POST'])def submit_student_data(): if request.method == 'POST': # request.form is an ImmutableMultiDict # request.form 是一个 ImmutableMultiDict # Convert it to a regular dictionary for easier handling # 将其转换为普通字典以便更轻松地处理 # or pass it directly if the template handles it. # 或者如果模板可以处理,则直接传递。 result_data = request.form.to_dict()
# Pass the collected data to the result template # 将收集到的数据传递给结果模板 return render_template("student_result.html", submitted_data=result_data) # Optional: Handle cases where someone accesses /submit via GET # 可选:处理通过 GET 访问 /submit 的情况 return "Please submit the form via POST.", 405 # Method Not Allowed return "请通过 POST 提交表单。", 405 # 不允许的方法 (Method Not Allowed)
if __name__ == '__main__': app.run(debug=True, port=5006) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口工作原理:
- 访问
/(或根 URL)触发show_student_form(),它显示student_form.html。 - 当用户提交表单时,一个 POST 请求被发送到
/submit(如表单的action属性中指定)。 submit_student_data()函数处理 POST 请求。request.form包含提交的数据(例如,{'student_name': 'Alice', 'major': 'Physics', 'email': 'alice@example.com'})。我们使用.to_dict()将其转换为标准 Python 字典,尽管render_template通常可以直接处理ImmutableMultiDict。- 这个
result_data字典以变量名submitted_data传递给student_result.html模板。 student_result.html中的 Jinja2for循环遍历submitted_data字典,并在 HTML 表格中显示每个字段及其值。
运行 app.py,导航到 http://127.0.0.1:5000/,填写表单并提交。你将被带到显示输入数据的结果页面。
Flask – Cookie
Section titled “Flask – Cookie”HTTP Cookie 是 Web 浏览器存储在客户端计算机上的一小段数据。网站使用它们来记住用户跨多个请求的信息,例如登录状态、偏好设置或购物车中的商品。Cookie 会随着每个后续请求发送回同一域的服务器。
Cookie 在 Flask 中的工作方式
Section titled “Cookie 在 Flask 中的工作方式”- 设置 Cookie: Cookie 设置在响应对象上。你通常使用
make_response()创建一个响应,然后使用response.set_cookie()方法。 - 读取 Cookie: 浏览器发送的传入 Cookie 在
request.cookies类似字典的对象中可用。 - Cookie 属性: 除了键和值,你还可以设置
max_age(生命周期,单位秒)、expires(特定过期日期/时间)、path(URL 路径限制)、domain(域限制)、secure(仅通过 HTTPS 发送)和httponly(阻止 JavaScript 访问)等属性。
示例:设置和读取 Cookie
我们来创建一个应用,询问用户的姓名,将其存储在 Cookie 中,然后在用户后续访问时使用存储的姓名问候他们。
- 创建
templates/ask_name.html:
<!DOCTYPE html><html><head><title>Enter Name</title></head><head><title>输入姓名</title></head><body> <h1>What's your name?</h1> <h1>你叫什么名字?</h1> <form action="{{ url_for('set_user_cookie') }}" method="post"> <p>Name: <input type="text" name="username" required></p> <p>姓名:<input type="text" name="username" required></p> <p><input type="submit" value="Save Name"></p> <p><input type="submit" value="保存姓名"></p> </form></body></html>- 创建
templates/show_greeting.html:
<!DOCTYPE html><html><head><title>Greeting</title></head><head><title>问候</title></head><body> <h1>Hello, {{ user_name_from_cookie }}!</h1> <h1>你好,{{ user_name_from_cookie }}!</h1> <p>Your name was remembered using a cookie.</p> <p>你的姓名通过 Cookie 被记住了。</p> <p><a href="{{ url_for('clear_user_cookie') }}">Forget My Name</a></p> <p><a href="{{ url_for('clear_user_cookie') }}">忘记我的姓名</a></p></body></html>- 创建 Flask 应用(
app.py):
from flask import Flask, render_template, request, make_response, redirect, url_forimport datetime
app = Flask(__name__)
@app.route('/')def index(): # Check if the username cookie exists # 检查 username cookie 是否存在 username = request.cookies.get('username') if username: # If cookie exists, show greeting # 如果 cookie 存在,显示问候语 return render_template('show_greeting.html', user_name_from_cookie=username) else: # If no cookie, ask for name # 如果没有 cookie,询问姓名 return render_template('ask_name.html')
@app.route('/setcookie', methods=['POST'])def set_user_cookie(): if request.method == 'POST': user = request.form['username']
# Create a response object # 创建一个响应对象 response = make_response(redirect(url_for('index')))
# Set the cookie # 设置 cookie # max_age is in seconds (e.g., 1 year) # max_age 以秒为单位(例如,1 年) max_age_seconds = 365 * 24 * 60 * 60 expires_time = datetime.datetime.utcnow() + datetime.timedelta(seconds=max_age_seconds)
response.set_cookie('username', user, max_age=max_age_seconds, expires=expires_time, httpyonly=True, samesite='Lax') # httponly=True prevents JavaScript access (good for security) # httponly=True 阻止 JavaScript 访问(有利于安全) # samesite='Lax' helps prevent CSRF attacks # samesite='Lax' 有助于防止 CSRF 攻击
return response # Handle non-POST requests if necessary # 如果需要,处理非 POST 请求 return redirect(url_for('index'))
@app.route('/clearcookie')def clear_user_cookie(): response = make_response(redirect(url_for('index'))) # Delete the cookie by setting an empty value and past expiry # 通过设置空值和过去的过期时间来删除 cookie response.set_cookie('username', '', expires=0) return response
if __name__ == '__main__': app.run(debug=True, port=5007) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口工作原理:
- 访问
/会使用request.cookies.get('username')检查是否存在 ‘username’ cookie。如果找到,则渲染show_greeting.html。如果未找到,则显示ask_name.html。 - 提交姓名表单会将 POST 请求发送到
/setcookie。 set_user_cookie函数从request.form获取姓名,使用make_response(redirect(url_for('index')))创建一个重定向响应,然后使用response.set_cookie()存储姓名。设置了max_age、httponly和samesite等重要属性以获得更好的控制和安全性。- 访问
/clearcookie会使用response.set_cookie(),设置空值和过去的过期日期 (expires=0),从而有效地删除 cookie。 - 浏览器存储 Cookie,并在后续请求中将其发送回同一域,允许
/路由检索并使用它。
安全说明: 标准 Cookie 以明文形式存储,用户可以轻松查看和修改,如果在不使用 HTTPS 的情况下,也可能被拦截。切勿将敏感信息(如密码)直接存储在 Cookie 中。对于会话管理,请使用 Flask 的会话机制,它使用加密签名的 Cookie。
Flask – 会话 (Sessions)
Section titled “Flask – 会话 (Sessions)”虽然 Cookie 将数据存储在客户端(浏览器),但会话通常将数据存储在服务器端。会话允许你在用户访问(或“会话”)你的网站期间,在多个请求之间维护有关用户的信息。常见的用途包括跟踪登录状态、用户偏好设置或购物车内容。
Flask 的默认会话实现
Section titled “Flask 的默认会话实现”Flask 内置的会话机制使用加密签名的 Cookie。这意味着:
- 会话数据实际上存储在客户端的 Cookie 中。
- Flask 使用一个密钥(通过
app.secret_key配置)对 Cookie 进行数字签名。 - 当客户端将 Cookie 发送回时,Flask 使用密钥验证签名。如果签名无效(表示 Cookie 数据被篡改过),则会话被丢弃。
- 这种方法对于简单的会话需求来说避免了服务器端存储,但也有局限性(例如,Cookie 大小限制,数据可见但不能篡改)。
重要提示: Flask 会话工作必须设置一个 secret_key。此密钥应是一个长、随机且不可预测的字符串,保持机密,理想情况下应从环境变量加载,而不是硬编码在源代码中。
使用会话对象
Section titled “使用会话对象”Flask 提供了一个 session 对象,其行为类似于 Python 字典。你需要从 flask 导入它。
from flask import Flask, session, request, render_template, redirect, url_forimport os # For environment variables# 用于环境变量
app = Flask(__name__)
# Configure a secret key.# 配置密钥。# IMPORTANT: Use a strong, random key and load from environment variables in production!# 重要:使用强、随机的密钥,并在生产环境中从环境变量加载!# Example: app.secret_key = os.environ.get('FLASK_SECRET_KEY', 'default-insecure-key-for-dev')# 示例:app.secret_key = os.environ.get('FLASK_SECRET_KEY', '默认开发不安全密钥')app.secret_key = 'a-very-secret-random-string-that-is-hard-to-guess'# 这是一个非常秘密的随机字符串,很难猜测
@app.route('/')def index(): if 'username' in session: username = session['username'] return f''' Logged in as: {username}<br> 以 {username} 身份登录<br> Visit <a href="{url_for('logout')}">/logout</a> to log out. 访问 <a href="{url_for('logout')}">/logout</a> 退出登录。 ''' return f''' You are not logged in.<br> 你尚未登录。<br> Visit <a href="{url_for('login')}">/login</a> to log in. 访问 <a href="{url_for('login')}">/login</a> 进行登录。 '''
@app.route('/login', methods=['GET', 'POST'])def login(): if request.method == 'POST': # In a real app, validate username/password here! # 在实际应用中,在此处验证用户名/密码! submitted_username = request.form.get('username') if submitted_username: # Store username in the session # 在会话中存储用户名 session['username'] = submitted_username return redirect(url_for('index')) else: return 'Please enter a username.', 400 return '请输入用户名。', 400
# Show login form on GET request # 在 GET 请求时显示登录表单 return ''' <form method="post"> Username: <input type="text" name="username"><br> 用户名:<input type="text" name="username"><br> <input type="submit" value="Login"> <input type="submit" value="登录"> </form> '''
@app.route('/logout')def logout(): # Remove 'username' from the session if it exists # 如果存在,从会话中移除 'username' session.pop('username', None) # Use pop with default None to avoid KeyError # 使用带默认值 None 的 pop 方法,避免 KeyError return redirect(url_for('index'))
if __name__ == '__main__': app.run(debug=True, port=5008) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口工作原理:
app.secret_key = ...: 配置签名会话 Cookie 所需的密钥。session['username'] = submitted_username: 在login函数中 (POST 请求后),用户名存储在session字典中。if 'username' in session:: 在index函数中,我们检查会话中是否存在 ‘username’ 键,以确定用户是否已登录。username = session['username']: 如果已登录,我们检索该值。session.pop('username', None): 在logout函数中,pop()从会话中移除 ‘username’ 键,有效地注销用户。使用None作为第二个参数可以防止键不存在时出错。- Flask 自动处理会话字典的序列化、签名、在响应上设置 Cookie,以及在后续请求中从 Cookie 中验证/加载它。
secret_key 的安全最佳实践:
- 不要硬编码: 将其保存在版本控制系统(如 Git)之外。
- 使用环境变量: 从环境变量加载密钥(例如,
os.environ.get('SECRET_KEY'))。 - 生成一个强密钥: 使用加密安全随机生成器(例如,
python -c 'import secrets; print(secrets.token_hex(32))')。
对于需要服务器端会话存储的应用(例如,由于会话数据量大或需要更严格的安全性),Flask-Session 等扩展提供了各种后端(Redis、Memcached、数据库、文件系统)。
Flask – 重定向与错误处理
Section titled “Flask – 重定向与错误处理”通常需要将用户从一个 URL 重定向到另一个 URL。例如,成功登录后,你可能将用户重定向到其仪表盘;或者提交表单后,将其重定向到确认页面。Flask 为此提供了 redirect() 函数。
redirect() 函数
from flask import redirect, url_for
# ... inside a view function ...# ... 在视图函数中 ...return redirect(url_for('target_endpoint_name'))# 返回重定向到 'target_endpoint_name' 的 URLredirect() 将目标 URL 作为其主要参数。强烈建议使用 url_for() 而不是硬编码来生成此 URL。
默认情况下,redirect() 发出 HTTP 状态码 302 Found(临时重定向)。你可以使用 code 参数指定不同的状态码,例如 301 Moved Permanently:
return redirect(url_for('new_location'), code=301)# 返回重定向到 'new_location' 的 URL,状态码为 301常见的重定向状态码:
301 Moved Permanently: 指示资源已永久移动。搜索引擎通常会更新其索引。302 Found: 指示临时重定向。客户端应继续在未来的请求中使用原始 URL。303 See Other: 通常在 POST 请求后使用,将客户端重定向到新资源时使用 GET 方法,防止用户刷新时意外重新提交。307 Temporary Redirect: 类似于 302,但强制客户端不更改请求方法(例如,如果原始请求是 POST,则重定向的请求也应该是 POST)。
示例:登录后重定向
from flask import Flask, redirect, url_for, render_template, request
app = Flask(__name__)app.secret_key = 'very secret' # Needed for potential session/flash usage# 需要密钥以用于可能的会话/消息闪现
@app.route('/')def index(): # Display the login form # 显示登录表单 return render_template('login_form.html') # Assume this template exists # 假设此模板存在
@app.route('/login', methods=['POST'])def process_login(): # Simulate checking credentials # 模拟检查凭据 if request.form.get('username') == 'admin' and request.form.get('password') == 'password': # Successful login, redirect to success page # 登录成功,重定向到成功页面 return redirect(url_for('login_success')) else: # Failed login, redirect back to the index (login form) # 登录失败,重定向回索引页(登录表单) # Optionally add a flash message here to indicate failure # 可选地在此处添加一条消息闪现以指示失败 return redirect(url_for('index'))
@app.route('/success')def login_success(): return 'Logged in successfully!' return '登录成功!'
# Assume templates/login_form.html exists:# 假设 templates/login_form.html 存在:# <!DOCTYPE html><html><body><form method=post action="{{ url_for('process_login') }}"># Username: <input type=text name=username><br># 用户名:<input type=text name=username><br># Password: <input type=password name=password><br># 密码:<input type=password name=password><br># <input type=submit value=Login></form></body></html># <input type=submit value=登录></form></body></html>
if __name__ == '__main__': app.run(debug=True, port=5009) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口有时,你需要明确地表示错误情况,例如请求的资源未找到或用户没有权限。Flask 为此提供了 abort() 函数。
abort() 函数
from flask import abort
# ... inside a view function ...# ... 在视图函数中 ...if not user_has_permission: abort(403) # Forbidden # 终止,状态码 403 (禁止访问)
if resource_not_found: abort(404) # Not Found # 终止,状态码 404 (未找到)abort() 接受一个 HTTP 状态码作为参数,并立即停止当前视图函数的执行,向客户端返回相应的错误页面。
abort() 常见的错误状态码:
400 Bad Request: 服务器因语法无效而无法理解请求。401 Unauthorized: 需要身份验证,但验证失败或尚未提供。403 Forbidden: 服务器理解请求,但拒绝授权(用户缺乏权限)。404 Not Found: 服务器找不到请求的资源。405 Method Not Allowed: 请求中指定的方法(例如,POST)不允许用于该资源。500 Internal Server Error: 表示意外服务器情况的通用错误消息。
示例:使用 abort() 进行授权检查
from flask import Flask, abort, session # Assuming session is used for login status# 假设使用 session 来管理登录状态
app = Flask(__name__)app.secret_key = 'super secret key'# 超级秘密密钥
@app.route('/admin/dashboard')def admin_dashboard(): if 'user_role' not in session or session['user_role'] != 'admin': # If user is not logged in or not an admin, abort with 403 Forbidden # 如果用户未登录或不是管理员,则终止并返回 403 禁止访问 abort(403) # If execution reaches here, user is an admin # 如果执行到达此处,说明用户是管理员 return 'Welcome to the Admin Dashboard!' return '欢迎来到管理员仪表盘!'
# Add dummy login/logout routes for testing# 添加用于测试的模拟登录/注销路由@app.route('/login/admin')def login_admin(): session['user_role'] = 'admin' return 'Logged in as admin. <a href="/admin/dashboard">Go to dashboard</a>' return '以管理员身份登录。 <a href="/admin/dashboard">前往仪表盘</a>'
@app.route('/login/guest')def login_guest(): session['user_role'] = 'guest' return 'Logged in as guest. <a href="/admin/dashboard">Try dashboard</a>' return '以访客身份登录。 <a href="/admin/dashboard">尝试访问仪表盘</a>'
@app.route('/logout')def logout(): session.pop('user_role', None) return 'Logged out.' return '已注销。'
if __name__ == '__main__': app.run(debug=True, port=5010) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口自定义错误页面
Section titled “自定义错误页面”Flask 允许你使用 @errorhandler() 装饰器定义自定义错误处理程序,以显示用户友好的错误页面,而不是默认页面。
from flask import render_template
@app.errorhandler(404)def page_not_found(error): # The error object is passed to the handler # 错误对象会传递给处理程序 return render_template('errors/404.html'), 404 # Return template and status code # 返回模板和状态码
@app.errorhandler(500)def internal_server_error(error): return render_template('errors/500.html'), 500然后你需要创建相应的 HTML 模板(templates/errors/404.html,templates/errors/500.html),以便在发生错误时提供更好的用户体验。
Flask – 消息闪现 (Message Flashing)
Section titled “Flask – 消息闪现 (Message Flashing)”消息闪现 (Message flashing) 是 Web 应用中常见的一种模式,用于在用户执行某个操作后提供反馈。例如,成功提交表单后,你可能在用户访问的下一个页面上显示一条临时消息,如“记录保存成功!”。Flask 使用 flash() 函数和会话提供了一个简单的系统来实现此功能。
- 闪现消息: 在视图函数中(通常是处理表单提交或操作的函数),你调用
flash('你的消息内容')。这会将消息存储在用户的会话中,准备在下一个请求中显示。 - 检索消息: 在用户访问的下一个页面的模板中,你调用
get_flashed_messages()来检索在前一个请求中闪现的所有消息。此函数返回一个消息列表。 - 显示消息: 你遍历
get_flashed_messages()返回的列表,并显示每条消息。消息一旦被检索,就会从会话中移除,因此它们只会显示一次。 - 需要密钥: 由于闪现功能使用会话,因此必须配置
app.secret_key。
flash() 函数
Section titled “flash() 函数”from flask import flash
# ... inside a view function ...# ... 在视图函数中 ...flash('Profile updated successfully!', 'success') # Message with optional category# 消息内容,带可选的分类flash('Invalid input, please check the fields.', 'error')# 输入无效,请检查字段。错误分类参数:
message: 要显示的消息文本。category(可选):一个字符串,用于对消息进行分类(例如,‘success’、‘error’、‘warning’、‘info’)。这允许你在模板中以不同方式样式化消息。
get_flashed_messages() 函数
Section titled “get_flashed_messages() 函数”你通常在你的基础模板或你希望消息出现的特定页面模板中调用此函数。
{# Example in a Jinja2 template (e.g., base.html or index.html) #}{# Jinja2 模板示例(例如,base.html 或 index.html) #}{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} <ul class="flashes"> {% for category, message in messages %} <li class="{{ category }}">{{ message }}</li> {% endfor %} </ul> {% endif %}{% endwith %}参数:
with_categories=False(默认):返回一个消息字符串列表。with_categories=True: 返回一个元组列表,每个元组是(category, message)。category_filter=[](默认):如果with_categories=True,可以提供一个类别列表,只检索与这些类别匹配的消息(例如,category_filter=['error'])。
示例:闪现登录反馈
- 创建
templates/layout.html(基础模板):
<!DOCTYPE html><html><head> <title>{% block title %}Flask App{% endblock %}</title> <title>{% block title %}Flask 应用{% endblock %}</title> <style> .flashes { list-style-type: none; padding: 0; margin: 1em 0; } .flashes li { padding: 0.5em; margin-bottom: 0.5em; border: 1px solid; } .flashes .success { background-color: #dff0d8; border-color: #d6e9c6; color: #3c763d; } .flashes .error { background-color: #f2dede; border-color: #ebccd1; color: #a94442; } </style></head><body> {# Display flashed messages #} {# 显示闪现消息 #} {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} <ul class="flashes"> {% for category, message in messages %} <li class="{{ category }}">{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %}
{# Content block for child templates #} {# 子模板的内容块 #} {% block content %}{% endblock %}</body></html>- 创建
templates/index_flash.html:
{% extends "layout.html" %}{% block title %}Home{% endblock %}{% block title %}主页{% endblock %}{% block content %} <h1>Welcome!</h1> <h1>欢迎!</h1> <p>Go to <a href="{{ url_for('login_flash') }}">Login Page</a></p> <p>前往 <a href="{{ url_for('login_flash') }}">登录页面</a></p>{% endblock %}- 创建
templates/login_flash.html:
{% extends "layout.html" %}{% block title %}Login{% endblock %}{% block title %}登录{% endblock %}{% block content %} <h2>Login Form</h2> <h2>登录表单</h2> <form method="post"> Username: <input type="text" name="username" value="admin"><br> 用户名:<input type="text" name="username" value="admin"><br> Password: <input type="password" name="password" value="password"><br> 密码:<input type="password" name="password" value="password"><br> <input type="submit" value="Login"> <input type="submit" value="登录"> </form>{% endblock %}- 创建 Flask 应用(
app.py):
from flask import Flask, flash, redirect, render_template, request, url_for
app = Flask(__name__)app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' # IMPORTANT: Use a real secret key# 重要:使用真实的密钥
@app.route('/flash-home')def index_flash(): return render_template('index_flash.html')
@app.route('/flash-login', methods=['GET', 'POST'])def login_flash(): if request.method == 'POST': # Simulate checking credentials # 模拟检查凭据 if request.form['username'] == 'admin' and request.form['password'] == 'password': flash('You were successfully logged in!', 'success') flash('你已成功登录!', 'success') return redirect(url_for('index_flash')) else: flash('Invalid username or password. Please try again!', 'error') flash('无效的用户名或密码。请重试!', 'error') # No redirect here, stay on login page to show error # 此处不重定向,留在登录页面显示错误 # Alternatively, redirect back to login: return redirect(url_for('login_flash')) # 或者,重定向回登录页:return redirect(url_for('login_flash'))
return render_template('login_flash.html')
if __name__ == "__main__": app.run(debug=True, port=5011) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口运行应用,转到 /flash-home,点击链接到 /flash-login。使用正确的凭据提交表单,你将被重定向到 /flash-home 并看到成功消息。使用不正确的凭据提交,错误消息将显示在登录表单上方。
Flask – 文件上传
Section titled “Flask – 文件上传”处理文件上传是 Web 应用的常见需求,允许用户上传图像、文档或其他文件。Flask 使用 request.files 对象使这一过程变得简单明了。
HTML 表单
Section titled “HTML 表单”要启用文件上传,你的 HTML 表单必须满足两个条件:
method属性必须设置为POST。enctype属性必须设置为multipart/form-data。此编码允许文件数据与表单字段一起发送。
表单示例(templates/upload_form.html):
<!DOCTYPE html><html><head><title>File Upload</title></head><head><title>文件上传</title></head><body> <h1>Upload a File</h1> <h1>上传文件</h1> <form method="post" action="{{ url_for('handle_upload') }}" enctype="multipart/form-data"> <p><input type="file" name="file_to_upload" required></p> <p><input type="submit" value="Upload"></p> <p><input type="submit" value="上传"></p> </form></body></html><input type="file"> 元素创建文件选择按钮。
在 Flask 中处理上传
Section titled “在 Flask 中处理上传”提交 enctype="multipart/form-data" 的表单时,上传的文件可以通过 request.files 类似字典的对象访问。键对应于 <input type="file"> 元素的 name 属性。
request.files 中的每个值都是一个 FileStorage 对象(由 Werkzeug 提供)。这些对象的行为类似于文件对象,但也有附加属性和方法,最重要的是:
filename: 客户端浏览器提供的原始文件名。save(destination): 将上传的文件保存到服务器上的指定目标路径。
保护文件名安全
Section titled “保护文件名安全”切勿直接信任客户端提供的 filename。它可能包含恶意字符或路径元素(如 ../../../../../etc/passwd),试图遍历目录。在使用文件名构建文件路径之前,务必使用 Werkzeug 的 secure_filename() 函数清理文件名。
from werkzeug.utils import secure_filename
# ... inside view function ...# ... 在视图函数中 ...file = request.files['file_to_upload']if file and file.filename: # Sanitize the filename # 清理文件名 safe_filename = secure_filename(file.filename) # Now use safe_filename to build the destination path # 现在使用 safe_filename 构建目标路径secure_filename() 删除潜在的有害字符和路径分隔符,返回一个适用于保存的安全版本。
应用示例:
-
在你的项目目录中创建一个名为
uploads的文件夹来存储上传的文件。 -
创建 Flask 应用(
app.py):
import osfrom flask import Flask, request, redirect, url_for, render_template, flashfrom werkzeug.utils import secure_filename
# Define the path for uploaded files# 定义上传文件的路径UPLOAD_FOLDER = 'uploads'# Define allowed file extensions (optional but recommended)# 定义允许的文件扩展名(可选但推荐)ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
app = Flask(__name__)app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDERapp.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # Optional: Limit upload size (e.g., 16MB)# 可选:限制上传大小(例如,16MB)app.secret_key = 'secr3t-upl0ad-k3y'# 秘密上传密钥
# Ensure the upload folder exists# 确保上传文件夹存在if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER)
# Helper function to check allowed extensions# 检查允许扩展名的辅助函数def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload')def show_upload_form(): return render_template('upload_form.html')
@app.route('/handle-upload', methods=['POST'])def handle_upload(): if request.method == 'POST': # Check if the post request has the file part # 检查 POST 请求是否包含文件部分 if 'file_to_upload' not in request.files: flash('No file part in the request', 'error') flash('请求中没有文件部分', 'error') return redirect(request.url)
file = request.files['file_to_upload']
# If the user does not select a file, the browser submits an # 如果用户未选择文件,浏览器会提交一个 # empty file without a filename. # 没有文件名的空文件。 if file.filename == '': flash('No selected file', 'warning') flash('未选择文件', 'warning') return redirect(request.url)
if file and allowed_file(file.filename): filename = secure_filename(file.filename) save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
try: file.save(save_path) flash(f'File "{filename}" uploaded successfully!', 'success') flash(f'文件 "{filename}" 上传成功!', 'success') # You could return a success page or redirect # 你可以返回一个成功页面或重定向 # return render_template('upload_success.html', filename=filename) return redirect(url_for('show_upload_form')) except Exception as e: flash(f'An error occurred while saving the file: {e}', 'error') flash(f'保存文件时发生错误: {e}', 'error') return redirect(request.url) else: flash('File type not allowed', 'error') flash('不允许的文件类型', 'error') return redirect(request.url)
# If method is not POST (though route only allows POST here) # 如果方法不是 POST(尽管路由此处只允许 POST) return redirect(url_for('show_upload_form'))
if __name__ == '__main__': app.run(debug=True, port=5012) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口运行应用,导航到 /upload,选择一个文件(例如,图像或 PDF),然后点击上传。如果成功,文件将保存在 uploads 文件夹中,并闪现成功消息。
配置选项:
| 配置键 | 描述 |
|---|---|
UPLOAD_FOLDER | 定义上传文件应存储的目录。确保此目录存在且你的应用具有写入权限。 |
MAX_CONTENT_LENGTH | 指定整个请求负载允许的最大大小(以字节为单位),有效地限制了上传大小。有助于防止拒绝服务攻击。 |
Flask – 扩展 (Extensions)
Section titled “Flask – 扩展 (Extensions)”Flask 被称为“微框架”,因为其核心有意保持小巧,专注于必需功能:处理请求(通过 Werkzeug 的 WSGI)和渲染响应(通过 Jinja2 的模板)。它故意省略了数据库集成、表单处理、用户身份验证等功能。
这种极简主义方法提供了灵活性,允许开发者为其特定需求选择最佳工具。核心功能之外的功能通过 Flask 扩展添加。这些是 Python 包,它们与 Flask 应用无缝集成,提供特定功能。
查找和安装扩展
Section titled “查找和安装扩展”有各种各样的扩展可用。你通常可以通过在 PyPI(Python 包索引)中搜索 Flask- 加上你需要的功能来找到它们(例如,Flask-SQLAlchemy、Flask-WTF)。官方的 Pallets Projects 网站也维护着一个推荐扩展列表。
安装通常在项目的虚拟环境中使用 pip 进行:
pip install Flask-SomeExtensionName大多数扩展遵循一个通用模式:
- 导入: 从扩展包导入主类或必要的组件。
- 初始化: 创建扩展主类的实例,通常直接或稍后使用
init_app()方法将 Flaskapp对象传递给它。这会将扩展注册到你的应用中。 - 配置: 在
app.config中设置任何必需的配置变量(例如,数据库 URI、邮件服务器设置)。 - 利用: 使用扩展提供的功能(例如,定义数据库模型、创建表单、发送电子邮件)。
常用的 Flask 扩展
Section titled “常用的 Flask 扩展”以下是一些最受欢迎和必要的扩展(在后续部分将更详细地介绍):
- Flask-SQLAlchemy: 集成强大的 SQLAlchemy ORM (对象关系映射器) 用于数据库交互。
- Flask-Migrate: 使用 Alembic 处理数据库模式迁移,常与 Flask-SQLAlchemy 一起使用。
- Flask-WTF: 提供与 WTForms 的集成,用于安全地创建、验证和渲染 Web 表单(包括 CSRF 防护)。
- Flask-Login: 管理用户会话,处理登录、注销和记住用户功能。
- Flask-Mail: 简化从应用发送电子邮件的功能。
- Flask-RESTful / Flask-RESTX / Flask-API: 构建 RESTful API 的框架。
- Flask-Cors: 处理跨域资源共享 (CORS) 头,对于从不同域访问的 API 是必需的。
- Flask-Session: 提供具有各种后端(Redis、Memcached 等)的服务器端会话管理。
- Flask-Caching: 为应用添加缓存支持。
以下部分将深入探讨一些关键扩展的使用。
Flask – 邮件 (Mail)
Section titled “Flask – 邮件 (Mail)”发送电子邮件通知、确认或新闻通讯是 Web 应用中常见的需求。Flask-Mail 扩展通过提供一个到 SMTP (简单邮件传输协议) 服务器的接口,简化了将电子邮件发送功能集成到 Flask 应用中。
使用 pip 安装扩展:
pip install Flask-MailFlask-Mail 需要在 app.config 中进行配置设置,以便连接到你的电子邮件服务器。常见的设置包括:
| 序号 | 配置键 | 描述 |
|---|---|---|
| 1 | MAIL_SERVER | SMTP 电子邮件服务器的主机名或 IP 地址(例如,‘smtp.gmail.com’,‘smtp.mailgun.org’)。 |
| 2 | MAIL_PORT | SMTP 服务器的端口号(例如,587 用于 TLS,465 用于 SSL,25 用于未加密)。 |
| 3 | MAIL_USE_TLS | 布尔值:启用/禁用传输层安全 (TLS) 加密(通常与端口 587 一起使用)。 |
| 4 | MAIL_USE_SSL | 布尔值:启用/禁用安全套接字层 (SSL) 加密(通常与端口 465 一起使用)。 |
| 5 | MAIL_USERNAME | 用于通过 SMTP 服务器进行身份验证的用户名。 |
| 6 | MAIL_PASSWORD | 用于 SMTP 服务器的密码或应用专用密码。 |
| 7 | MAIL_DEFAULT_SENDER | 可选:如果创建消息时未指定发件人,则使用此默认电子邮件地址作为发件人(可以是元组:('Sender Name', 'sender@example.com'))。 |
| 8 | MAIL_MAX_EMAILS | 可选:单个连接中发送的最大电子邮件数量。默认为 None(无限制)。 |
| 9 | MAIL_SUPPRESS_SEND | 可选:在测试期间设置为 True 以阻止发送(app.testing=True 时)。默认为 app.testing。 |
| 10 | MAIL_ASCII_ATTACHMENTS | 可选:布尔值。如果为 True,则将附件文件名转换为 ASCII。默认为 False。 |
安全说明: 切勿将 MAIL_USERNAME 和 MAIL_PASSWORD 等敏感凭据直接硬编码在你的代码中。使用环境变量或安全的配置管理系统。
Gmail 特殊说明: 如果使用 Gmail,你可能需要为你的 Google 账户启用两步验证,并生成一个“应用专用密码”用作 MAIL_PASSWORD。出于安全原因,使用你的常规 Gmail 密码可能会被阻止。
Mail: 主类,用于管理连接和发送。使用 Flask 应用初始化它。Message: 表示单个电子邮件消息,包括主题、发件人、收件人、正文、附件等。
用法示例:
from flask import Flaskfrom flask_mail import Mail, Messageimport os
app = Flask(__name__)
# --- Configuration (Load from environment variables ideally!) ---# --- 配置(理想情况下从环境变量加载!)---app.config['MAIL_SERVER'] = os.environ.get('MAIL_SERVER', 'smtp.gmail.com')app.config['MAIL_PORT'] = int(os.environ.get('MAIL_PORT', 587))app.config['MAIL_USE_TLS'] = os.environ.get('MAIL_USE_TLS', 'true').lower() == 'true'app.config['MAIL_USE_SSL'] = os.environ.get('MAIL_USE_SSL', 'false').lower() == 'true'# IMPORTANT: Set these as environment variables# 重要:将这些设置为环境变量app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME') # e.g., 'your_email@gmail.com'# 例如,'你的邮箱@gmail.com'app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD') # e.g., your App Password# 例如,你的应用专用密码app.config['MAIL_DEFAULT_SENDER'] = os.environ.get('MAIL_DEFAULT_SENDER', app.config['MAIL_USERNAME'])# --------------------------------------------------------------
# Initialize the Mail extension# 初始化 Mail 扩展mail = Mail(app)
@app.route('/send-test-email')def send_email(): try: # Ensure username and password are set # 确保用户名和密码已设置 if not app.config['MAIL_USERNAME'] or not app.config['MAIL_PASSWORD']: return "Mail server credentials not configured.", 500 return "邮件服务器凭据未配置。", 500
# Create a message # 创建消息 msg = Message( subject="Test Email from Flask-Mail", subject="来自 Flask-Mail 的测试邮件", # sender is optional if MAIL_DEFAULT_SENDER is set # 如果设置了 MAIL_DEFAULT_SENDER,发件人是可选的 # sender=app.config['MAIL_DEFAULT_SENDER'], recipients=["recipient@example.com"] # Replace with a real recipient # 收件人列表,请替换为真实的收件人 ) msg.body = "This is a plain text email body sent via Flask-Mail." msg.body = "这是一封通过 Flask-Mail 发送的纯文本电子邮件正文。" # Optional: Add HTML body # 可选:添加 HTML 正文 # msg.html = "<h1>Hello</h1><p>This is an **HTML** email body.</p>" # msg.html = "<h1>你好</h1><p>这是一封**HTML**电子邮件正文。</p>"
# Optional: Add attachment # 可选:添加附件 # with app.open_resource("static/images/logo.png") as fp: # with app.open_resource("static/images/logo.png") 作为 fp: # msg.attach("logo.png", "image/png", fp.read()) # msg.attach("logo.png", "image/png", fp.read())
# Send the message # 发送消息 mail.send(msg)
return "Test email sent successfully! Check the recipient's inbox." return "测试邮件发送成功!请检查收件箱。"
except Exception as e: app.logger.error(f"Failed to send email: {e}") # Log the error # 记录错误 return f"Failed to send email: {e}", 500 return f"发送邮件失败: {e}", 500
if __name__ == '__main__': # Before running: export MAIL_USERNAME='your_email@gmail.com' # 运行前: export MAIL_USERNAME='你的邮箱@gmail.com' # export MAIL_PASSWORD='your_app_password' # export MAIL_PASSWORD='你的应用专用密码' app.run(debug=True, port=5013) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口步骤:
- 安装 Flask-Mail:
pip install Flask-Mail - 在
app.config中配置你的 SMTP 服务器详细信息(最好使用环境变量)。 - 初始化
Mail(app)。 - 在视图函数中,创建一个
Message对象,设置主题、收件人和正文/HTML。 - 使用
mail.send(msg)发送电子邮件。 - 运行应用并访问
/send-test-email。
请记住将 "recipient@example.com" 替换为你实际可以查看的电子邮件地址。
Flask – WTF (WTForms 集成)
Section titled “Flask – WTF (WTForms 集成)”手动创建 HTML 表单(<form>、<input> 等)并在服务器上验证提交的数据可能会重复且容易出错。你需要编写用于表单字段的 HTML,然后编写单独的 Python 代码从 request 对象解析和验证每个字段的数据。
WTForms 库提供了一种使用 Python 类定义表单的强大方式。Flask-WTF 扩展将 WTForms 无缝集成到 Flask 中,提供:
- 在 Python 中定义表单: 在 Python 类中定义表单字段、类型、标签和验证规则。
- HTML 渲染: 在 Jinja2 模板中轻松渲染表单字段(包括标签和错误消息)。
- 数据验证: 对表单字段应用各种内置或自定义验证规则。
- CSRF 防护: 自动生成和验证跨站请求伪造 (CSRF) 令牌,增强安全性(需要
app.secret_key)。 - 文件上传处理: 与文件上传集成。
安装 Flask-WTF(其中包含 WTForms 作为依赖项):
pip install Flask-WTF你可能还需要特定的验证器,例如用于电子邮件的:
pip install email-validator通过创建一个继承自 flask_wtf.FlaskForm 的类来定义表单。在类内部,使用从 wtforms 导入的字段类型将表单字段定义为类属性。
常见的 WTForms 字段类型:
| 序号 | 字段类型 | 描述 |
|---|---|---|
| 1 | StringField | 表示 <input type='text'>。最常见的文本输入。 |
| 2 | TextAreaField | 表示 <textarea>。 |
| 3 | PasswordField | 表示 <input type='password'>(屏蔽输入)。 |
| 4 | BooleanField | 表示 <input type='checkbox'>。 |
| 5 | IntegerField | 一个 StringField,带整数验证。 |
| 6 | DecimalField | 一个 StringField,带小数验证。 |
| 7 | DateField, DateTimeField | 日期和日期时间字段。 |
| 8 | RadioField | 表示一组 <input type='radio'> 按钮。 |
| 9 | SelectField | 表示 <select> 下拉菜单。 |
| 10 | FileField | 表示 <input type='file'> 用于文件上传(需要 Flask-WTF 集成)。 |
| 11 | SubmitField | 表示 <input type='submit'> 或 <button type='submit'>。 |
常见的 WTForms 验证器:
验证器作为列表传递给字段定义。
| 序号 | 验证器 | 描述 |
|---|---|---|
| 1 | DataRequired (或 InputRequired) | 检查字段是否不为空。 |
| 2 | Email | 验证输入是否看起来像电子邮件地址(需要 email-validator)。 |
| 3 | Length(min=-1, max=-1) | 验证输入字符串的长度。 |
| 4 | NumberRange(min=None, max=None) | 验证数字是否在指定范围内。 |
| 5 | URL | 验证输入是否为 URL。 |
| 6 | EqualTo('fieldname') | 将值与另一个字段进行比较(例如,用于密码确认)。 |
| 7 | Optional | 允许字段为空,如果为空则跳过后续验证器。 |
表单定义示例(forms.py):
from flask_wtf import FlaskFormfrom wtforms import StringField, TextAreaField, SelectField, SubmitField, PasswordField, BooleanFieldfrom wtforms.validators import DataRequired, Length, Email, EqualTo
class ContactForm(FlaskForm): name = StringField('Your Name', validators=[DataRequired(), Length(min=2, max=50)]) # 姓名,验证器:数据必填,长度介于 2-50 字符 email = StringField('Your Email', validators=[DataRequired(), Email()]) # 电子邮件,验证器:数据必填,邮箱格式 subject = StringField('Subject', validators=[DataRequired(), Length(max=100)]) # 主题,验证器:数据必填,最大长度 100 字符 message = TextAreaField('Message', validators=[DataRequired(), Length(min=10)]) # 消息,验证器:数据必填,最小长度 10 字符 subscribe = BooleanField('Subscribe to newsletter?') # 订阅新闻通讯?布尔字段 submit = SubmitField('Send Message') # 提交按钮 '发送消息'在 Flask 中使用表单
Section titled “在 Flask 中使用表单”-
实例化: 在你的视图函数中创建一个表单类的实例。
-
验证: 如果处理 POST 请求,调用
form.validate_on_submit()。这将检查请求是否为 POST 以及所有验证器是否通过。它还会自动处理 CSRF 令牌验证。 -
访问数据: 如果验证成功,通过
form.<field_name>.data访问提交的数据。 -
渲染: 将表单实例传递给你的模板。
应用示例(app.py):
from flask import Flask, render_template, request, flash, redirect, url_forfrom forms import ContactForm # Assuming forms.py is in the same directory# 假设 forms.py 在同一目录下import os
app = Flask(__name__)# IMPORTANT: Set a real secret key, preferably from environment variables# 重要:设置一个真实的密钥,最好从环境变量加载app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'you-should-really-change-this')# 你真的应该更改此默认密钥
@app.route('/contact', methods=['GET', 'POST'])def contact(): form = ContactForm() # Instantiate the form # 实例化表单
if form.validate_on_submit(): # Handles POST validation & CSRF # 处理 POST 验证和 CSRF # Access validated data # 访问验证后的数据 name = form.name.data email = form.email.data subject = form.subject.data message = form.message.data subscribe = form.subscribe.data
# Process the data (e.g., send email, save to database) # 处理数据(例如,发送电子邮件,保存到数据库) print(f"Received contact form: Name={name}, Email={email}, Subject={subject}, Subscribe={subscribe}") print(f"收到联系表单:姓名={name}, 电子邮件={email}, 主题={subject}, 订阅={subscribe}") print(f"Message:\n{message}") print(f"消息:\n{message}")
flash('Your message has been sent successfully!', 'success') flash('您的消息已成功发送!', 'success') return redirect(url_for('contact')) # Redirect after successful POST # POST 成功后重定向
# If GET request or validation failed, render the form template # 如果是 GET 请求或验证失败,渲染表单模板 # WTForms automatically adds errors to form.errors if validation fails # 如果验证失败,WTForms 会自动将错误添加到 form.errors return render_template('contact_form.html', title='Contact Us', form=form) return render_template('contact_form.html', title='联系我们', form=form)
if __name__ == '__main__': app.run(debug=True, port=5014) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口在模板中渲染表单(templates/contact_form.html):
Section titled “在模板中渲染表单(templates/contact_form.html):”Flask-WTF 使渲染变得容易。你可以单独渲染字段或使用快捷方式。
<!DOCTYPE html><html><head> <title>{{ title }}</title> <style> .form-field { margin-bottom: 1em; } .form-field label { display: block; margin-bottom: 0.2em; font-weight: bold; } .form-field input[type='text'], .form-field input[type='email'], .form-field textarea { width: 90%; max-width: 400px; padding: 0.4em; border: 1px solid #ccc; } .form-field textarea { min-height: 100px; } .form-field .error { color: red; font-size: 0.9em; margin-top: 0.2em; } .form-field ul { list-style: none; padding-left: 0; margin-top: 0.2em; } .flash-success { padding: 1em; background-color: lightgreen; border: 1px solid green; margin-bottom: 1em; } </style></head><body> <h1>{{ title }}</h1>
{# Display flashed messages (optional, but good practice) #} {# 显示闪现消息(可选,但推荐做法) #} {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} <div class="flash-{{ category }}">{{ message }}</div> {% endfor %} {% endif %} {% endwith %}
<form method="POST" action="{{ url_for('contact') }}" novalidate> {{ form.hidden_tag() }} {# Renders CSRF token and other hidden fields #} {# 渲染 CSRF 令牌和其他隐藏字段 #}
<div class="form-field"> {{ form.name.label }} {{ form.name(size=30) }} {# Render the input field #} {# 渲染输入字段 #} {% if form.name.errors %} <ul class="error"> {% for error in form.name.errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} </div>
<div class="form-field"> {{ form.email.label }} {{ form.email() }} {% if form.email.errors %} <ul class="error"> {% for error in form.email.errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} </div>
<div class="form-field"> {{ form.subject.label }} {{ form.subject() }} {% if form.subject.errors %} <ul class="error"> {% for error in form.subject.errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} </div>
<div class="form-field"> {{ form.message.label }} {{ form.message() }} {% if form.message.errors %} <ul class="error"> {% for error in form.message.errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} </div>
<div class="form-field"> {{ form.subscribe() }} {# Render checkbox #} {# 渲染复选框 #} {{ form.subscribe.label }} </div>
<div class="form-field"> {{ form.submit() }} {# Render submit button #} {# 渲染提交按钮 #} </div> </form></body></html>关键模板元素:
{{ form.hidden_tag() }}: 重要! 渲染 CSRF 令牌隐藏字段和表单中定义的其他隐藏字段。{{ form.<field_name>.label }}: 渲染字段的<label>标签。{{ form.<field_name>(**kwargs) }}: 渲染输入元素本身(例如,<input>、<textarea>)。你可以将 HTML 属性作为关键字参数传递(例如,form.name(size=30, class_='my-input'))。form.<field_name>.errors: 如果存在,则为该特定字段的验证错误列表。novalidate属性在<form>上:阻止默认的浏览器验证,允许 WTForms 验证优先并显示一致的错误消息。
运行应用,访问 /contact,并尝试提交无效或缺失数据的表单,以查看验证错误。
Flask – SQLite
Section titled “Flask – SQLite”SQLite 是一个轻量级的、基于文件的关系型数据库管理系统 (RDBMS)。与基于服务器的数据库(如 PostgreSQL 或 MySQL)不同,SQLite 将整个数据库存储在主机上的单个文件中。它通过 sqlite3 模块包含在 Python 的标准库中,使得在包括 Flask 在内的 Python 项目中开始使用数据库变得异常容易。
SQLite 的优点:
- 简单性: 无需安装或管理单独的数据库服务器进程。
- 可移植性: 整个数据库是一个单一文件,易于复制或移动。
- 零配置: 通常无需任何设置。
- 适合开发: 非常适合本地开发和测试。
- 适用于: 中低流量网站、嵌入式应用、原型设计。
SQLite 的缺点:
- 并发性限制: 在高写入并发(许多用户同时尝试写入数据)下表现不佳。
- 可伸缩性有限: 无法像客户端-服务器数据库那样跨多个服务器伸缩。
- 功能较少: 缺乏大型 RDBMS 中的一些高级功能。
- 不适合: 高流量应用、需要高写入性能或复杂多用户场景的应用。
在 Flask 中直接使用 sqlite3
Section titled “在 Flask 中直接使用 sqlite3”虽然 Flask-SQLAlchemy(下文介绍)提供了一个更高级别的 ORM 接口,但你可以直接使用 Python 内置的 sqlite3 模块与 SQLite 进行交互。
- 设置数据库和表(运行一次,例如在单独的
setup_db.py脚本中):
import sqlite3
DATABASE_FILE = 'mydatabase.db'
def setup(): conn = None try: conn = sqlite3.connect(DATABASE_FILE) print(f"Database '{DATABASE_FILE}' opened successfully.") print(f"数据库 '{DATABASE_FILE}' 打开成功。")
conn.execute(''' CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, address TEXT, city TEXT, pincode TEXT ); ''') print("Table 'students' created or already exists.") print("表 'students' 已创建或已存在。")
except sqlite3.Error as e: print(f"Database error: {e}") print(f"数据库错误:{e}") finally: if conn: conn.close() print("Database connection closed.") print("数据库连接已关闭。")
if __name__ == '__main__': setup()运行 python setup_db.py 来创建 mydatabase.db 文件和 students 表。
- Flask 应用(
app.py):
import sqlite3from flask import Flask, render_template, request, redirect, url_for, flash, g
DATABASE_FILE = 'mydatabase.db'
app = Flask(__name__)app.secret_key = 'sqlite-example-key'# sqlite 示例密钥
# Helper function to get database connection# 获取数据库连接的辅助函数def get_db(): db = getattr(g, '_database', None) if db is None: try: db = g._database = sqlite3.connect(DATABASE_FILE) # Return rows as dictionary-like objects # 将行作为类似字典的对象返回 db.row_factory = sqlite3.Row except sqlite3.Error as e: flash(f"Database connection error: {e}", "error") flash(f"数据库连接错误:{e}", "error") return None return db
# Close database connection automatically after each request# 在每个请求后自动关闭数据库连接@app.teardown_appcontextdef close_connection(exception): db = getattr(g, '_database', None) if db is not None: db.close()
# Route to display the form for adding a new student# 显示添加新学生表单的路由@app.route('/add-student')def add_student_form(): return render_template('student_form_sqlite.html') # Assume this template exists # 假设此模板存在
# Route to handle adding the student record# 处理添加学生记录的路由@app.route('/add-record', methods=['POST'])def add_student_record(): if request.method == 'POST': db = get_db() if not db: return redirect(url_for('student_list')) # Redirect if DB connection failed # 如果数据库连接失败,重定向
try: name = request.form['nm'] addr = request.form['add'] city = request.form['city'] pin = request.form['pin']
cursor = db.cursor() # Use placeholders (?) to prevent SQL injection # 使用占位符 (?) 防止 SQL 注入 cursor.execute("INSERT INTO students (name, address, city, pincode) VALUES (?, ?, ?, ?)", (name, addr, city, pin)) db.commit() flash("Record successfully added", "success") flash("记录成功添加", "success") except sqlite3.Error as e: db.rollback() # Roll back changes on error # 在错误时回滚更改 flash(f"Error in insert operation: {e}", "error") flash(f"插入操作错误:{e}", "error") except KeyError as e: flash(f"Missing form field: {e}", "error") flash(f"缺失表单字段:{e}", "error") finally: # The connection is closed by @app.teardown_appcontext # 连接由 @app.teardown_appcontext 关闭 return redirect(url_for('student_list'))
# Redirect if not POST # 如果不是 POST,则重定向 return redirect(url_for('add_student_form'))
# Route to display the list of all students# 显示所有学生列表的路由@app.route('/list')def student_list(): db = get_db() if not db: return render_template('student_list_sqlite.html', rows=[]) # Pass empty list on error # 在错误时传递空列表
rows = [] # Default to empty list # 默认为空列表 try: cursor = db.cursor() cursor.execute("SELECT id, name, address, city, pincode FROM students") rows = cursor.fetchall() # Fetch all results # 获取所有结果 except sqlite3.Error as e: flash(f"Error fetching records: {e}", "error") flash(f"获取记录错误:{e}", "error") finally: # Connection closed by teardown function # 连接由 teardown 函数关闭 return render_template('student_list_sqlite.html', rows=rows)
# Simple home page route# 简单主页路由@app.route('/')def home(): return render_template('home_sqlite.html') # Assume this exists # 假设此模板存在
# --- Assume Templates Exist ---# --- 假设模板存在 ---# templates/home_sqlite.html: Links to /add-student and /list# templates/home_sqlite.html: 链接到 /add-student 和 /list# templates/student_form_sqlite.html: Form with fields nm, add, city, pin, POSTing to /add-record# templates/student_form_sqlite.html: 包含 nm, add, city, pin 字段的表单,POST 到 /add-record# templates/student_list_sqlite.html: Displays rows in an HTML table# templates/student_list_sqlite.html: 在 HTML 表格中显示行# ------------------------------
if __name__ == '__main__': app.run(debug=True, port=5015) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口Flask 应用中的关键概念:
get_db(): 用于建立数据库连接的辅助函数。它使用 Flask 的g对象(每个请求的全局命名空间)来存储连接,确保每个请求只打开一个连接。@app.teardown_appcontext: 一个装饰器,注册一个函数在每个请求后运行,无论成功或失败。我们在这里使用它来可靠地关闭数据库连接 (close_connection)。db.row_factory = sqlite3.Row: 配置连接以返回可以按列名(类似字典)而不是仅按索引访问的行。db.cursor(): 创建执行 SQL 命令所需的游标对象。cursor.execute(sql, params): 执行 SQL 查询。至关重要,使用占位符 (?) 并将参数作为元组传递,以防止 SQL 注入漏洞。切勿使用 f-strings 或字符串格式化直接将数据插入到 SQL 查询中。db.commit(): 将当前会话中进行的更改(添加、更新、删除)保存到数据库。db.rollback(): 回滚自上次提交以来所做的更改,通常用于错误处理。cursor.fetchall(): 检索 SELECT 查询产生的所有行。
虽然直接使用 sqlite3 可以工作,但这需要编写原始 SQL 并手动管理连接。对于更复杂的应用,通过 Flask-SQLAlchemy 扩展使用 ORM(如 SQLAlchemy)通常更高效且更易于维护。
Flask – SQLAlchemy
Section titled “Flask – SQLAlchemy”编写原始 SQL 查询(如 SQLite 部分所示)可能会变得繁琐且容易出错,尤其是在大型应用中。对象关系映射器 (ORM) 提供了一个更高级别的抽象,允许你使用 Python 对象和方法与数据库交互,而不是编写 SQL。
SQLAlchemy 是 Python 最流行、最强大的 ORM 工具包。Flask-SQLAlchemy 是一个 Flask 扩展,它将 SQLAlchemy 无缝集成到你的 Flask 应用中,简化了配置和会话管理。
什么是 ORM (Object-Relational Mapping)?
Section titled “什么是 ORM (Object-Relational Mapping)?”ORM 是一种映射技术:
- 数据库表到 Python 类(称为模型 - Models)。
- 表列到这些类的属性。
- 表中的行到这些类的实例(对象)。
这使你可以通过操作 Python 对象来执行数据库操作(创建、读取、更新、删除 - CRUD)。
示例:
# Instead of:# 替代方法:# cursor.execute("INSERT INTO students (name, city) VALUES (?, ?)", ('Alice', 'London'))
# You might write (using an ORM):# 你可能会写(使用 ORM):new_student = Student(name='Alice', city='London')db.session.add(new_student)db.session.commit()使用 Flask-SQLAlchemy 的好处:
- 效率: 与原始 SQL 相比,编写更少的样板代码。
- 可读性: 与对象交互的 Python 代码通常更容易理解。
- 可维护性: 数据库模式的更改通常可以在模型定义中管理。
- 数据库无关性: SQLAlchemy 支持各种数据库后端(PostgreSQL、MySQL、SQLite 等),更易于切换数据库。
- 会话管理: Flask-SQLAlchemy 在请求上下文中自动处理数据库会话的建立和清理。
- 集成: 与 Flask-Migrate(用于数据库模式迁移)等其他 Flask 扩展配合良好。
- 安装:
pip install Flask-SQLAlchemy根据你使用的数据库(SQLite 除外),你可能需要安装一个数据库驱动:
# For PostgreSQL:# 对于 PostgreSQL:pip install psycopg2-binary# For MySQL:# 对于 MySQL:pip install mysqlclient # or PyMySQL# 或 PyMySQL- 配置数据库 URI: 在你的 Flask 应用配置中设置
SQLALCHEMY_DATABASE_URI。这告诉 SQLAlchemy 你的数据库在哪里。
app = Flask(__name__)
# Examples:# 示例:# SQLite (relative path)# SQLite(相对路径)# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydatabase.db'# SQLite (absolute path - note the extra slashes)# SQLite(绝对路径 - 注意额外的斜杠)# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////path/to/your/mydatabase.db'
# PostgreSQL# app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@host:port/database_name'
# MySQL# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlclient://username:password@host:port/database_name'
# Use environment variables for production!# 生产环境中使用环境变量!uri = os.environ.get('DATABASE_URL', 'sqlite:///default_dev.db')if uri.startswith("postgres://"): # Heroku adjustment # Heroku 调整 uri = uri.replace("postgres://", "postgresql://", 1)app.config['SQLALCHEMY_DATABASE_URI'] = uri
# Optional: Disable modification tracking for better performance if not needed# 可选:如果不需要,禁用修改跟踪以提高性能app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False- 初始化: 创建
SQLAlchemy类的实例,并传递你的 Flask 应用对象。
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)定义模型 (Models)
Section titled “定义模型 (Models)”模型是继承自 db.Model 的 Python 类。使用 db.Column 定义的每个类属性表示数据库表中的一个列。
class Student(db.Model): # Table name defaults to 'student' (lowercase class name) # 表名默认为 'student'(类名小写) # __tablename__ = 'students_table' # Optional: specify custom table name # 可选:指定自定义表名
# Define columns # 定义列 id = db.Column(db.Integer, primary_key=True) # Auto-incrementing primary key # 自动递增主键 name = db.Column(db.String(100), nullable=False) # Required string, max 100 chars # 必需字符串,最大 100 个字符 city = db.Column(db.String(50)) address = db.Column(db.String(200)) pincode = db.Column(db.String(10))
# Optional: Define an __init__ method for easier object creation # 可选:定义 __init__ 方法以便更轻松地创建对象 # Not strictly required by SQLAlchemy, but convenient # SQLAlchemy 并非严格要求,但很方便 def __init__(self, name, city, address, pincode): self.name = name self.city = city self.address = address self.pincode = pincode
# Optional: Define a __repr__ method for helpful debugging output # 可选:定义 __repr__ 方法以获得有用的调试输出 def __repr__(self): return f'<Student {self.name} (ID: {self.id})>' return f'<学生 {self.name} (ID: {self.id})>'常见的 db.Column 类型包括 db.Integer、db.String(length)、db.Text、db.Float、db.Boolean、db.Date、db.DateTime。
常见的 db.Column 参数包括 primary_key=True、nullable=False(必需字段)、unique=True、default=value、index=True。
在使用模型之前,数据库中需要存在相应的表。你可以使用 db.create_all() 创建它们。
# Typically run once, or use Flask-Migrate for managing changes# 通常运行一次,或使用 Flask-Migrate 管理更改with app.app_context(): # Need application context # 需要应用上下文 db.create_all() print("Database tables created (if they didn't exist).") print("数据库表已创建(如果它们不存在)。")注意: db.create_all() 只创建尚不存在的表。如果你更改了模型,它不会更新现有表。对于管理现有模式的更改,请使用 Flask-Migrate 等迁移工具。
基本 CRUD 操作
Section titled “基本 CRUD 操作”SQLAlchemy 操作在会话 (session) 内执行。Flask-SQLAlchemy 为你管理此会话,使其可用为 db.session。
- 创建(插入):
student1 = Student(name='Charlie', city='Chicago', address='1 Main St', pincode='60606')db.session.add(student1)db.session.commit() # Save changes to the database# 将更改保存到数据库
- 读取(查询):
# Get all students# 获取所有学生all_students = Student.query.all()# Get student by primary key (ID)# 根据主键(ID)获取学生student_by_id = Student.query.get(1) # Returns one student or None# 返回一个学生或 None# Filter results (example: find students in Chicago)# 过滤结果(示例:查找芝加哥的学生)chicago_students = Student.query.filter_by(city='Chicago').all()# More complex filter (example: name starts with 'A')# 更复杂的过滤器(示例:名字以 'A' 开头)a_students = Student.query.filter(Student.name.startswith('A')).all()# Get the first result of a filter# 获取过滤结果中的第一个first_chicago = Student.query.filter_by(city='Chicago').first()
- 更新:
student_to_update = Student.query.get(1)# 要更新的学生if student_to_update:student_to_update.city = 'New York'db.session.commit() # Commit the change# 提交更改
- 删除:
student_to_delete = Student.query.get(2)# 要删除的学生if student_to_delete:db.session.delete(student_to_delete)db.session.commit() # Commit the deletion# 提交删除
db.session.commit() 将当前会话中进行的更改(添加、更新、删除)持久化到数据库。db.session.rollback() 可用于丢弃更改。
Flask 应用示例
Section titled “Flask 应用示例”我们来使用 Flask-SQLAlchemy 修改学生应用。
-
设置: 安装
Flask-SQLAlchemy。配置SQLALCHEMY_DATABASE_URI。 -
模型(
models.py- 可选的单独文件):
# Assuming db object is initialized elsewhere and imported# 假设 db 对象已在别处初始化并导入# from app import db
# class Student(db.Model): ... (definition as above) ...# class Student(db.Model): ... (定义如上) ...- Flask 应用(
app.py):
from flask import Flask, request, flash, url_for, redirect, render_templatefrom flask_sqlalchemy import SQLAlchemyimport os
app = Flask(__name__)
# --- Configuration ---# --- 配置 ---uri = os.environ.get('DATABASE_URL', 'sqlite:///students_sqlalchemy.db')# 数据库 URI,默认使用 SQLite 文件if uri.startswith("postgres://"): uri = uri.replace("postgres://", "postgresql://", 1)app.config['SQLALCHEMY_DATABASE_URI'] = uriapp.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Falseapp.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'sqlalchemy-so-secret')# SQLAlchemy 秘密密钥# --------------------
db = SQLAlchemy(app)
# --- Model Definition ---# --- 模型定义 ---class Student(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) city = db.Column(db.String(50)) address = db.Column(db.String(200)) pincode = db.Column(db.String(10))
def __init__(self, name, city, address, pincode): self.name = name self.city = city self.address = address self.pincode = pincode# ----------------------
@app.route('/')def show_all_students(): try: students = Student.query.all() # 查询所有学生 except Exception as e: # Handle potential database errors during query # 处理查询期间潜在的数据库错误 flash(f"Error fetching students: {e}", "error") flash(f"获取学生时发生错误:{e}", "error") students = [] return render_template('show_all_students.html', students=students)
@app.route('/new-student', methods=['GET', 'POST'])def add_new_student(): if request.method == 'POST': # Simple validation (could use Flask-WTF for robust validation) # 简单验证(可以使用 Flask-WTF 进行健壮验证) if not request.form['name'] or not request.form['city'] or not request.form['address']: flash('Please enter all required fields (Name, City, Address)', 'error') flash('请输入所有必填字段(姓名、城市、地址)', 'error') else: try: new_stud = Student(request.form['name'], request.form['city'], request.form['address'], request.form['pincode'])
db.session.add(new_stud) db.session.commit() flash('Record was successfully added', 'success') flash('记录已成功添加', 'success') return redirect(url_for('show_all_students')) except Exception as e: db.session.rollback() flash(f"Error adding record: {e}", "error") flash(f"添加记录时发生错误:{e}", "error")
# Render form for GET request or if POST validation/commit failed # 对于 GET 请求或 POST 验证/提交失败时,渲染表单 return render_template('new_student_form.html')
# --- Templates ---# --- 模板 ---# templates/show_all_students.html: Displays student data from the 'students' list in a table.# templates/show_all_students.html: 在表格中显示 'students' 列表中的学生数据。# Iterate: {% for student in students %} ... {{ student.name }} ... {% endfor %}# 迭代: {% for student in students %} ... {{ student.name }} ... {% endfor %}# templates/new_student_form.html: Form with fields name, city, address, pincode POSTing to /new-student# templates/new_student_form.html: 包含 name, city, address, pincode 字段的表单,POST 到 /new-student# -----------------
if __name__ == '__main__': with app.app_context(): db.create_all() # Create tables if they don't exist # 如果表不存在则创建表 app.run(debug=True, port=5016) # Use a different port if 5000 is busy # 如果 5000 端口被占用,使用不同的端口此版本将原始 SQL 和手动连接处理替换为 SQLAlchemy 的面向对象方法,通常会带来更简洁、更易于维护的数据库交互代码。
Flask – 部署 (Deployment)
Section titled “Flask – 部署 (Deployment)”Flask 内置的 Web 服务器(app.run())仅用于开发目的。它便于测试和调试,但并非为生产负载下的性能、安全性或稳定性而构建。要部署你的 Flask 应用供实际用户使用,你需要使用生产就绪的设置。
为什么不能在生产环境中使用开发服务器?
Section titled “为什么不能在生产环境中使用开发服务器?”- 默认单线程: 一次只能处理一个请求,在高负载下性能低下。
- 未优化: 缺乏专用 WSGI 服务器的效率和健壮性。
- 安全风险: 特别是如果意外启用了
debug=True,会暴露主要安全漏洞。
生产部署策略
Section titled “生产部署策略”Python Web 应用的典型生产部署涉及多个组件:
客户端 (浏览器) <--> Web 服务器 (例如,Nginx, Apache) <--> WSGI 服务器 (例如,Gunicorn, uWSGI) <--> 你的 Flask 应用- 你的 Flask 应用: 你的 Python 代码。
- WSGI 服务器: 专门理解 WSGI 规范的服务器。它运行你的 Flask 应用的多个实例(worker),管理请求,并与应用高效通信。流行的选择:
- Gunicorn: 纯 Python 实现,广泛使用,配置简单。
- uWSGI: 配置更复杂但性能可能更高,使用 C 编写。
- Web 服务器(反向代理): 位于 WSGI 服务器之前。它处理传入的客户端连接,直接服务静态文件(比 Python 快得多),管理 HTTPS/SSL 终止,执行负载均衡(如果需要),并将动态请求转发给 WSGI 服务器。流行的选择:
- Nginx: 高性能、事件驱动,非常流行。
- Apache: 成熟、功能丰富,常与
mod_wsgi(现在较少见)一起使用或作为反向代理。
你有几种部署位置和方式的选项:
1. Platform-as-a-Service (PaaS)
Section titled “1. Platform-as-a-Service (PaaS)”PaaS 提供商抽象了大部分服务器管理工作。你通常提供代码(通常通过 Git)和一个配置文件(例如 Heroku 的 Procfile 或 Google App Engine 的 app.yaml),平台负责配置服务器、部署代码、伸缩等。
示例:
- Heroku: 非常流行,易于上手,文档齐全。
- PythonAnywhere: 专门专注于 Python 托管,界面简单。
- Render: 现代 PaaS,为 Web 服务和数据库提供免费层级。
- Google App Engine: Google Cloud 的 PaaS 产品。
- AWS Elastic Beanstalk: AWS 的 PaaS 产品。
- Azure App Service: Microsoft Azure 的 PaaS 产品。
PaaS 通常是将 Flask 应用部署上线的快捷方式。
2. Virtual Private Servers (VPS) / 云计算实例
Section titled “2. Virtual Private Servers (VPS) / 云计算实例”这种方法为你提供对虚拟服务器的完全控制。你负责安装操作系统、安全更新、Web 服务器 (Nginx/Apache)、WSGI 服务器 (Gunicorn/uWSGI)、Python、你的应用代码以及任何数据库。
示例:
- DigitalOcean Droplets
- Linode
- AWS EC2
- Google Compute Engine
- Azure Virtual Machines
这需要更多的系统管理知识,但提供了最大的灵活性。
3. 容器 (Docker)
Section titled “3. 容器 (Docker)”Docker 允许你将 Flask 应用、其依赖项、Python 运行时和 WSGI 服务器打包到标准化的容器镜像中。然后可以在不同环境(开发、测试、生产)中一致地运行此镜像。
优势:
- 一致性: 确保环境在任何地方都相同。
- 隔离: 容器彼此隔离运行。
- 可移植性: 易于在主机或云提供商之间移动容器。
- 可伸缩性: 容器编排平台(如 Kubernetes、Docker Swarm 或支持容器的 PaaS 服务,如 Google Cloud Run、AWS Fargate、Heroku Docker Deploy)使得伸缩变得容易。
部署通常涉及构建 Docker 镜像,并将其部署到容器注册表,然后再部署到容器托管平台。
示例:使用 Gunicorn 运行(本地)
Section titled “示例:使用 Gunicorn 运行(本地)”即使在部署到服务器之前,你也可以在本地使用生产 WSGI 服务器(如 Gunicorn)测试你的应用。
- 安装 Gunicorn:
pip install gunicorn- 运行 Gunicorn:
# Syntax: gunicorn [OPTIONS]MODULE_NAME:VARIABLE_NAME# 语法: gunicorn [OPTIONS]MODULE_NAME:VARIABLE_NAME# Assuming your Flask app instance is named 'app' in 'my_app.py'# 假设你的 Flask 应用实例在 'my_app.py' 中命名为 'app'gunicorn --workers 4 --bind 0.0.0.0:8000 my_app:app--workers 4: 指定处理请求的工作进程数量(根据服务器的 CPU 核心数调整,通常每个核心 2-4 个)。--bind 0.0.0.0:8000: 告诉 Gunicorn 监听所有可用网络接口的 8000 端口。my_app:app: 指定 Python 模块 (my_app.py) 以及该模块中的 Flask 应用实例变量 (app)。
这模拟了你的应用在生产环境中的 WSGI 服务器下如何运行(尽管通常位于 Nginx/Apache 后面)。
选择哪种部署方法取决于你的技术经验、预算、伸缩需求和期望的控制级别。