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 g 对象(全局变量) #
g 对象是 Flask 提供的请求级别的全局变量,用于在同一个请求的多个函数之间共享数据。它在每个请求开始时创建,请求结束时自动清除。
为什么需要 g 对象?
问题场景:
- 在视图函数中需要调用多个辅助函数
- 这些辅助函数都需要访问相同的数据(如当前用户、数据库连接等)
- 如果每次都通过参数传递会很繁琐
传统方式的问题:
# 不好的做法:通过参数传递
def get_user_posts(user_id):
# 需要数据库连接
pass
def get_user_comments(user_id):
# 也需要数据库连接
pass
@app.route('/user/<user_id>')
def show_user(user_id):
db = get_database_connection() # 获取数据库连接
posts = get_user_posts(user_id, db) # 需要传递db
comments = get_user_comments(user_id, db) # 需要传递db
return render_template('user.html', posts=posts, comments=comments)使用 g 对象的解决方案:
# 好的做法:使用 g 对象
from flask import g
def get_user_posts(user_id):
# 直接从 g 对象获取数据库连接
posts = g.db.query(...).filter_by(user_id=user_id).all()
return posts
@app.route('/user/<user_id>')
def show_user(user_id):
g.db = get_database_connection() # 存储到 g 对象
posts = get_user_posts(user_id) # 不需要传递db
comments = get_user_comments(user_id) # 不需要传递db
return render_template('user.html', posts=posts, comments=comments)基本用法:
# 导入Flask类和g对象
# g对象是Flask提供的请求级别的全局变量
from flask import Flask, g, render_template
# 创建Flask应用实例
app = Flask(__name__)
# 模拟获取当前用户信息的函数
def get_current_user():
# 在实际应用中,这里可能从session或token中获取用户信息
# 这里只是模拟
return {'id': 1, 'name': '张三', 'role': 'admin'}
# 定义在请求开始前执行的函数
# before_request装饰器会在每个请求处理前执行
@app.before_request
def load_user():
# 将当前用户信息存储到g对象中
# 这样在同一个请求的所有函数中都可以访问
g.current_user = get_current_user()
# 可以存储多个值
g.request_start_time = time.time()
# 定义辅助函数,从g对象获取数据
def get_user_name():
# 从g对象获取当前用户信息
# 如果g对象中没有该属性,会抛出AttributeError
return g.current_user['name']
def is_admin():
# 检查当前用户是否是管理员
return g.current_user.get('role') == 'admin'
# 定义路由
@app.route('/')
def index():
# 在视图函数中可以直接使用g对象
user_name = g.current_user['name']
# 也可以调用辅助函数
is_user_admin = is_admin()
return f'''
<h1>欢迎,{user_name}!</h1>
<p>管理员权限:{'是' if is_user_admin else '否'}</p>
<p>请求开始时间:{g.request_start_time}</p>
'''
# 定义另一个路由,也可以访问g对象
@app.route('/profile')
def profile():
# 在这个路由中也可以访问g对象中的用户信息
user = g.current_user
return f'''
<h1>用户资料</h1>
<p>用户ID:{user["id"]}</p>
<p>用户名:{user["name"]}</p>
<p>角色:{user["role"]}</p>
'''
# 运行应用
if __name__ == '__main__':
import time
app.run(debug=True)使用 g 对象存储数据库连接:
# 导入Flask类和g对象
from flask import Flask, g, render_template
import sqlite3
# 创建Flask应用实例
app = Flask(__name__)
# 数据库文件路径
DATABASE = 'example.db'
# 获取数据库连接的函数
def get_db():
# 如果g对象中已经有数据库连接,直接返回
# 这样可以避免在同一个请求中重复创建连接
db = getattr(g, '_database', None)
if db is None:
# 如果g对象中没有连接,创建新连接
db = g._database = sqlite3.connect(DATABASE)
# 设置返回字典格式的游标(更易用)
db.row_factory = sqlite3.Row
return db
# 定义在请求结束时关闭数据库连接的函数
# teardown_appcontext装饰器会在请求结束时执行
@app.teardown_appcontext
def close_db(error):
# 关闭数据库连接
db = getattr(g, '_database', None)
if db is not None:
db.close()
# 定义查询用户的函数
def get_user_by_id(user_id):
# 从g对象获取数据库连接
db = get_db()
# 执行查询
cursor = db.execute('SELECT * FROM users WHERE id = ?', (user_id,))
user = cursor.fetchone()
return dict(user) if user else None
# 定义路由
@app.route('/user/<int:user_id>')
def show_user(user_id):
# 使用辅助函数查询用户
# 辅助函数内部会从g对象获取数据库连接
user = get_user_by_id(user_id)
if user:
return f'''
<h1>用户信息</h1>
<p>ID:{user["id"]}</p>
<p>姓名:{user["name"]}</p>
<p>邮箱:{user["email"]}</p>
'''
else:
return '<h1>用户不存在</h1>', 404
# 运行应用
if __name__ == '__main__':
app.run(debug=True)使用 g 对象存储请求级别的缓存:
# 导入Flask类和g对象
from flask import Flask, g, render_template
import time
# 创建Flask应用实例
app = Flask(__name__)
# 模拟一个耗时的数据获取函数
def fetch_expensive_data():
# 模拟耗时操作(实际应用中可能是数据库查询、API调用等)
time.sleep(0.1)
return {'data': '这是耗时获取的数据', 'timestamp': time.time()}
# 定义在请求开始前执行的函数
@app.before_request
def load_data():
# 将耗时获取的数据存储到g对象中
# 这样在同一个请求的多个函数中都可以使用,避免重复获取
g.expensive_data = fetch_expensive_data()
# 定义辅助函数
def get_data():
# 从g对象获取数据
return g.expensive_data
# 定义路由1
@app.route('/page1')
def page1():
# 使用g对象中的数据
data = g.expensive_data
return f'<h1>页面1</h1><p>数据:{data["data"]}</p>'
# 定义路由2
@app.route('/page2')
def page2():
# 也可以使用辅助函数获取数据
data = get_data()
return f'<h1>页面2</h1><p>数据:{data["data"]}</p>'
# 运行应用
if __name__ == '__main__':
app.run(debug=True)实际应用示例:用户认证和权限检查:
# 导入Flask类和所需函数
from flask import Flask, g, render_template, request, session, redirect, url_for
# 创建Flask应用实例
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
# 模拟用户数据库(实际应用中来自真实数据库)
users_db = {
'admin': {'password': '123456', 'role': 'admin', 'name': '管理员'},
'user1': {'password': 'password', 'role': 'user', 'name': '普通用户'}
}
# 定义在请求开始前加载当前用户的函数
@app.before_request
def load_current_user():
# 从session中获取用户名
username = session.get('username')
if username and username in users_db:
# 如果用户已登录,将用户信息存储到g对象
user_info = users_db[username]
g.current_user = {
'username': username,
'name': user_info['name'],
'role': user_info['role']
}
else:
# 如果用户未登录,g.current_user为None
g.current_user = None
# 定义检查用户是否登录的辅助函数
def is_logged_in():
# 检查g对象中是否有当前用户
return g.current_user is not None
# 定义检查用户角色的辅助函数
def is_admin():
# 检查当前用户是否是管理员
return g.current_user and g.current_user['role'] == 'admin'
# 定义需要登录才能访问的装饰器
def login_required(f):
from functools import wraps
@wraps(f)
def decorated_function(*args, **kwargs):
# 检查用户是否登录
if not is_logged_in():
# 如果未登录,重定向到登录页面
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated_function
# 定义需要管理员权限的装饰器
def admin_required(f):
from functools import wraps
@wraps(f)
def decorated_function(*args, **kwargs):
# 检查用户是否登录
if not is_logged_in():
return redirect(url_for('login'))
# 检查用户是否是管理员
if not is_admin():
return '<h1>权限不足</h1><p>您需要管理员权限才能访问此页面。</p>', 403
return f(*args, **kwargs)
return decorated_function
# 定义登录路由
@app.route('/login', methods=['GET', 'POST'])
def login():
# 如果已登录,重定向到首页
if is_logged_in():
return redirect(url_for('index'))
if request.method == 'POST':
username = request.form.get('username', '').strip()
password = request.form.get('password', '')
# 验证用户名和密码
if username in users_db and users_db[username]['password'] == password:
# 登录成功,存储用户名到session
session['username'] = username
# 重定向到首页(g对象会在下一个请求中自动更新)
return redirect(url_for('index'))
else:
return '<h1>登录失败</h1><p>用户名或密码错误。</p>'
else:
# GET请求,显示登录表单
return '''
<form method="post">
<label>用户名:</label>
<input type="text" name="username" required><br><br>
<label>密码:</label>
<input type="password" name="password" required><br><br>
<button type="submit">登录</button>
</form>
'''
# 定义登出路由
@app.route('/logout')
def logout():
# 清除session
session.pop('username', None)
return redirect(url_for('index'))
# 定义首页路由(需要登录)
@app.route('/')
@login_required
def index():
# 使用g对象中的当前用户信息
user = g.current_user
return f'''
<h1>欢迎,{user["name"]}!</h1>
<p>用户名:{user["username"]}</p>
<p>角色:{user["role"]}</p>
<a href="/profile">查看资料</a>
<a href="/admin">管理后台</a>
<a href="/logout">登出</a>
'''
# 定义用户资料路由(需要登录)
@app.route('/profile')
@login_required
def profile():
# 使用g对象中的当前用户信息
user = g.current_user
return f'''
<h1>用户资料</h1>
<p>用户名:{user["username"]}</p>
<p>姓名:{user["name"]}</p>
<p>角色:{user["role"]}</p>
<a href="/">返回首页</a>
'''
# 定义管理后台路由(需要管理员权限)
@app.route('/admin')
@admin_required
def admin():
# 只有管理员才能访问
# 使用g对象中的当前用户信息
user = g.current_user
return f'''
<h1>管理后台</h1>
<p>欢迎,{user["name"]}管理员!</p>
<p>这里是管理后台的内容。</p>
<a href="/">返回首页</a>
'''
# 运行应用
if __name__ == '__main__':
app.run(debug=True)注意事项:
- 请求级别:g 对象只在同一个请求中有效,不同请求之间的 g 对象是独立的
- 自动清除:每个请求结束后,g 对象会自动清除,不需要手动清理
- 线程安全:Flask 确保每个请求的 g 对象是线程安全的
- 属性访问:访问不存在的属性会抛出
AttributeError,建议使用getattr(g, 'key', default)或先检查
最佳实践:
- 使用 before_request:在 `@app.before_request` 中初始化 g 对象的数据
- 使用 teardown_appcontext:在 `@app.teardown_appcontext` 中清理资源(如关闭数据库连接)
- 避免存储大量数据:g 对象应该只存储请求级别需要的小量数据
- 使用辅助函数:通过辅助函数访问 g 对象,而不是直接访问,提高代码可维护性
2.6 模板渲染(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>2.7 模板上下文处理器(context_processor) #
在多个模板中都需要使用相同的变量时(如用户信息、网站配置等),如果每个视图函数都传递这些变量会很繁琐。Flask提供了`@app.context_processor`装饰器,可以向所有模板的上下文注入全局变量。
为什么需要context_processor?
问题场景:
- 每个模板都需要显示当前登录用户信息
- 每个模板都需要显示网站标题、导航菜单等
- 需要在所有模板中使用某些工具函数
传统方式的问题:
# 每个视图函数都需要传递相同的变量
@app.route('/')
def index():
current_user = get_current_user() # 获取当前用户
site_title = '我的网站'
return render_template('index.html',
current_user=current_user,
site_title=site_title)
@app.route('/about')
def about():
current_user = get_current_user() # 重复代码
site_title = '我的网站' # 重复代码
return render_template('about.html',
current_user=current_user,
site_title=site_title)使用context_processor的解决方案:
# 定义一次,所有模板都可以使用
@app.context_processor
def inject_user():
return dict(current_user=get_current_user(),
site_title='我的网站')基本用法:
# 导入Flask类和render_template函数
from flask import Flask, render_template
# 创建Flask应用实例
app = Flask(__name__)
# 定义context_processor函数
# 这个函数会在每次渲染模板时自动调用
# 返回的字典中的变量会被注入到所有模板的上下文中
@app.context_processor
def inject_global_vars():
# 返回一个字典,字典中的键值对会成为模板中的全局变量
return {
'site_name': '我的Flask网站',
'site_url': 'https://example.com',
'current_year': 2024
}
# 定义路由
@app.route('/')
def index():
# 不需要传递site_name等变量,它们已经在模板上下文中了
return render_template('index.html')
@app.route('/about')
def about():
# 同样不需要传递,所有模板都可以直接使用
return render_template('about.html')
# 运行应用
if __name__ == '__main__':
app.run(debug=True)在模板中使用:
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ site_name }}</title>
</head>
<body>
<h1>欢迎访问 {{ site_name }}</h1>
<p>网站地址:{{ site_url }}</p>
<p>当前年份:{{ current_year }}</p>
</body>
</html>注入函数:
context_processor不仅可以注入变量,还可以注入函数,让模板中可以使用Python函数。
from flask import Flask, render_template
from datetime import datetime
app = Flask(__name__)
# 定义一些工具函数
def format_date(date):
"""格式化日期"""
return date.strftime('%Y年%m月%d日')
def get_greeting():
"""根据时间返回问候语"""
hour = datetime.now().hour
if hour < 12:
return '早上好'
elif hour < 18:
return '下午好'
else:
return '晚上好'
# 将函数注入到模板上下文
@app.context_processor
def inject_helpers():
return {
'format_date': format_date, # 注入格式化函数
'greeting': get_greeting() # 注入问候语(调用函数获取值)
}
# 定义路由
@app.route('/')
def index():
return render_template('index.html',
current_date=datetime.now())
# 运行应用
if __name__ == '__main__':
app.run(debug=True)在模板中使用函数:
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>{{ greeting }}!</h1>
<p>当前日期:{{ format_date(current_date) }}</p>
<!-- format_date函数可以在模板中直接调用 -->
</body>
</html>注入当前用户信息:
实际应用中最常见的用法是注入当前登录用户信息。
from flask import Flask, render_template, session
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
# 模拟用户数据(实际应用中来自数据库)
users = {
'admin': {'name': '管理员', 'email': 'admin@example.com'},
'user1': {'name': '用户1', 'email': 'user1@example.com'}
}
def get_current_user():
"""获取当前登录用户"""
# 从session中获取用户名
username = session.get('username')
if username and username in users:
return users[username]
return None
# 将当前用户注入到所有模板
@app.context_processor
def inject_user():
return {
'current_user': get_current_user() # 每次渲染模板时都会获取最新用户信息
}
# 登录路由
@app.route('/login/<username>')
def login(username):
if username in users:
session['username'] = username
return f'<h1>登录成功!</h1><p>欢迎,{users[username]["name"]}</p><a href="/">返回首页</a>'
return '<h1>用户不存在</h1>'
# 登出路由
@app.route('/logout')
def logout():
session.pop('username', None)
return '<h1>已登出</h1><a href="/">返回首页</a>'
# 首页路由
@app.route('/')
def index():
# current_user已经在模板上下文中,不需要传递
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>
</head>
<body>
<h1>欢迎访问</h1>
<!-- 检查用户是否登录 -->
{% if current_user %}
<p>当前用户:{{ current_user.name }}</p>
<p>邮箱:{{ current_user.email }}</p>
<a href="/logout">登出</a>
{% else %}
<p>您还未登录</p>
<a href="/login/admin">登录</a>
{% endif %}
</body>
</html>注意事项:
- 性能考虑:context_processor函数在每次渲染模板时都会调用,避免在其中执行耗时操作
- 变量覆盖:如果视图函数传递了同名变量,会覆盖context_processor注入的变量
- 返回值:context_processor函数必须返回一个字典
- 命名规范:使用有意义的变量名,避免与视图函数传递的变量冲突
最佳实践:
- 按功能分组:将相关的变量和函数放在同一个context_processor中
- 使用缓存:对于不经常变化的数据,考虑使用缓存
- 文档说明:在代码中注释说明哪些变量是全局可用的
- 避免过度使用:只注入真正需要在多个模板中使用的变量
2.8 Flash Messages(闪存消息) #
Flash Messages 是 Flask 提供的在请求之间传递消息的机制。它通常用于在操作完成后(如登录成功、数据保存成功)向用户显示提示信息。
为什么需要 Flash Messages?
问题场景:
- 用户提交表单后,需要重定向到另一个页面
- 重定向后,原来的页面数据已经丢失
- 需要在新的页面上显示操作结果(成功或失败)
传统方式的问题:
# 不好的做法:使用URL参数传递消息
@app.route('/login', methods=['POST'])
def login():
if login_success:
return redirect('/dashboard?message=登录成功') # URL参数方式,不优雅使用 Flash Messages 的解决方案:
# 好的做法:使用 Flash Messages
from flask import flash, redirect, url_for
@app.route('/login', methods=['POST'])
def login():
if login_success:
flash('登录成功!', 'success') # 存储消息
return redirect(url_for('dashboard')) # 重定向后消息仍然可用基本用法:
# 导入Flask类和flash、get_flashed_messages函数
# flash用于存储消息
# get_flashed_messages用于获取消息
from flask import Flask, flash, redirect, url_for, render_template, request, session
# 创建Flask应用实例
app = Flask(__name__)
# 设置密钥(Flash Messages需要session支持,session需要密钥)
app.config['SECRET_KEY'] = 'your-secret-key-here'
# 模拟用户数据(实际应用中来自数据库)
users = {
'admin': '123456',
'user1': 'password123'
}
# 定义登录路由
@app.route('/login', methods=['GET', 'POST'])
def login():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '')
password = request.form.get('password', '')
# 验证用户名和密码
if username in users and users[username] == password:
# 登录成功,存储用户信息到session
session['username'] = username
# 使用flash()存储成功消息
# 第一个参数是消息内容,第二个参数是消息类别(可选)
flash('登录成功!欢迎回来。', 'success')
# 重定向到首页
return redirect(url_for('index'))
else:
# 登录失败,存储错误消息
flash('用户名或密码错误,请重试。', 'error')
# 重新显示登录页面
return redirect(url_for('login'))
else:
# GET请求,显示登录表单
return render_template('login.html')
# 定义登出路由
@app.route('/logout')
def logout():
# 清除session中的用户信息
session.pop('username', None)
# 存储登出成功消息
flash('您已成功登出。', 'info')
# 重定向到首页
return redirect(url_for('index'))
# 定义首页路由
@app.route('/')
def index():
# 渲染首页模板(Flash Messages会自动在模板中显示)
return render_template('index.html')
# 运行应用
if __name__ == '__main__':
app.run(debug=True)在模板中显示 Flash Messages:
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>首页</title>
<style>
/* Flash Messages 样式 */
.flash-messages {
margin: 20px 0;
}
.flash-message {
padding: 15px;
margin-bottom: 10px;
border-radius: 5px;
border-left: 4px solid;
}
.flash-success {
background-color: #d4edda;
border-color: #28a745;
color: #155724;
}
.flash-error {
background-color: #f8d7da;
border-color: #dc3545;
color: #721c24;
}
.flash-info {
background-color: #d1ecf1;
border-color: #17a2b8;
color: #0c5460;
}
.flash-warning {
background-color: #fff3cd;
border-color: #ffc107;
color: #856404;
}
</style>
</head>
<body>
<h1>欢迎访问</h1>
<!-- 显示 Flash Messages -->
<!-- get_flashed_messages()获取所有Flash消息 -->
<!-- with_categories=True表示同时获取消息类别 -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-messages">
<!-- 遍历所有消息 -->
{% for category, message in messages %}
<!-- 根据消息类别应用不同的样式 -->
<div class="flash-message flash-{{ category }}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- 页面内容 -->
{% if session.username %}
<p>当前用户:{{ session.username }}</p>
<a href="{{ url_for('logout') }}">登出</a>
{% else %}
<p>您还未登录</p>
<a href="{{ url_for('login') }}">登录</a>
{% endif %}
</body>
</html>创建登录页面模板:
<!-- templates/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;
}
.flash-message {
padding: 15px;
margin-bottom: 15px;
border-radius: 5px;
border-left: 4px solid;
}
.flash-error {
background-color: #f8d7da;
border-color: #dc3545;
color: #721c24;
}
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%;
}
</style>
</head>
<body>
<h1>用户登录</h1>
<!-- 显示 Flash Messages(如果有错误消息) -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash-message flash-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- 登录表单 -->
<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('index') }}">返回首页</a></p>
</body>
</html>消息类别:
Flash Messages 支持不同的消息类别,可以根据类别显示不同的样式:
# 导入Flask类和flash函数
from flask import Flask, flash, redirect, url_for, render_template
# 创建Flask应用实例
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
# 定义路由,演示不同类别的消息
@app.route('/test')
def test():
# 成功消息(通常用绿色显示)
flash('操作成功完成!', 'success')
# 错误消息(通常用红色显示)
flash('操作失败,请重试。', 'error')
# 信息消息(通常用蓝色显示)
flash('这是一条提示信息。', 'info')
# 警告消息(通常用黄色显示)
flash('请注意:这是一个警告。', 'warning')
# 重定向到显示页面
return redirect(url_for('show_messages'))
# 定义显示消息的路由
@app.route('/messages')
def show_messages():
# 渲染模板,显示所有Flash Messages
return render_template('messages.html')
# 运行应用
if __name__ == '__main__':
app.run(debug=True)实际应用示例:用户注册和登录:
# 导入Flask类和所需函数
from flask import Flask, flash, redirect, url_for, render_template, request, session
# 创建Flask应用实例
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
# 模拟用户数据库(实际应用中应该使用真实数据库)
users_db = {}
# 定义注册路由
@app.route('/register', methods=['GET', 'POST'])
def register():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '').strip()
email = request.form.get('email', '').strip()
password = request.form.get('password', '')
# 验证输入
if not username or not email or not password:
flash('请填写所有必填字段。', 'error')
return redirect(url_for('register'))
# 检查用户名是否已存在
if username in users_db:
flash('用户名已存在,请选择其他用户名。', 'error')
return redirect(url_for('register'))
# 检查邮箱是否已注册
if any(u['email'] == email for u in users_db.values()):
flash('该邮箱已被注册,请使用其他邮箱。', 'error')
return redirect(url_for('register'))
# 保存用户信息(实际应用中应该加密密码)
users_db[username] = {
'email': email,
'password': password # 实际应用中应该使用哈希加密
}
# 注册成功,显示成功消息
flash('注册成功!请登录。', 'success')
# 重定向到登录页面
return redirect(url_for('login'))
else:
# GET请求,显示注册表单
return render_template('register.html')
# 定义登录路由
@app.route('/login', methods=['GET', 'POST'])
def login():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '').strip()
password = request.form.get('password', '')
# 验证用户名和密码
if username in users_db and users_db[username]['password'] == password:
# 登录成功,存储用户信息到session
session['username'] = username
# 显示成功消息
flash(f'欢迎回来,{username}!', 'success')
# 重定向到首页
return redirect(url_for('index'))
else:
# 登录失败,显示错误消息
flash('用户名或密码错误,请重试。', 'error')
# 重新显示登录页面
return redirect(url_for('login'))
else:
# GET请求,显示登录表单
return render_template('login.html')
# 定义登出路由
@app.route('/logout')
def logout():
# 获取当前用户名(用于显示消息)
username = session.get('username', '')
# 清除session
session.pop('username', None)
# 显示登出消息
if username:
flash(f'再见,{username}!您已成功登出。', 'info')
# 重定向到首页
return redirect(url_for('index'))
# 定义首页路由
@app.route('/')
def index():
# 渲染首页模板
return render_template('index.html')
# 运行应用
if __name__ == '__main__':
app.run(debug=True)创建基础模板(包含 Flash Messages 显示):
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{% block title %}我的应用{% endblock %}</title>
<style>
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
/* Flash Messages 样式 */
.flash-messages {
margin: 20px 0;
}
.flash-message {
padding: 15px 20px;
margin-bottom: 10px;
border-radius: 5px;
border-left: 4px solid;
position: relative;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.flash-success {
background-color: #d4edda;
border-color: #28a745;
color: #155724;
}
.flash-error {
background-color: #f8d7da;
border-color: #dc3545;
color: #721c24;
}
.flash-info {
background-color: #d1ecf1;
border-color: #17a2b8;
color: #0c5460;
}
.flash-warning {
background-color: #fff3cd;
border-color: #ffc107;
color: #856404;
}
/* 内容区域样式 */
.content {
background-color: white;
padding: 30px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<div class="container">
<!-- Flash Messages 显示区域 -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-messages">
{% for category, message in messages %}
<div class="flash-message flash-{{ category }}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- 页面内容 -->
<div class="content">
{% block content %}{% endblock %}
</div>
</div>
{% block extra_js %}{% endblock %}
</body>
</html>注意事项:
- 需要设置 SECRET_KEY:Flash Messages 依赖 session,session 需要 SECRET_KEY 来加密
- 消息只显示一次:Flash Messages 在获取后会自动清除,不会重复显示
- 消息类别:建议使用标准的类别名称(success、error、info、warning),便于统一样式
- 重定向后显示:Flash Messages 通常在重定向后的页面显示,确保用户能看到操作结果
最佳实践:
- 统一消息样式:在基础模板中定义 Flash Messages 的样式,所有页面统一使用
- 使用消息类别:根据消息类型使用不同的类别,提供更好的用户体验
- 消息内容清晰:消息内容要简洁明了,让用户清楚知道发生了什么
- 及时反馈:在操作完成后立即显示消息,不要延迟
3. 应用配置 #
Flask应用需要各种配置,如数据库连接、密钥、调试模式等。Flask提供了灵活的配置管理方式。
3.1 为什么需要配置管理? #
问题场景:
- 开发环境和生产环境需要不同的配置(如数据库地址、调试模式)
- 配置信息分散在代码中,难以管理和修改
- 敏感信息(如密钥)不应该硬编码在代码中
解决方案:
- 将配置集中管理
- 使用配置文件或配置类
- 根据环境加载不同的配置
3.2 基本配置方式 #
方式1:直接设置配置
# 导入Flask类
from flask import Flask
# 创建Flask应用实例
app = Flask(__name__)
# 直接设置配置项
# app.config是一个字典,可以像普通字典一样操作
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'your-secret-key-here'
app.config['DATABASE_URI'] = 'sqlite:///mydatabase.db'
# 也可以使用update()方法批量设置
app.config.update(
DEBUG=True,
SECRET_KEY='your-secret-key-here',
DATABASE_URI='sqlite:///mydatabase.db'
)
# 访问配置
print(app.config['DEBUG']) # 输出: True方式2:从环境变量读取
# 导入Flask类和os模块
from flask import Flask
import os
# 创建Flask应用实例
app = Flask(__name__)
# 从环境变量读取配置
# os.getenv()获取环境变量,如果不存在则使用默认值
app.config['DEBUG'] = os.getenv('FLASK_DEBUG', 'False') == 'True'
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'default-secret-key')
app.config['DATABASE_URI'] = os.getenv('DATABASE_URI', 'sqlite:///mydatabase.db')3.3 使用配置类(推荐方式) #
将配置组织成类,更清晰、更易管理。
创建配置类:
# config.py
# 配置类文件,定义不同环境的配置
# 基础配置类(所有环境共用的配置)
class Config:
# 应用密钥,用于加密会话等
# 实际应用中应该使用环境变量或密钥管理服务
SECRET_KEY = 'dev-secret-key-change-in-production'
# 数据库配置
DATABASE_URI = 'sqlite:///mydatabase.db'
# 其他通用配置
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 最大上传文件大小:16MB
# 开发环境配置
class DevelopmentConfig(Config):
# 开发环境开启调试模式
DEBUG = True
# 开发环境的数据库
DATABASE_URI = 'sqlite:///dev_database.db'
# 生产环境配置
class ProductionConfig(Config):
# 生产环境关闭调试模式
DEBUG = False
# 生产环境的数据库
DATABASE_URI = 'postgresql://user:password@localhost/prod_database'
# 测试环境配置
class TestingConfig(Config):
# 测试环境开启测试模式
TESTING = True
# 测试环境使用内存数据库
DATABASE_URI = 'sqlite:///:memory:'
# 配置字典,方便根据环境名称获取配置
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig,
'default': DevelopmentConfig # 默认使用开发环境配置
}在主应用中使用配置类:
# app.py
# 导入Flask类
from flask import Flask
# 导入配置类
from config import config
# 创建Flask应用实例
app = Flask(__name__)
# 从环境变量获取配置环境名称,默认为'development'
import os
config_name = os.getenv('FLASK_ENV', 'development')
# 使用from_object()方法加载配置类
# from_object()会加载配置类中所有大写的属性作为配置项
app.config.from_object(config[config_name])
# 验证配置是否加载成功
print(f"当前环境: {config_name}")
print(f"调试模式: {app.config['DEBUG']}")
print(f"数据库URI: {app.config['DATABASE_URI']}")
# 定义路由
@app.route('/')
def index():
return '<h1>首页</h1>'
# 运行应用
if __name__ == '__main__':
app.run(debug=app.config['DEBUG'])3.4 app.config.from_object() #
app.config.from_object() 是Flask提供的从对象(类或模块)加载配置的方法。
语法:
app.config.from_object(obj)参数:
obj:配置对象,可以是:- 配置类(推荐)
- Python模块(包含配置变量的模块)
- 配置对象的字符串路径(如
'config.DevelopmentConfig')
工作原理:
from_object()会遍历对象的所有属性- 只加载大写的属性作为配置项
- 忽略私有属性(以下划线开头的属性)
- 忽略方法和特殊属性
示例1:从配置类加载
# config.py
class Config:
DEBUG = True
SECRET_KEY = 'my-secret-key'
DATABASE_URI = 'sqlite:///app.db'
# 小写属性不会被加载
database_name = 'mydb'
# 私有属性不会被加载
_private_key = 'private'
# 方法不会被加载
def get_database(self):
return self.DATABASE_URI
# app.py
from flask import Flask
from config import Config
app = Flask(__name__)
# 加载配置类
app.config.from_object(Config)
# 验证配置
print(app.config['DEBUG']) # 输出: True
print(app.config['SECRET_KEY']) # 输出: 'my-secret-key'
print(app.config['DATABASE_URI']) # 输出: 'sqlite:///app.db'
# 小写属性不会被加载
print('database_name' in app.config) # 输出: False示例2:从模块加载
# config_module.py
# 配置模块,直接定义配置变量
DEBUG = True
SECRET_KEY = 'my-secret-key'
DATABASE_URI = 'sqlite:///app.db'
# app.py
from flask import Flask
import config_module
app = Flask(__name__)
# 从模块加载配置
app.config.from_object(config_module)
# 验证配置
print(app.config['DEBUG']) # 输出: True
print(app.config['SECRET_KEY']) # 输出: 'my-secret-key'示例3:使用字符串路径
# config.py
class DevelopmentConfig:
DEBUG = True
SECRET_KEY = 'dev-key'
# app.py
from flask import Flask
app = Flask(__name__)
# 使用字符串路径加载配置
# 格式: '模块路径.类名'
app.config.from_object('config.DevelopmentConfig')
# 验证配置
print(app.config['DEBUG']) # 输出: True3.5 多环境配置管理 #
在实际项目中,通常需要管理多个环境的配置。
完整的配置管理示例:
项目结构:
my_app/
│ app.py
│ config.py
│ .env # 环境变量文件(可选)config.py(配置类文件):
# config.py
import os
# 基础配置类
class Config:
# 从环境变量读取,如果没有则使用默认值
SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-me')
# 数据库配置
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URI', 'sqlite:///app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 其他配置
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB
# 开发环境配置
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.getenv('DEV_DATABASE_URI', 'sqlite:///dev.db')
# 生产环境配置
class ProductionConfig(Config):
DEBUG = False
# 生产环境应该从环境变量读取敏感信息
SECRET_KEY = os.getenv('SECRET_KEY') # 必须设置,不能有默认值
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URI') # 必须设置
# 测试环境配置
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' # 内存数据库,测试后自动删除
# 配置字典
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig,
'default': DevelopmentConfig
}app.py(主应用文件):
# app.py
from flask import Flask
from config import config
import os
# 创建Flask应用实例
app = Flask(__name__)
# 从环境变量获取配置环境名称
# FLASK_ENV环境变量可以在系统环境变量中设置,或在.env文件中设置
config_name = os.getenv('FLASK_ENV', 'development')
# 使用from_object()加载配置
app.config.from_object(config[config_name])
# 可选:从环境变量文件加载额外配置
# 需要安装python-dotenv: pip install python-dotenv
# from dotenv import load_dotenv
# load_dotenv() # 从.env文件加载环境变量
# 定义路由
@app.route('/')
def index():
return f'<h1>当前环境: {config_name}</h1><p>调试模式: {app.config["DEBUG"]}</p>'
# 运行应用
if __name__ == '__main__':
app.run(debug=app.config['DEBUG'])使用方式:
# 开发环境(默认)
python app.py
# 生产环境
export FLASK_ENV=production
python app.py
# 测试环境
export FLASK_ENV=testing
python app.py3.6 配置的优先级 #
Flask配置的加载顺序(优先级从高到低):
- 直接设置:
app.config['KEY'] = 'value'(最高优先级) - from_object():
app.config.from_object(Config) - from_envvar():从环境变量指定的文件加载
- from_pyfile():从Python文件加载
- 默认值:Flask内置的默认配置(最低优先级)
示例:
from flask import Flask
from config import Config
app = Flask(__name__)
# 1. 先加载配置类
app.config.from_object(Config)
# 2. 然后可以覆盖特定配置
app.config['DEBUG'] = False # 覆盖配置类中的DEBUG值
# 3. 或者从环境变量覆盖
import os
if os.getenv('FORCE_DEBUG'):
app.config['DEBUG'] = True # 环境变量强制开启调试模式3.7 配置的最佳实践 #
1. 使用配置类组织配置:
- 基础配置类包含所有环境共用的配置
- 不同环境继承基础配置类,只覆盖需要改变的配置
2. 敏感信息使用环境变量:
- 密钥、数据库密码等敏感信息不应该硬编码
- 使用环境变量或密钥管理服务
3. 配置文件不要提交到版本控制:
- 将包含敏感信息的配置文件添加到
.gitignore - 提供配置模板文件(如
config.example.py)
4. 使用配置字典管理多环境:
- 通过配置字典方便切换环境
- 使用环境变量指定当前环境
5. 验证必需配置:
# 在应用启动时验证必需配置
required_config = ['SECRET_KEY', 'DATABASE_URI']
for key in required_config:
if not app.config.get(key):
raise ValueError(f'必需配置 {key} 未设置')4. 返回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'), 4043. 在主应用中统一注册:
# 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的常见用法总结 #
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.html7.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')