Flask
1. 什么是Flask?
Flask 是一个用 Python 编写的轻量级 Web 应用框架。它被设计为易于使用、灵活且可扩展,是构建 Web 应用程序和 API 的绝佳选择,尤其适合初学者和小型到中型项目。
1.1 生活中的类比
想象一下,你想建一个网站,比如个人博客或者在线商店。
传统方法 :
- 需要学习复杂的Web服务器配置
- 需要处理HTTP协议的细节
- 需要自己实现路由、模板等功能
- 开发效率低,容易出错
使用Flask :
- 几行代码就能创建一个Web应用
- Flask帮你处理了底层的复杂性
- 专注于业务逻辑,而不是技术细节
- 开发效率高,代码简洁
Flask 就像是一个"工具箱",提供了构建Web应用所需的基本工具,让你可以快速搭建网站。
1.2 Flask的核心定位
Flask 的定位是:一个轻量级、灵活、易扩展的Web应用框架 。
核心特点 :
- 微框架 :核心简单,只提供最基本的功能
- 高度可扩展 :通过扩展库添加更多功能
- 灵活自由 :没有强制的项目结构
- 易于学习 :对初学者非常友好
1.3 为什么选择Flask?
vs 传统Web开发 :
- 传统方式需要处理大量底层细节,Flask封装了这些复杂性
- 传统方式开发效率低,Flask可以快速开发
- 传统方式代码冗长,Flask代码简洁优雅
2. Flask核心概念
2.1 应用对象(Flask)
Flask应用的核心是应用对象,它是整个Web应用的入口。
创建应用对象 :
# 导入Flask类
# Flask类用于创建Web应用实例
from flask import Flask
# 创建Flask应用实例
# __name__用于确定应用的根路径,Flask会根据它找到模板和静态文件
app = Flask(__name__)
# 打印应用对象信息(用于验证)
print(f"Flask应用已创建: {app}")
print(f"应用名称: {app.name}")
应用对象的作用 :
- 配置路由(URL到函数的映射)
- 注册扩展(如数据库、表单等)
- 管理应用配置
- 处理请求和响应
2.2 路由(Route)
路由是将URL映射到Python函数的过程。当用户访问某个URL时,Flask会调用对应的函数。
基本路由 :
# 导入Flask类
from flask import Flask
# 创建Flask应用实例
app = Flask(__name__)
# 定义根路由
# @app.route('/')表示当用户访问网站根目录时
# 装饰器@app.route()将URL '/' 映射到下面的函数
@app.route('/')
def index():
# 返回响应内容(HTML字符串)
return '<h1>欢迎访问首页!</h1>'
# 定义另一个路由
# '/about'表示访问 http://localhost:5000/about 时
@app.route('/about')
def about():
# 返回关于页面的内容
return '<h1>关于我们</h1><p>这是一个Flask应用示例。</p>'
# 运行应用
if __name__ == '__main__':
# 启动开发服务器
# host='0.0.0.0'表示监听所有网络接口
# port=5000表示使用5000端口
# debug=True开启调试模式
app.run(host='0.0.0.0', port=5000, debug=True)
动态路由 :
# 导入Flask类
from flask import Flask
# 创建Flask应用实例
app = Flask(__name__)
# 定义动态路由
# <name>是URL参数,会被传递给函数
# 访问 /user/张三 时,name='张三'
@app.route('/user/<name>')
def show_user(name):
# 使用URL参数生成响应
return f'<h1>你好,{name}!</h1>'
# 定义带类型的动态路由
# <int:post_id>表示post_id必须是整数
# 访问 /post/123 时,post_id=123(整数)
@app.route('/post/<int:post_id>')
def show_post(post_id):
# 使用整数参数
return f'<h1>文章 #{post_id}</h1>'
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
2.3 视图函数
视图函数是处理请求的核心,它接收请求,处理业务逻辑,然后返回响应。
视图函数的特点 :
- 必须返回一个响应(字符串、HTML、JSON等)
- 可以访问请求数据(如表单数据、URL参数等)
- 可以调用其他函数或访问数据库
示例 :
# 导入Flask类和request对象
# request对象包含客户端发送的请求信息
from flask import Flask, request
# 创建Flask应用实例
app = Flask(__name__)
# 定义视图函数
# 这个函数处理根路径的请求
@app.route('/')
def index():
# 返回简单的HTML响应
return '<h1>首页</h1><p>欢迎访问!</p>'
# 定义带参数的视图函数
# 从URL中获取参数
@app.route('/greet/<name>')
def greet(name):
# 使用URL参数生成响应
return f'<h1>你好,{name}!</h1>'
# 定义处理GET和POST请求的视图函数
# methods参数指定允许的HTTP方法
@app.route('/form', methods=['GET', 'POST'])
def handle_form():
# 判断请求方法
if request.method == 'POST':
# 如果是POST请求,获取表单数据
# request.form是包含表单数据的字典
username = request.form.get('username', '')
return f'<h1>收到表单数据:{username}</h1>'
else:
# 如果是GET请求,返回表单页面
return '''
<form method="post">
<label>用户名:</label>
<input type="text" name="username">
<button type="submit">提交</button>
</form>
'''
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
2.4 请求对象(request)
request对象包含客户端发送的所有请求信息,如表单数据、URL参数、请求头等。
获取请求数据 :
# 导入Flask类和request对象
from flask import Flask, request
# 创建Flask应用实例
app = Flask(__name__)
# 定义处理表单的路由
# methods=['GET', 'POST']表示支持GET和POST两种请求方法
@app.route('/login', methods=['GET', 'POST'])
def login():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
# request.form是包含表单数据的字典
username = request.form.get('username', '')
password = request.form.get('password', '')
# 简单的验证逻辑(实际应用中应该连接数据库)
if username == 'admin' and password == '123456':
return f'<h1>登录成功!</h1><p>欢迎,{username}!</p>'
else:
return '<h1>登录失败</h1><p>用户名或密码错误</p>'
else:
# GET请求,显示登录表单
return '''
<!DOCTYPE html>
<html>
<head>
<title>登录</title>
</head>
<body>
<h1>用户登录</h1>
<form method="post">
<label>用户名:</label><br>
<input type="text" name="username" required><br><br>
<label>密码:</label><br>
<input type="password" name="password" required><br><br>
<button type="submit">登录</button>
</form>
</body>
</html>
'''
# 定义获取URL参数的路由
# 访问 /search?q=Flask 时,q='Flask'
@app.route('/search')
def search():
# 获取URL参数
# request.args是包含URL参数的字典
query = request.args.get('q', '')
if query:
return f'<h1>搜索结果</h1><p>搜索关键词:{query}</p>'
else:
return '<h1>请输入搜索关键词</h1>'
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
2.5 模板渲染(render_template)
直接在Python代码中拼接HTML字符串很繁琐,Flask使用Jinja2模板引擎来分离Python代码和HTML。
为什么需要模板?
- HTML代码和Python代码分离,更易维护
- 可以复用HTML结构(如导航栏、页脚)
- 支持变量、循环、条件判断等
使用模板 :
首先创建模板文件 templates/hello.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>问候页面</title>
</head>
<body>
<h1>你好,{{ name }}!</h1>
<p>欢迎访问我们的网站。</p>
</body>
</html>
然后使用模板:
# 导入Flask类和render_template函数
# render_template用于渲染HTML模板
from flask import Flask, render_template
# 创建Flask应用实例
app = Flask(__name__)
# 定义路由,使用模板渲染
# <name>是URL参数
@app.route('/hello/<name>')
def hello(name):
# 渲染模板
# 'hello.html'是模板文件名(在templates目录下)
# name=name将Python变量传递给模板
return render_template('hello.html', name=name)
# 定义带列表数据的路由
@app.route('/users')
def show_users():
# 定义用户列表
users = ['张三', '李四', '王五']
# 将用户列表传递给模板
return render_template('users.html', users=users)
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
创建 templates/users.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<ul>
{% for user in users %}
<li>{{ user }}</li>
{% endfor %}
</ul>
</body>
</html>
3. 博客系统
让我们创建一个完整的博客系统示例,包含首页、文章列表和文章详情页。
3.1 项目结构
首先创建项目目录结构:
Windows 系统:
# 创建项目目录
mkdir my_blog
cd my_blog
# 创建templates目录
mkdir templates
macOS 系统:
# 创建项目目录
mkdir my_blog
cd my_blog
# 创建templates目录
mkdir templates
项目结构:
my_blog/
│ app.py # 主应用文件
│
└───templates/ # HTML模板目录
base.html # 基础模板
index.html # 首页模板
post.html # 文章详情页模板
3.2 创建主应用文件
创建 app.py:
# 导入Flask类和render_template函数
# Flask用于创建Web应用
# render_template用于渲染HTML模板
from flask import Flask, render_template
# 创建Flask应用实例
# __name__用于确定应用的根路径
app = Flask(__name__)
# 模拟博客文章数据
# 在实际应用中,这些数据通常来自数据库
posts = [
{
'id': 1, # 文章ID
'title': '第一篇文章', # 文章标题
'content': '这是我的第一篇博客文章内容。Flask是一个很棒的Web框架!', # 文章内容
'author': '小明', # 作者
'date': '2024-01-01' # 发布日期
},
{
'id': 2,
'title': '学习Flask',
'content': '今天学习了Flask的路由和模板,非常有趣!Flask让Web开发变得简单。',
'author': '小红',
'date': '2024-01-02'
},
{
'id': 3,
'title': 'Flask模板系统',
'content': 'Jinja2模板引擎非常强大,支持变量、循环、条件判断等功能。',
'author': '小刚',
'date': '2024-01-03'
}
]
# 定义首页路由
# '/'表示网站根目录
@app.route('/')
def index():
# 渲染首页模板
# 'index.html'是模板文件名
# posts=posts将文章列表传递给模板
return render_template('index.html', posts=posts)
# 定义文章详情页路由
# <int:post_id>表示URL参数,必须是整数
# 例如:访问 /post/1 时,post_id=1
@app.route('/post/<int:post_id>')
def show_post(post_id):
# 根据ID查找文章
# next()函数用于从迭代器中获取第一个匹配的元素
# 如果没找到,返回None
post = next((post for post in posts if post['id'] == post_id), None)
# 如果文章不存在,返回404错误
if post is None:
# 返回错误信息和状态码
return '<h1>文章未找到</h1><p>抱歉,您访问的文章不存在。</p>', 404
# 渲染文章详情页模板
# 'post.html'是模板文件名
# post=post将文章对象传递给模板
return render_template('post.html', post=post)
# 运行应用
if __name__ == '__main__':
# 启动开发服务器
# debug=True开启调试模式
app.run(debug=True)
3.3 创建基础模板
创建 templates/base.html(基础模板,定义公共结构):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 页面标题,使用Jinja2的block语法,子模板可以覆盖 -->
<title>{% block title %}我的迷你博客{% endblock %}</title>
<style>
/* 简单的CSS样式 */
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
header {
background-color: #4CAF50;
color: white;
padding: 20px;
text-align: center;
margin-bottom: 20px;
}
header a {
color: white;
text-decoration: none;
}
main {
padding: 20px;
}
article {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
article h3 {
margin-top: 0;
}
article a {
color: #4CAF50;
text-decoration: none;
}
article a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<!-- 页头 -->
<header>
<!-- url_for()函数用于生成URL,'index'是视图函数名 -->
<h1><a href="{{ url_for('index') }}">我的博客</a></h1>
</header>
<!-- 主要内容区域 -->
<!-- block content是内容块,子模板可以填充内容 -->
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
3.4 创建首页模板
创建 templates/index.html(首页模板,继承自base.html):
<!-- 继承基础模板 -->
<!-- extends指令表示此模板继承自base.html -->
{% extends "base.html" %}
<!-- 覆盖title块,设置页面标题 -->
{% block title %}首页 - 我的迷你博客{% endblock %}
<!-- 填充content块,定义页面主要内容 -->
{% block content %}
<h2>所有文章</h2>
<!-- 使用for循环遍历文章列表 -->
<!-- posts是Python传递过来的变量 -->
{% for post in posts %}
<article>
<!-- 文章标题,链接到文章详情页 -->
<!-- url_for()生成URL,'show_post'是视图函数名,post_id是参数 -->
<h3><a href="{{ url_for('show_post', post_id=post.id) }}">{{ post.title }}</a></h3>
<!-- 显示作者和日期 -->
<p><strong>作者:</strong>{{ post.author }} | <strong>日期:</strong>{{ post.date }}</p>
<!-- 显示文章内容的前100个字符 -->
<!-- [:100]是Python的切片语法,获取前100个字符 -->
<p>{{ post.content[:100] }}...</p>
</article>
<!-- 分隔线 -->
<hr>
{% endfor %}
<!-- 如果没有文章,显示提示信息 -->
{% if not posts %}
<p>暂无文章。</p>
{% endif %}
{% endblock %}
3.5 创建文章详情页模板
创建 templates/post.html(文章详情页模板):
<!-- 继承基础模板 -->
{% extends "base.html" %}
<!-- 设置页面标题为文章标题 -->
{% block title %}{{ post.title }} - 我的迷你博客{% endblock %}
<!-- 定义页面内容 -->
{% block content %}
<article>
<!-- 显示文章标题 -->
<h2>{{ post.title }}</h2>
<!-- 显示作者和日期 -->
<p><strong>作者:</strong>{{ post.author }} | <strong>日期:</strong>{{ post.date }}</p>
<!-- 显示完整文章内容 -->
<div>
{{ post.content }}
</div>
</article>
<!-- 返回首页的链接 -->
<a href="{{ url_for('index') }}">← 返回首页</a>
{% endblock %}
3.6 运行博客应用
Windows 系统:
# 在项目目录下运行
python app.py
macOS 系统:
# 在项目目录下运行
python3 app.py
运行后,访问以下URL:
http://127.0.0.1:5000/- 首页,显示所有文章http://127.0.0.1:5000/post/1- 第一篇文章详情http://127.0.0.1:5000/post/2- 第二篇文章详情
4. 处理表单数据
Web应用经常需要处理用户提交的表单数据。Flask可以轻松处理GET和POST请求。
4.1 简单表单示例
创建一个处理用户注册表单的示例:
# 导入Flask类和request对象
# Flask用于创建Web应用
# request对象包含客户端发送的请求信息
from flask import Flask, request, render_template
# 创建Flask应用实例
app = Flask(__name__)
# 定义注册页面路由
# methods=['GET', 'POST']表示支持GET和POST两种请求方法
@app.route('/register', methods=['GET', 'POST'])
def register():
# 判断请求方法
if request.method == 'POST':
# 如果是POST请求,处理表单提交
# 获取表单数据
# request.form是包含表单数据的字典
username = request.form.get('username', '')
email = request.form.get('email', '')
password = request.form.get('password', '')
# 简单的验证(实际应用中应该更严格)
if not username or not email or not password:
# 如果必填字段为空,返回错误信息
return '<h1>注册失败</h1><p>请填写所有必填字段。</p><a href="/register">返回</a>'
# 在实际应用中,这里应该将数据保存到数据库
# 这里只是简单返回成功信息
return f'''
<h1>注册成功!</h1>
<p>用户名:{username}</p>
<p>邮箱:{email}</p>
<p><a href="/register">继续注册</a></p>
'''
else:
# GET请求,显示注册表单
# 渲染注册表单模板
return render_template('register.html')
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
创建 templates/register.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
}
form {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 3px;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 3px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>用户注册</h1>
<!-- 表单,method="post"表示使用POST方法提交 -->
<form method="post">
<!-- 用户名输入框 -->
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<!-- 邮箱输入框 -->
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<!-- 密码输入框 -->
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
<!-- 提交按钮 -->
<button type="submit">注册</button>
</form>
</body>
</html>
4.2 使用模板处理表单
更好的方式是在模板中处理表单,这样代码更清晰:
# 导入Flask类和request、render_template函数
from flask import Flask, request, render_template
# 创建Flask应用实例
app = Flask(__name__)
# 定义注册页面路由
@app.route('/register', methods=['GET', 'POST'])
def register():
# 初始化错误信息字典
errors = {}
# 初始化成功标志
success = False
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '').strip()
email = request.form.get('email', '').strip()
password = request.form.get('password', '')
# 验证用户名
if not username:
errors['username'] = '用户名不能为空'
elif len(username) < 3:
errors['username'] = '用户名至少需要3个字符'
# 验证邮箱
if not email:
errors['email'] = '邮箱不能为空'
elif '@' not in email:
errors['email'] = '邮箱格式不正确'
# 验证密码
if not password:
errors['password'] = '密码不能为空'
elif len(password) < 6:
errors['password'] = '密码至少需要6个字符'
# 如果没有错误,注册成功
if not errors:
success = True
# 在实际应用中,这里应该将数据保存到数据库
# 这里只是简单标记为成功
# 渲染模板,传递错误信息和表单数据
return render_template('register.html',
errors=errors,
success=success,
username=username,
email=email)
else:
# GET请求,显示空表单
return render_template('register.html', errors={}, success=False)
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
更新 templates/register.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
}
form {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 5px;
border: 1px solid #ddd;
border-radius: 3px;
box-sizing: border-box;
}
.error {
color: red;
font-size: 12px;
margin-bottom: 15px;
}
.success {
color: green;
background-color: #d4edda;
padding: 10px;
border-radius: 3px;
margin-bottom: 15px;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 3px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>用户注册</h1>
<!-- 如果注册成功,显示成功消息 -->
{% if success %}
<div class="success">
<p>注册成功!</p>
</div>
{% endif %}
<!-- 注册表单 -->
<form method="post">
<!-- 用户名输入框 -->
<label for="username">用户名:</label>
<!-- value属性保留用户输入的值 -->
<input type="text" id="username" name="username"
value="{{ username if username else '' }}" required>
<!-- 如果有错误,显示错误信息 -->
{% if errors.username %}
<div class="error">{{ errors.username }}</div>
{% endif %}
<!-- 邮箱输入框 -->
<label for="email">邮箱:</label>
<input type="email" id="email" name="email"
value="{{ email if email else '' }}" required>
{% if errors.email %}
<div class="error">{{ errors.email }}</div>
{% endif %}
<!-- 密码输入框 -->
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
{% if errors.password %}
<div class="error">{{ errors.password }}</div>
{% endif %}
<!-- 提交按钮 -->
<button type="submit">注册</button>
</form>
</body>
</html>
5. 返回JSON数据(API)
Flask不仅可以返回HTML,还可以返回JSON数据,用于构建API。
5.1 简单的API示例
# 导入Flask类和jsonify函数
# Flask用于创建Web应用
# jsonify用于将Python字典转换为JSON响应
from flask import Flask, jsonify
# 创建Flask应用实例
app = Flask(__name__)
# 模拟用户数据(实际应用中来自数据库)
users = [
{'id': 1, 'name': '张三', 'email': 'zhangsan@example.com'},
{'id': 2, 'name': '李四', 'email': 'lisi@example.com'},
{'id': 3, 'name': '王五', 'email': 'wangwu@example.com'},
]
# 定义获取所有用户的API
# 返回JSON格式的数据
@app.route('/api/users', methods=['GET'])
def get_users():
# 使用jsonify将Python字典列表转换为JSON响应
# jsonify会自动设置Content-Type为application/json
return jsonify(users)
# 定义获取单个用户的API
# <int:user_id>是URL参数,必须是整数
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
# 根据ID查找用户
user = next((u for u in users if u['id'] == user_id), None)
# 如果用户不存在,返回404错误
if user is None:
# jsonify也可以返回错误信息
return jsonify({'error': '用户未找到'}), 404
# 返回用户信息
return jsonify(user)
# 定义创建用户的API
# 使用POST方法创建新资源
@app.route('/api/users', methods=['POST'])
def create_user():
# 从请求中获取JSON数据
# request.json包含请求体中的JSON数据
from flask import request
data = request.json
# 简单的验证
if not data or 'name' not in data or 'email' not in data:
return jsonify({'error': '缺少必填字段'}), 400
# 创建新用户(实际应用中应该保存到数据库)
new_user = {
'id': len(users) + 1,
'name': data['name'],
'email': data['email']
}
users.append(new_user)
# 返回新创建的用户信息
return jsonify(new_user), 201 # 201表示资源创建成功
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
5.2 测试API
可以使用Python的requests库测试API:
# 导入requests库用于发送HTTP请求
import requests
# 定义API的基础URL
base_url = 'http://127.0.0.1:5000/api'
# 测试1:获取所有用户
print("测试1:获取所有用户")
response = requests.get(f'{base_url}/users')
# 打印响应状态码
print(f"状态码: {response.status_code}")
# 打印响应内容(JSON格式)
print(f"响应: {response.json()}")
# 测试2:获取单个用户
print("\n测试2:获取用户ID为1的用户")
response = requests.get(f'{base_url}/users/1')
print(f"状态码: {response.status_code}")
print(f"响应: {response.json()}")
# 测试3:创建新用户
print("\n测试3:创建新用户")
new_user = {
'name': '赵六',
'email': 'zhaoliu@example.com'
}
# 发送POST请求,传递JSON数据
response = requests.post(f'{base_url}/users', json=new_user)
print(f"状态码: {response.status_code}")
print(f"响应: {response.json()}")
6. 组织大型应用:Blueprint
6.1 什么是Blueprint?
当Flask应用变得越来越大时,把所有路由都写在一个文件中会变得难以维护。Blueprint(蓝图)是Flask提供的组织大型应用的方式,它可以将应用分解成多个模块,每个模块负责不同的功能。
生活中的类比 :
- 没有Blueprint :就像把所有东西都放在一个大箱子里,找东西很困难
- 使用Blueprint :就像把东西分类放在不同的抽屉里,每个抽屉有标签,找东西很容易
Blueprint的优势 :
- 模块化 :将不同功能分离到不同模块
- 可复用 :可以在多个应用中复用Blueprint
- 易维护 :代码结构清晰,易于理解和维护
- 团队协作 :不同开发者可以负责不同的Blueprint
6.2 为什么需要Blueprint?
问题场景 : 假设你有一个包含以下功能的Web应用:
- 用户管理(注册、登录、个人资料)
- 博客功能(文章列表、文章详情、发布文章)
- 评论功能(发表评论、删除评论)
如果所有路由都写在一个文件中:
# 不好的做法:所有路由在一个文件中
from flask import Flask
app = Flask(__name__)
# 用户相关路由
@app.route('/user/register')
def register():
pass
@app.route('/user/login')
def login():
pass
# 博客相关路由
@app.route('/blog')
def blog_list():
pass
@app.route('/blog/<id>')
def blog_detail(id):
pass
# 评论相关路由
@app.route('/comment/add')
def add_comment():
pass
# ... 更多路由
# 文件会变得很长,难以维护
使用Blueprint的解决方案 : 将不同功能分离到不同的Blueprint中,代码更清晰、更易维护。
6.3 创建第一个Blueprint
让我们创建一个简单的Blueprint示例:
项目结构 :
my_app/
│ app.py # 主应用文件
│
├───blueprints/ # Blueprint目录
│ ├──__init__.py # 包初始化文件
│ └──auth.py # 认证相关的Blueprint
│
└───templates/ # 模板目录
└──auth/
login.html
register.html
创建Blueprint(blueprints/auth.py):
# 导入Blueprint类
# Blueprint用于创建可复用的路由模块
from flask import Blueprint, render_template, request, redirect, url_for
# 创建Blueprint实例
# 第一个参数'auth'是Blueprint的名称
# 第二个参数__name__用于确定Blueprint的根路径
# url_prefix='/auth'表示所有路由都会自动添加'/auth'前缀
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
# 在Blueprint中定义路由
# 实际URL会是 /auth/login(因为有url_prefix)
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '')
password = request.form.get('password', '')
# 简单的验证(实际应用中应该连接数据库)
if username == 'admin' and password == '123456':
# 登录成功,重定向到首页
# url_for('index')会生成首页的URL
return redirect(url_for('index'))
else:
# 登录失败,显示错误信息
error = '用户名或密码错误'
return render_template('auth/login.html', error=error)
else:
# GET请求,显示登录表单
return render_template('auth/login.html')
# 定义注册路由
# 实际URL会是 /auth/register
@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '')
email = request.form.get('email', '')
password = request.form.get('password', '')
# 简单的验证
if not username or not email or not password:
error = '请填写所有字段'
return render_template('auth/register.html', error=error)
# 在实际应用中,这里应该将用户保存到数据库
# 这里只是简单返回成功信息
return f'<h1>注册成功!</h1><p>用户名:{username}</p><p><a href="/auth/login">去登录</a></p>'
else:
# GET请求,显示注册表单
return render_template('auth/register.html')
在主应用中注册Blueprint(app.py):
# 导入Flask类
from flask import Flask, render_template
# 导入Blueprint
# 从blueprints包中导入auth模块
from blueprints.auth import auth_bp
# 创建Flask应用实例
app = Flask(__name__)
# 注册Blueprint
# register_blueprint()方法将Blueprint注册到应用中
app.register_blueprint(auth_bp)
# 定义首页路由
@app.route('/')
def index():
# 渲染首页模板
return render_template('index.html')
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
创建模板文件(templates/auth/login.html):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 400px;
margin: 50px auto;
padding: 20px;
}
form {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 3px;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 3px;
cursor: pointer;
width: 100%;
}
.error {
color: red;
margin-bottom: 15px;
}
</style>
</head>
<body>
<h1>用户登录</h1>
<!-- 如果有错误信息,显示错误 -->
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
<!-- 登录表单 -->
<form method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
<button type="submit">登录</button>
</form>
<p><a href="{{ url_for('auth.register') }}">还没有账号?去注册</a></p>
</body>
</html>
创建注册模板(templates/auth/register.html):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 400px;
margin: 50px auto;
padding: 20px;
}
form {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 3px;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 3px;
cursor: pointer;
width: 100%;
}
.error {
color: red;
margin-bottom: 15px;
}
</style>
</head>
<body>
<h1>用户注册</h1>
<!-- 如果有错误信息,显示错误 -->
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
<!-- 注册表单 -->
<form method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
<button type="submit">注册</button>
</form>
<p><a href="{{ url_for('auth.login') }}">已有账号?去登录</a></p>
</body>
</html>
6.4 多个Blueprint示例
让我们创建一个更完整的示例,包含多个Blueprint:
项目结构 :
my_app/
│ app.py
│
├───blueprints/
│ ├──__init__.py
│ ├──auth.py # 认证Blueprint
│ ├──blog.py # 博客Blueprint
│ └──api.py # API Blueprint
│
└───templates/
├──index.html
├──auth/
│ ├──login.html
│ └──register.html
└──blog/
├──list.html
└──detail.html
创建博客Blueprint(blueprints/blog.py):
# 导入Blueprint类和render_template函数
from flask import Blueprint, render_template
# 创建博客Blueprint
# url_prefix='/blog'表示所有路由都会添加'/blog'前缀
blog_bp = Blueprint('blog', __name__, url_prefix='/blog')
# 模拟博客文章数据
# 在实际应用中,这些数据来自数据库
posts = [
{
'id': 1,
'title': '第一篇文章',
'content': '这是我的第一篇博客文章内容。',
'author': '小明',
'date': '2024-01-01'
},
{
'id': 2,
'title': '学习Flask',
'content': '今天学习了Flask的Blueprint,非常有用!',
'author': '小红',
'date': '2024-01-02'
}
]
# 定义博客列表路由
# 实际URL是 /blog/
@blog_bp.route('/')
def list_posts():
# 渲染博客列表模板
return render_template('blog/list.html', posts=posts)
# 定义文章详情路由
# 实际URL是 /blog/<post_id>
@blog_bp.route('/<int:post_id>')
def show_post(post_id):
# 根据ID查找文章
post = next((p for p in posts if p['id'] == post_id), None)
# 如果文章不存在,返回404
if post is None:
return '<h1>文章未找到</h1>', 404
# 渲染文章详情模板
return render_template('blog/detail.html', post=post)
创建API Blueprint(blueprints/api.py):
# 导入Blueprint类和jsonify函数
from flask import Blueprint, jsonify
# 创建API Blueprint
# url_prefix='/api'表示所有路由都会添加'/api'前缀
api_bp = Blueprint('api', __name__, url_prefix='/api')
# 模拟用户数据
users = [
{'id': 1, 'name': '张三', 'email': 'zhangsan@example.com'},
{'id': 2, 'name': '李四', 'email': 'lisi@example.com'},
]
# 定义获取所有用户的API
# 实际URL是 /api/users
@api_bp.route('/users', methods=['GET'])
def get_users():
# 返回JSON格式的用户列表
return jsonify(users)
# 定义获取单个用户的API
# 实际URL是 /api/users/<user_id>
@api_bp.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
# 根据ID查找用户
user = next((u for u in users if u['id'] == user_id), None)
# 如果用户不存在,返回404
if user is None:
return jsonify({'error': '用户未找到'}), 404
# 返回用户信息
return jsonify(user)
更新主应用(app.py):
# 导入Flask类
from flask import Flask, render_template
# 导入所有Blueprint
# 从blueprints包中导入各个Blueprint模块
from blueprints.auth import auth_bp
from blueprints.blog import blog_bp
from blueprints.api import api_bp
# 创建Flask应用实例
app = Flask(__name__)
# 注册所有Blueprint
# 将不同的Blueprint注册到应用中
app.register_blueprint(auth_bp) # 注册认证Blueprint
app.register_blueprint(blog_bp) # 注册博客Blueprint
app.register_blueprint(api_bp) # 注册API Blueprint
# 定义首页路由
@app.route('/')
def index():
# 渲染首页模板
return render_template('index.html')
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
创建首页模板(templates/index.html):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>首页</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.nav {
background-color: #f0f0f0;
padding: 20px;
border-radius: 5px;
margin-bottom: 20px;
}
.nav a {
display: inline-block;
margin-right: 20px;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 3px;
}
.nav a:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>欢迎访问</h1>
<div class="nav">
<h2>导航菜单</h2>
<!-- 使用url_for()生成Blueprint中的路由URL -->
<!-- 'auth.login'表示auth Blueprint中的login函数 -->
<a href="{{ url_for('auth.login') }}">登录</a>
<a href="{{ url_for('auth.register') }}">注册</a>
<a href="{{ url_for('blog.list_posts') }}">博客列表</a>
<a href="{{ url_for('api.get_users') }}">API: 获取用户</a>
</div>
<p>这是一个使用Blueprint组织的Flask应用示例。</p>
<p>不同的功能被分离到不同的Blueprint中,代码更清晰、更易维护。</p>
</body>
</html>
6.5 Blueprint的URL前缀和模板
Blueprint可以有自己的URL前缀和模板目录。
带URL前缀的Blueprint :
# 创建Blueprint时指定url_prefix
# 所有路由都会自动添加这个前缀
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
# 这个路由的实际URL是 /auth/login
@auth_bp.route('/login')
def login():
return '登录页面'
Blueprint的模板目录 :
# 创建Blueprint时指定template_folder
# Blueprint会在这个目录中查找模板
blog_bp = Blueprint('blog', __name__,
url_prefix='/blog',
template_folder='blog_templates')
# 这个路由会从blog_templates目录中查找模板
@blog_bp.route('/')
def index():
return render_template('index.html') # 查找 blog_templates/index.html
完整示例 :
# blueprints/blog.py
from flask import Blueprint, render_template
# 创建Blueprint,指定URL前缀和模板目录
blog_bp = Blueprint('blog', __name__,
url_prefix='/blog',
template_folder='blog_templates')
# 定义路由
@blog_bp.route('/')
def index():
# 会从blog_templates目录查找模板
return render_template('index.html')
6.6 Blueprint之间的通信
不同的Blueprint可以通过url_for()函数相互引用。
示例 :
# blueprints/auth.py
from flask import Blueprint, render_template, redirect, url_for
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
@auth_bp.route('/login')
def login():
# 登录成功后,重定向到博客首页
# 'blog.index'表示blog Blueprint中的index函数
return redirect(url_for('blog.index'))
# blueprints/blog.py
from flask import Blueprint, render_template, url_for
blog_bp = Blueprint('blog', __name__, url_prefix='/blog')
@blog_bp.route('/')
def index():
# 在模板中可以链接到其他Blueprint的路由
login_url = url_for('auth.login')
return render_template('blog/index.html', login_url=login_url)
6.7 Blueprint的最佳实践
1. 按功能模块组织 :
my_app/
│ app.py
│
├───blueprints/
│ ├──__init__.py
│ ├──auth.py # 认证相关
│ ├──blog.py # 博客相关
│ ├──admin.py # 管理后台
│ └──api.py # API接口
│
├───templates/
│ ├──auth/
│ ├──blog/
│ └──admin/
│
└───static/
├──css/
├──js/
└──images/
2. 每个Blueprint独立管理 :
# blueprints/blog.py
from flask import Blueprint, render_template
# 创建Blueprint
blog_bp = Blueprint('blog', __name__, url_prefix='/blog')
# 在这个Blueprint中定义所有博客相关的路由
@blog_bp.route('/')
def index():
return render_template('blog/index.html')
@blog_bp.route('/<int:post_id>')
def show_post(post_id):
return render_template('blog/detail.html', post_id=post_id)
# 可以定义Blueprint级别的错误处理
@blog_bp.errorhandler(404)
def not_found(error):
return render_template('blog/404.html'), 404
3. 在主应用中统一注册 :
# app.py
from flask import Flask
# 导入所有Blueprint
from blueprints.auth import auth_bp
from blueprints.blog import blog_bp
from blueprints.api import api_bp
app = Flask(__name__)
# 统一注册所有Blueprint
app.register_blueprint(auth_bp)
app.register_blueprint(blog_bp)
app.register_blueprint(api_bp)
# 主应用只保留首页等核心路由
@app.route('/')
def index():
return '首页'
6.8 完整示例:使用Blueprint的博客系统
下面是一个完整的、使用Blueprint组织的博客系统:
项目结构 :
my_blog/
│ app.py
│
├───blueprints/
│ ├──__init__.py
│ ├──auth.py
│ └──blog.py
│
└───templates/
├──base.html
├──index.html
├──auth/
│ ├──login.html
│ └──register.html
└──blog/
├──list.html
└──detail.html
主应用文件(app.py):
# 导入Flask类
from flask import Flask, render_template
# 导入Blueprint
from blueprints.auth import auth_bp
from blueprints.blog import blog_bp
# 创建Flask应用实例
app = Flask(__name__)
# 注册Blueprint
app.register_blueprint(auth_bp)
app.register_blueprint(blog_bp)
# 定义首页路由
@app.route('/')
def index():
# 渲染首页模板
return render_template('index.html')
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
认证Blueprint(blueprints/auth.py):
# 导入Blueprint和相关函数
from flask import Blueprint, render_template, request, redirect, url_for
# 创建认证Blueprint
# url_prefix='/auth'表示所有路由都会添加'/auth'前缀
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
# 定义登录路由
# 实际URL是 /auth/login
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '')
password = request.form.get('password', '')
# 简单验证
if username == 'admin' and password == '123456':
# 登录成功,重定向到博客列表
return redirect(url_for('blog.list_posts'))
else:
# 登录失败
error = '用户名或密码错误'
return render_template('auth/login.html', error=error)
else:
# GET请求,显示登录表单
return render_template('auth/login.html')
# 定义注册路由
# 实际URL是 /auth/register
@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '')
email = request.form.get('email', '')
password = request.form.get('password', '')
# 简单验证
if not username or not email or not password:
error = '请填写所有字段'
return render_template('auth/register.html', error=error)
# 注册成功(实际应用中应该保存到数据库)
return f'<h1>注册成功!</h1><p>用户名:{username}</p><p><a href="/auth/login">去登录</a></p>'
else:
# GET请求,显示注册表单
return render_template('auth/register.html')
博客Blueprint(blueprints/blog.py):
# 导入Blueprint和render_template函数
from flask import Blueprint, render_template
# 创建博客Blueprint
# url_prefix='/blog'表示所有路由都会添加'/blog'前缀
blog_bp = Blueprint('blog', __name__, url_prefix='/blog')
# 模拟博客文章数据
posts = [
{
'id': 1,
'title': '第一篇文章',
'content': '这是我的第一篇博客文章内容。',
'author': '小明',
'date': '2024-01-01'
},
{
'id': 2,
'title': '学习Flask Blueprint',
'content': 'Blueprint让Flask应用的组织更加清晰和模块化。',
'author': '小红',
'date': '2024-01-02'
}
]
# 定义博客列表路由
# 实际URL是 /blog/
@blog_bp.route('/')
def list_posts():
# 渲染博客列表模板
return render_template('blog/list.html', posts=posts)
# 定义文章详情路由
# 实际URL是 /blog/<post_id>
@blog_bp.route('/<int:post_id>')
def show_post(post_id):
# 根据ID查找文章
post = next((p for p in posts if p['id'] == post_id), None)
# 如果文章不存在,返回404
if post is None:
return '<h1>文章未找到</h1>', 404
# 渲染文章详情模板
return render_template('blog/detail.html', post=post)
创建必要的模板文件 :
创建 templates/index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>欢迎访问博客系统</h1>
<nav>
<a href="{{ url_for('auth.login') }}">登录</a> |
<a href="{{ url_for('auth.register') }}">注册</a> |
<a href="{{ url_for('blog.list_posts') }}">博客列表</a>
</nav>
</body>
</html>
创建 templates/blog/list.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>博客列表</title>
</head>
<body>
<h1>博客文章列表</h1>
<nav>
<a href="{{ url_for('index') }}">首页</a> |
<a href="{{ url_for('auth.login') }}">登录</a>
</nav>
{% for post in posts %}
<article>
<h3><a href="{{ url_for('blog.show_post', post_id=post.id) }}">{{ post.title }}</a></h3>
<p>作者:{{ post.author }} | 日期:{{ post.date }}</p>
<p>{{ post.content[:50] }}...</p>
</article>
<hr>
{% endfor %}
</body>
</html>
创建 templates/blog/detail.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ post.title }}</title>
</head>
<body>
<h1>{{ post.title }}</h1>
<p>作者:{{ post.author }} | 日期:{{ post.date }}</p>
<div>{{ post.content }}</div>
<p><a href="{{ url_for('blog.list_posts') }}">← 返回列表</a></p>
</body>
</html>
6.9 Blueprint的常见用法总结
1. 基本创建 :
# 创建Blueprint
bp = Blueprint('name', __name__)
# 定义路由
@bp.route('/')
def index():
return 'Hello'
# 注册Blueprint
app.register_blueprint(bp)
2. 带URL前缀 :
# 所有路由都会添加'/prefix'前缀
bp = Blueprint('name', __name__, url_prefix='/prefix')
@bp.route('/hello') # 实际URL是 /prefix/hello
def hello():
return 'Hello'
3. 带模板目录 :
# Blueprint会在指定目录查找模板
bp = Blueprint('name', __name__, template_folder='my_templates')
@bp.route('/')
def index():
return render_template('index.html') # 从my_templates目录查找
4. 在模板中引用Blueprint路由 :
<!-- 使用url_for()生成Blueprint中的路由URL -->
<!-- 'blueprint_name.function_name'格式 -->
<a href="{{ url_for('blog.list_posts') }}">博客列表</a>
7. 静态文件
Web应用通常需要CSS、JavaScript、图片等静态文件。Flask可以轻松处理这些文件。
7.1 项目结构
my_app/
│ app.py
│
├───static/ # 静态文件目录
│ ├───css/
│ │ style.css
│ ├───js/
│ │ script.js
│ └───images/
│ logo.png
│
└───templates/ # 模板目录
index.html
7.2 使用静态文件
创建static/css/style.css:
/* 全局样式 */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
/* 容器样式 */
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
/* 标题样式 */
h1 {
color: #4CAF50;
}
/* 按钮样式 */
.btn {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 3px;
cursor: pointer;
}
.btn:hover {
background-color: #45a049;
}
创建templates/index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>我的应用</title>
<!-- 使用url_for()函数引用CSS文件 -->
<!-- 'static'是Flask内置的端点,filename是文件路径 -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<div class="container">
<h1>欢迎访问</h1>
<p>这是一个使用Flask和CSS的示例页面。</p>
<button class="btn">点击我</button>
</div>
<!-- 引用JavaScript文件 -->
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>
创建static/js/script.js:
// 等待页面加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
// 获取按钮元素
const btn = document.querySelector('.btn');
// 为按钮添加点击事件
btn.addEventListener('click', function() {
// 显示提示框
alert('按钮被点击了!');
});
});
更新app.py:
# 导入Flask类和render_template函数
from flask import Flask, render_template
# 创建Flask应用实例
app = Flask(__name__)
# 定义首页路由
@app.route('/')
def index():
# 渲染模板(会自动加载CSS和JS)
return render_template('index.html')
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
8. 常见问题和注意事项
8.1 调试模式
开发时建议开启调试模式,但生产环境必须关闭。
# 开发环境:开启调试模式
if __name__ == '__main__':
app.run(debug=True) # 开发时使用
# 生产环境:关闭调试模式
if __name__ == '__main__':
app.run(debug=False) # 生产环境必须关闭
8.2 端口被占用
如果5000端口被占用,可以修改端口:
# 修改端口为8080
if __name__ == '__main__':
app.run(port=8080, debug=True)
8.3 模板文件找不到
确保模板文件在正确的目录中:
# Flask默认在templates目录中查找模板
# 如果模板在其他目录,需要指定
app = Flask(__name__, template_folder='my_templates')
8.4 静态文件找不到
确保静态文件在正确的目录中:
# Flask默认在static目录中查找静态文件
# 如果静态文件在其他目录,需要指定
app = Flask(__name__, static_folder='my_static')