库简介
Flask是一个轻量级的Web应用框架,基于Werkzeug WSGI工具包和Jinja2模板引擎。它被设计为易于使用和扩展,遵循"微框架"理念,这意味着Flask核心简单但可以通过扩展添加更多功能。
主要特点:
- 轻量级:核心功能简单,不包含数据库抽象层、表单验证等
- 灵活:可以通过扩展添加所需功能
- 易于学习:API设计简洁,文档完善
- 强大的扩展生态系统:拥有丰富的第三方扩展
- Jinja2模板引擎:内置强大的模板系统
- 开发服务器和调试器:内置开发服务器和交互式调试器
适用场景:
- 小型到中型Web应用
- RESTful API服务
- 微服务架构
- 快速原型开发
- 学习和教学
安装方法
基本安装
pip install flask
安装常用扩展
# Flask-SQLAlchemy:数据库ORM
pip install flask-sqlalchemy
# Flask-WTF:表单处理
pip install flask-wtf
# Flask-Login:用户认证
pip install flask-login
# Flask-RESTful:REST API开发
pip install flask-restful
# Flask-CORS:跨域支持
pip install flask-cors
验证安装
import flask
print(f"Flask版本:{flask.__version__}")
入门示例
1. 最简单的Flask应用
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
if __name__ == '__main__':
app.run(debug=True)
2. 带路由参数的应用
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
@app.route('/user/<username>')
def show_user(username):
return f'用户:{username}'
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f'帖子ID:{post_id}'
if __name__ == '__main__':
app.run(debug=True)
3. 使用模板
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html', title='首页')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
if __name__ == '__main__':
app.run(debug=True)
templates/index.html:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>欢迎来到{{ title }}</h1>
<p>这是一个Flask应用示例</p>
</body>
</html>
templates/user.html:
<!DOCTYPE html>
<html>
<head>
<title>用户页面</title>
</head>
<body>
<h1>你好,{{ name }}!</h1>
<p>欢迎访问你的个人页面</p>
</body>
</html>
进阶实战
完整的待办事项应用
from flask import Flask, request, jsonify, render_template
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'your-secret-key-change-in-production'
db = SQLAlchemy(app)
# 定义数据模型
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=True)
completed = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'description': self.description,
'completed': self.completed,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None
}
# 创建数据库表
with app.app_context():
db.create_all()
# API端点
@app.route('/api/todos', methods=['GET'])
def get_todos():
"""获取所有待办事项"""
todos = Todo.query.all()
return jsonify([todo.to_dict() for todo in todos])
@app.route('/api/todos/<int:todo_id>', methods=['GET'])
def get_todo(todo_id):
"""获取单个待办事项"""
todo = Todo.query.get_or_404(todo_id)
return jsonify(todo.to_dict())
@app.route('/api/todos', methods=['POST'])
def create_todo():
"""创建新的待办事项"""
data = request.json
if not data or 'title' not in data:
return jsonify({'error': '标题是必填项'}), 400
todo = Todo(
title=data['title'],
description=data.get('description', ''),
completed=data.get('completed', False)
)
db.session.add(todo)
db.session.commit()
return jsonify(todo.to_dict()), 201
@app.route('/api/todos/<int:todo_id>', methods=['PUT'])
def update_todo(todo_id):
"""更新待办事项"""
todo = Todo.query.get_or_404(todo_id)
data = request.json
if 'title' in data:
todo.title = data['title']
if 'description' in data:
todo.description = data['description']
if 'completed' in data:
todo.completed = data['completed']
db.session.commit()
return jsonify(todo.to_dict())
@app.route('/api/todos/<int:todo_id>', methods=['DELETE'])
def delete_todo(todo_id):
"""删除待办事项"""
todo = Todo.query.get_or_404(todo_id)
db.session.delete(todo)
db.session.commit()
return jsonify({'message': '待办事项已删除'}), 200
# Web界面
@app.route('/')
def index():
return render_template('todo_index.html')
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
templates/todo_index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>待办事项管理</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.completed {
text-decoration: line-through;
color: #6c757d;
}
.todo-item {
transition: all 0.3s ease;
}
.todo-item:hover {
background-color: #f8f9fa;
}
</style>
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4">待办事项管理</h1>
<!-- 添加新待办事项 -->
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">添加新事项</h5>
<form id="addTodoForm">
<div class="mb-3">
<label for="title" class="form-label">标题</label>
<input type="text" class="form-control" id="title" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">描述</label>
<textarea class="form-control" id="description" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
</div>
</div>
<!-- 待办事项列表 -->
<div class="card">
<div class="card-body">
<h5 class="card-title">待办事项列表</h5>
<div id="todoList" class="list-group">
<!-- 动态加载待办事项 -->
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 页面加载时获取待办事项
document.addEventListener('DOMContentLoaded', function() {
loadTodos();
// 表单提交事件
document.getElementById('addTodoForm').addEventListener('submit', function(e) {
e.preventDefault();
addTodo();
});
});
// 加载待办事项
async function loadTodos() {
try {
const response = await fetch('/api/todos');
const todos = await response.json();
renderTodos(todos);
} catch (error) {
console.error('加载待办事项失败:', error);
}
}
// 渲染待办事项
function renderTodos(todos) {
const todoList = document.getElementById('todoList');
todoList.innerHTML = '';
todos.forEach(todo => {
const todoItem = document.createElement('div');
todoItem.className = `list-group-item todo-item ${todo.completed ? 'completed' : ''}`;
todoItem.innerHTML = `
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-1">${todo.title}</h6>
<p class="mb-1 text-muted">${todo.description || '无描述'}</p>
<small>创建时间: ${new Date(todo.created_at).toLocaleString()}</small>
</div>
<div>
<button class="btn btn-sm ${todo.completed ? 'btn-success' : 'btn-outline-success'}"
onclick="toggleTodo(${todo.id})">
${todo.completed ? '已完成' : '标记完成'}
</button>
<button class="btn btn-sm btn-outline-danger ms-2"
onclick="deleteTodo(${todo.id})">
删除
</button>
</div>
</div>
`;
todoList.appendChild(todoItem);
});
}
// 添加待办事项
async function addTodo() {
const title = document.getElementById('title').value;
const description = document.getElementById('description').value;
try {
const response = await fetch('/api/todos', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: title,
description: description,
completed: false
})
});
if (response.ok) {
document.getElementById('addTodoForm').reset();
loadTodos();
}
} catch (error) {
console.error('添加待办事项失败:', error);
}
}
// 切换待办事项状态
async function toggleTodo(id) {
try {
// 先获取当前状态
const response = await fetch(`/api/todos/${id}`);
const todo = await response.json();
// 更新状态
await fetch(`/api/todos/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
completed: !todo.completed
})
});
loadTodos();
} catch (error) {
console.error('更新待办事项失败:', error);
}
}
// 删除待办事项
async function deleteTodo(id) {
if (confirm('确定要删除这个待办事项吗?')) {
try {
await fetch(`/api/todos/${id}`, {
method: 'DELETE'
});
loadTodos();
} catch (error) {
console.error('删除待办事项失败:', error);
}
}
}
</script>
</body>
</html>
高级功能:用户认证系统
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'your-very-secret-key-here'
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
# 用户模型
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(200), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
is_active = db.Column(db.Boolean, default=True)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def get_id(self):
return str(self.id)
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# 路由
@app.route('/')
def index():
return render_template('auth_index.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
email = request.form['email']
password = request.form['password']
# 检查用户是否已存在
if User.query.filter_by(username=username).first():
flash('用户名已存在')
return redirect(url_for('register'))
if User.query.filter_by(email=email).first():
flash('邮箱已存在')
return redirect(url_for('register'))
# 创建新用户
user = User(username=username, email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()
flash('注册成功,请登录')
return redirect(url_for('login'))
return render_template('register.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
login_user(user)
flash('登录成功')
return redirect(url_for('dashboard'))
else:
flash('用户名或密码错误')
return render_template('login.html')
@app.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html', user=current_user)
@app.route('/logout')
@login_required
def logout():
logout_user()
flash('已退出登录')
return redirect(url_for('index'))
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
高级功能
1. 蓝图(Blueprints) - 模块化应用
# auth/__init__.py
from flask import Blueprint
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
from . import routes
# auth/routes.py
from flask import render_template, request, redirect, url_for, flash
from . import auth_bp
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
# 登录逻辑
return render_template('auth/login.html')
@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
# 注册逻辑
return render_template('auth/register.html')
# main.py
from flask import Flask
from auth import auth_bp
app = Flask(__name__)
app.register_blueprint(auth_bp)
2. RESTful API开发
from flask import Flask, request, jsonify
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class UserResource(Resource):
def get(self, user_id=None):
if user_id:
# 返回单个用户
return {'id': user_id, 'name': '张三'}
else:
# 返回用户列表
return [{'id': 1, 'name': '张三'}, {'id': 2, 'name': '李四'}]
def post(self):
data = request.json
# 创建新用户
return {'id': 3, 'name': data['name']}, 201
def put(self, user_id):
data = request.json
# 更新用户
return {'id': user_id, 'name': data['name']}
def delete(self, user_id):
# 删除用户
return {'message': '用户已删除'}, 204
api.add_resource(UserResource, '/users', '/users/<int:user_id>')
3. 文件上传
from flask import Flask, request, render_template
import os
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['