【Python好用到哭的库】flask-轻量级Web框架

166 阅读5分钟

库简介

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['