8、Python Web开发基础 - Flask框架

105 阅读13分钟

Python Web开发基础 - Flask框架

1. Web开发概述

作为Java开发者,您可能已经熟悉了Spring Boot等Web框架。在Python世界中,有多种优秀的Web框架可供选择,其中Flask是一个轻量级、灵活且易于学习的框架,非常适合快速构建Web应用。

Flask框架

2. Flask简介

Flask是一个微型Web框架,由Werkzeug(WSGI工具库)和Jinja(模板引擎)组成。它被设计为简单、灵活且可扩展,适合从小型应用到复杂项目的各种需求。

Flask的特点

  • 轻量级:核心简洁,但可通过扩展增加功能
  • 灵活性:不强制特定的项目结构或依赖
  • 易于学习:API简单直观,文档完善
  • 可扩展:丰富的扩展生态系统
  • WSGI兼容:基于Werkzeug,符合WSGI标准

Flask vs Django

特性FlaskDjango
类型微框架全栈框架
学习曲线平缓较陡
灵活性中等
内置功能丰富
适用场景小型到中型应用,API服务大型复杂应用
项目结构自由定义相对固定

3. 安装Flask

使用pip安装Flask非常简单:

# 创建虚拟环境
python -m venv venv

# 激活虚拟环境
# Windows
venv\Scripts\activate
# macOS/Linux
source venv/bin/activate

# 安装Flask
pip install flask

4. 第一个Flask应用

让我们创建一个简单的"Hello World"应用:

# app.py
from flask import Flask

# 创建Flask应用实例
app = Flask(__name__)

# 定义路由和视图函数
@app.route('/')
def hello_world():
    return 'Hello, World!'

# 启动应用
if __name__ == '__main__':
    app.run(debug=True)

运行应用:

python app.py

访问 http://127.0.0.1:5000/ 即可看到"Hello, World!"消息。

理解代码

  • Flask(__name__) 创建Flask应用实例,__name__是当前模块的名称
  • @app.route('/') 是一个装饰器,将URL路径映射到视图函数
  • app.run(debug=True) 启动开发服务器,debug=True启用调试模式

5. 路由和视图

Flask请求处理流程

sequenceDiagram
    participant 客户端
    participant WSGI服务器
    participant Flask应用
    participant 路由系统
    participant 视图函数
    participant 模板引擎
    
    客户端->>WSGI服务器: HTTP请求
    WSGI服务器->>Flask应用: 转发请求
    Flask应用->>路由系统: 查找匹配路由
    路由系统->>视图函数: 调用对应视图函数
    视图函数->>模板引擎: 渲染模板(可选)
    模板引擎-->>视图函数: 返回渲染结果
    视图函数-->>Flask应用: 返回响应
    Flask应用-->>WSGI服务器: 返回响应
    WSGI服务器-->>客户端: HTTP响应
    
    Note over 路由系统,视图函数: 中间件和钩子函数在请求处理过程中执行

基本路由

@app.route('/')
def index():
    return '首页'

@app.route('/about')
def about():
    return '关于我们'

动态路由

@app.route('/user/<username>')
def show_user_profile(username):
    return f'用户: {username}'

@app.route('/post/<int:post_id>')
def show_post(post_id):
    return f'帖子ID: {post_id}'

动态路由部分可以指定类型:

  • string(默认):接受任何不包含斜杠的文本
  • int:接受正整数
  • float:接受正浮点数
  • path:接受包含斜杠的文本
  • uuid:接受UUID字符串

HTTP方法

默认情况下,路由只响应GET请求。可以通过methods参数指定允许的HTTP方法:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # 处理表单提交
        return '处理登录'
    else:
        # 显示登录表单
        return '显示登录表单'

URL构建

使用url_for()函数可以根据视图函数名生成URL:

from flask import url_for

@app.route('/')
def index():
    # 生成'/user/john'的URL
    user_url = url_for('show_user_profile', username='john')
    return f'用户页面: {user_url}'

6. 请求和响应

请求对象

Flask提供了全局的request对象来访问请求数据:

from flask import request

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    
    # 处理登录逻辑
    if username == 'admin' and password == 'secret':
        return '登录成功'
    return '登录失败'

常用的请求属性和方法:

  • request.form:表单数据(POST请求)
  • request.args:URL参数(查询字符串)
  • request.cookies:Cookie数据
  • request.files:上传的文件
  • request.headers:HTTP头信息
  • request.method:HTTP请求方法
  • request.path:请求路径
  • request.remote_addr:客户端IP地址

响应对象

Flask视图函数可以返回多种类型的响应:

from flask import make_response, jsonify, redirect, url_for

# 返回字符串(自动转换为响应对象)
@app.route('/hello')
def hello():
    return 'Hello World'

# 返回自定义响应
@app.route('/custom')
def custom_response():
    response = make_response('Custom Response')
    response.status_code = 200
    response.headers['Custom-Header'] = 'Value'
    return response

# 返回JSON
@app.route('/api/data')
def get_data():
    data = {
        'name': '张三',
        'age': 30,
        'city': '北京'
    }
    return jsonify(data)

# 重定向
@app.route('/redirect')
def redirect_example():
    return redirect(url_for('hello'))

7. 模板渲染

Flask使用Jinja2作为模板引擎,可以将Python变量传递到HTML模板中:

目录结构

myapp/
  ├── app.py
  └── templates/
      ├── base.html
      └── index.html

模板文件

<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}{% endblock %} - 我的应用</title>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">首页</a>
        <a href="{{ url_for('about') }}">关于</a>
    </nav>
    <div class="content">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

<!-- templates/index.html -->
{% extends 'base.html' %}

{% block title %}首页{% endblock %}

{% block content %}
    <h1>欢迎, {{ name }}!</h1>
    
    <h2>用户列表:</h2>
    <ul>
        {% for user in users %}
            <li>{{ user.name }} - {{ user.email }}</li>
        {% endfor %}
    </ul>
{% endblock %}

渲染模板

from flask import render_template

@app.route('/')
def index():
    name = '张三'
    users = [
        {'name': '张三', 'email': 'zhang@example.com'},
        {'name': '李四', 'email': 'li@example.com'},
        {'name': '王五', 'email': 'wang@example.com'}
    ]
    return render_template('index.html', name=name, users=users)

Jinja2模板语法

  • {{ ... }}:输出表达式的值
  • {% ... %}:控制结构(if、for等)
  • {# ... #}:注释
  • {% extends '...' %}:模板继承
  • {% block ... %} {% endblock %}:定义可被子模板覆盖的块

8. 静态文件

Flask自动添加一个static路由来提供静态文件:

目录结构

myapp/
  ├── app.py
  ├── static/
  │   ├── css/
  │   │   └── style.css
  │   ├── js/
  │   │   └── script.js
  │   └── images/
  │       └── logo.png
  └── templates/
      └── index.html

在模板中引用静态文件

<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo">

9. 表单处理

HTML表单

<!-- templates/login.html -->
{% extends 'base.html' %}

{% block title %}登录{% endblock %}

{% block content %}
    <h1>登录</h1>
    
    {% if error %}
        <div class="error">{{ error }}</div>
    {% endif %}
    
    <form method="post">
        <div>
            <label for="username">用户名:</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div>
            <label for="password">密码:</label>
            <input type="password" id="password" name="password" required>
        </div>
        <div>
            <button type="submit">登录</button>
        </div>
    </form>
{% endblock %}

处理表单提交

from flask import request, render_template, redirect, url_for, session

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        
        # 简单的验证逻辑(实际应用中应使用数据库和密码哈希)
        if username == 'admin' and password == 'password':
            session['username'] = username  # 存储用户会话
            return redirect(url_for('dashboard'))
        else:
            error = '无效的用户名或密码'
    
    return render_template('login.html', error=error)

@app.route('/dashboard')
def dashboard():
    # 检查用户是否已登录
    if 'username' not in session:
        return redirect(url_for('login'))
    
    return render_template('dashboard.html', username=session['username'])

使用Flask-WTF扩展

Flask-WTF是一个集成了WTForms的Flask扩展,提供了更强大的表单处理功能:

pip install flask-wtf
from flask import Flask, render_template, redirect, url_for, flash
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'  # 用于CSRF保护

# 定义表单类
class LoginForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired()])
    password = PasswordField('密码', validators=[DataRequired(), Length(min=6)])
    submit = SubmitField('登录')

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    
    if form.validate_on_submit():
        # 表单验证通过
        username = form.username.data
        password = form.password.data
        
        # 处理登录逻辑
        if username == 'admin' and password == 'password':
            flash('登录成功!', 'success')
            return redirect(url_for('dashboard'))
        else:
            flash('无效的用户名或密码', 'error')
    
    return render_template('login_wtf.html', form=form)
<!-- templates/login_wtf.html -->
{% extends 'base.html' %}

{% block title %}登录{% endblock %}

{% block content %}
    <h1>登录</h1>
    
    {% with messages = get_flashed_messages(with_categories=true) %}
        {% if messages %}
            {% for category, message in messages %}
                <div class="{{ category }}">{{ message }}</div>
            {% endfor %}
        {% endif %}
    {% endwith %}
    
    <form method="post">
        {{ form.hidden_tag() }}
        <div>
            {{ form.username.label }}
            {{ form.username() }}
            {% if form.username.errors %}
                <div class="errors">
                    {% for error in form.username.errors %}
                        <span>{{ error }}</span>
                    {% endfor %}
                </div>
            {% endif %}
        </div>
        <div>
            {{ form.password.label }}
            {{ form.password() }}
            {% if form.password.errors %}
                <div class="errors">
                    {% for error in form.password.errors %}
                        <span>{{ error }}</span>
                    {% endfor %}
                </div>
            {% endif %}
        </div>
        <div>
            {{ form.submit() }}
        </div>
    </form>
{% endblock %}

10. 会话和Cookie

会话(Session)

Flask提供了session对象来存储跨请求的用户数据:

from flask import session

app.config['SECRET_KEY'] = 'your-secret-key'  # 必须设置密钥

@app.route('/set_session')
def set_session():
    session['username'] = '张三'
    return '会话已设置'

@app.route('/get_session')
def get_session():
    username = session.get('username', '未知用户')
    return f'当前用户: {username}'

@app.route('/clear_session')
def clear_session():
    session.pop('username', None)  # 删除特定键
    # session.clear()  # 清除整个会话
    return '会话已清除'

Cookie

from flask import make_response

@app.route('/set_cookie')
def set_cookie():
    response = make_response('Cookie已设置')
    response.set_cookie('user_id', '123', max_age=3600)  # 过期时间为1小时
    return response

@app.route('/get_cookie')
def get_cookie():
    user_id = request.cookies.get('user_id', '未知')
    return f'用户ID: {user_id}'

@app.route('/clear_cookie')
def clear_cookie():
    response = make_response('Cookie已清除')
    response.delete_cookie('user_id')
    return response

11. 数据库集成

Flask本身不包含数据库抽象层,但可以轻松集成各种数据库。

使用SQLAlchemy(ORM)

SQLAlchemy是Python中最流行的ORM工具,Flask-SQLAlchemy是其Flask扩展:

pip install flask-sqlalchemy
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

# 定义模型
class User(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)
    posts = db.relationship('Post', backref='author', lazy=True)
    
    def __repr__(self):
        return f'<User {self.username}>'

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    
    def __repr__(self):
        return f'<Post {self.title}>'

# 创建数据库表
with app.app_context():
    db.create_all()

# 添加数据
@app.route('/add_user')
def add_user():
    user = User(username='张三', email='zhang@example.com')
    db.session.add(user)
    db.session.commit()
    return '用户已添加'

# 查询数据
@app.route('/users')
def list_users():
    users = User.query.all()
    result = '<h1>用户列表</h1><ul>'
    for user in users:
        result += f'<li>{user.username} - {user.email}</li>'
    result += '</ul>'
    return result

12. RESTful API开发

Flask非常适合构建RESTful API:

from flask import Flask, jsonify, request, abort

app = Flask(__name__)

# 模拟数据库
USERS = [
    {'id': 1, 'name': '张三', 'email': 'zhang@example.com'},
    {'id': 2, 'name': '李四', 'email': 'li@example.com'}
]

# 获取所有用户
@app.route('/api/users', methods=['GET'])
def get_users():
    return jsonify({'users': USERS})

# 获取单个用户
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = next((user for user in USERS if user['id'] == user_id), None)
    if user is None:
        abort(404)  # 返回404错误
    return jsonify({'user': user})

# 创建新用户
@app.route('/api/users', methods=['POST'])
def create_user():
    if not request.json or 'name' not in request.json or 'email' not in request.json:
        abort(400)  # 返回400错误
    
    user = {
        'id': USERS[-1]['id'] + 1 if USERS else 1,
        'name': request.json['name'],
        'email': request.json['email']
    }
    USERS.append(user)
    return jsonify({'user': user}), 201  # 返回201状态码

# 更新用户
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    user = next((user for user in USERS if user['id'] == user_id), None)
    if user is None:
        abort(404)
    
    if not request.json:
        abort(400)
    
    user['name'] = request.json.get('name', user['name'])
    user['email'] = request.json.get('email', user['email'])
    
    return jsonify({'user': user})

# 删除用户
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    user = next((user for user in USERS if user['id'] == user_id), None)
    if user is None:
        abort(404)
    
    USERS.remove(user)
    return jsonify({'result': True})

# 自定义错误处理
@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Not found'}), 404

@app.errorhandler(400)
def bad_request(error):
    return jsonify({'error': 'Bad request'}), 400

13. 项目结构

随着应用规模增长,良好的项目结构变得重要:

简单的项目结构

myapp/
  ├── app.py                  # 应用入口
  ├── config.py               # 配置文件
  ├── models.py               # 数据模型
  ├── forms.py                # 表单定义
  ├── routes.py               # 路由和视图
  ├── static/                 # 静态文件
  │   ├── css/
  │   ├── js/
  │   └── images/
  └── templates/              # 模板文件
      ├── base.html
      ├── index.html
      └── ...

使用蓝图(Blueprint)组织大型应用

蓝图允许将应用分割成多个模块:

myapp/
  ├── app.py                  # 应用入口
  ├── config.py               # 配置文件
  ├── models.py               # 数据模型
  ├── extensions.py           # 扩展实例化
  ├── blueprints/             # 蓝图模块
  │   ├── main/               # 主蓝图
  │   │   ├── __init__.py
  │   │   ├── routes.py
  │   │   └── forms.py
  │   ├── auth/               # 认证蓝图
  │   │   ├── __init__.py
  │   │   ├── routes.py
  │   │   └── forms.py
  │   └── api/                # API蓝图
  │       ├── __init__.py
  │       └── routes.py
  ├── static/                 # 静态文件
  └── templates/              # 模板文件
      ├── base.html
      ├── main/
      ├── auth/
      └── ...

蓝图实现示例:

# blueprints/main/__init__.py
from flask import Blueprint

main = Blueprint('main', __name__)

from . import routes
# blueprints/main/routes.py
from flask import render_template
from . import main

@main.route('/')
def index():
    return render_template('main/index.html')

@main.route('/about')
def about():
    return render_template('main/about.html')
# app.py
from flask import Flask
from config import Config
from extensions import db
from blueprints.main import main
from blueprints.auth import auth
from blueprints.api import api

def create_app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(config_class)
    
    # 初始化扩展
    db.init_app(app)
    
    # 注册蓝图
    app.register_blueprint(main)
    app.register_blueprint(auth, url_prefix='/auth')
    app.register_blueprint(api, url_prefix='/api')
    
    return app

if __name__ == '__main__':
    app = create_app()
    app.run(debug=True)

14. 部署Flask应用

生产环境配置

在生产环境中,应该禁用调试模式并使用适当的WSGI服务器:

# 禁用调试模式
app.run(debug=False)

使用Gunicorn部署

Gunicorn是一个Python WSGI HTTP服务器:

# 安装Gunicorn
pip install gunicorn

# 运行应用
gunicorn -w 4 -b 127.0.0.1:8000 app:app

使用uWSGI部署

uWSGI是另一个流行的WSGI服务器:

# 安装uWSGI
pip install uwsgi

# 运行应用
uwsgi --http 127.0.0.1:8000 --module app:app

使用Nginx和WSGI服务器

在生产环境中,通常使用Nginx作为反向代理,配合WSGI服务器:

Client <-> Nginx <-> WSGI Server (Gunicorn/uWSGI) <-> Flask Application

15. 练习:构建简单的博客应用

让我们创建一个简单的博客应用,包含以下功能:

  • 查看博客文章列表
  • 查看单篇文章详情
  • 添加新文章(需要登录)
  • 用户注册和登录

项目结构

blog/
  ├── app.py
  ├── config.py
  ├── models.py
  ├── forms.py
  ├── static/
  │   └── css/
  │       └── style.css
  └── templates/
      ├── base.html
      ├── index.html
      ├── post.html
      ├── create_post.html
      ├── login.html
      └── register.html

实现代码

# config.py
import os

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///blog.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
# models.py
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(128))
    posts = db.relationship('Post', backref='author', lazy='dynamic')
    
    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 __repr__(self):
        return f'<User {self.username}>'

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    
    def __repr__(self):
        return f'<Post {self.title}>'
# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, EqualTo, Length, ValidationError
from models import User

class LoginForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired()])
    password = PasswordField('密码', validators=[DataRequired()])
    submit = SubmitField('登录')

class RegistrationForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired(), Length(min=2, max=20)])
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('密码', validators=[DataRequired()])
    confirm_password = PasswordField('确认密码', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('注册')
    
    def validate_username(self, username):
        user = User.query.filter_by(username=username.data).first()
        if user:
            raise ValidationError('该用户名已被使用')
    
    def validate_email(self, email):
        user = User.query.filter_by(email=email.data).first()
        if user:
            raise ValidationError('该邮箱已被注册')

class PostForm(FlaskForm):
    title = StringField('标题', validators=[DataRequired()])
    content = TextAreaField('内容', validators=[DataRequired()])
    submit = SubmitField('发布')
# app.py
from flask import Flask, render_template, redirect, url_for, flash, request, abort
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from config import Config
from models import db, User, Post
from forms import LoginForm, RegistrationForm, PostForm

app = Flask(__name__)
app.config.from_object(Config)

# 初始化扩展
db.init_app(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

# 创建数据库表
with app.app_context():
    db.create_all()

# 路由
@app.route('/')
def index():
    page = request.args.get('page', 1, type=int)
    posts = Post.query.order_by(Post.timestamp.desc()).paginate(page=page, per_page=5)
    return render_template('index.html', posts=posts)

@app.route('/post/<int:post_id>')
def post(post_id):
    post = Post.query.get_or_404(post_id)
    return render_template('post.html', post=post)

@app.route('/post/new', methods=['GET', 'POST'])
@login_required
def new_post():
    form = PostForm()
    if form.validate_on_submit():
        post = Post(title=form.title.data, content=form.content.data, author=current_user)
        db.session.add(post)
        db.session.commit()
        flash('文章已发布!', 'success')
        return redirect(url_for('index'))
    return render_template('create_post.html', form=form, title='新文章')

@app.route('/post/<int:post_id>/update', methods=['GET', 'POST'])
@login_required
def update_post(post_id):
    post = Post.query.get_or_404(post_id)
    if post.author != current_user:
        abort(403)
    form = PostForm()
    if form.validate_on_submit():
        post.title = form.title.data
        post.content = form.content.data
        db.session.commit()
        flash('文章已更新!', 'success')
        return redirect(url_for('post', post_id=post.id))
    elif request.method == 'GET':
        form.title.data = post.title
        form.content.data = post.content
    return render_template('create_post.html', form=form, title='更新文章')

@app.route('/post/<int:post_id>/delete', methods=['POST'])
@login_required
def delete_post(post_id):
    post = Post.query.get_or_404(post_id)
    if post.author != current_user:
        abort(403)
    db.session.delete(post)
    db.session.commit()
    flash('文章已删除!', 'success')
    return redirect(url_for('index'))

@app.route('/register', methods=['GET', 'POST'])
def register():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(username=form.username.data, email=form.email.data)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('注册成功!现在您可以登录了。', 'success')
        return redirect(url_for('login'))
    return render_template('register.html', form=form, title='注册')

@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user and user.check_password(form.password.data):
            login_user(user)
            next_page = request.args.get('next')
            return redirect(next_page or url_for('index'))
        else:
            flash('登录失败。请检查用户名和密码。', 'danger')
    return render_template('login.html', form=form, title='登录')

@app.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)

模板文件

<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}博客{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
    <header>
        <nav>
            <div class="container">
                <h1><a href="{{ url_for('index') }}">我的博客</a></h1>
                <ul>
                    <li><a href="{{ url_for('index') }}">首页</a></li>
                    {% if current_user.is_authenticated %}
                        <li><a href="{{ url_for('new_post') }}">写文章</a></li>
                        <li><a href="{{ url_for('logout') }}">退出</a></li>
                    {% else %}
                        <li><a href="{{ url_for('login') }}">登录</a></li>
                        <li><a href="{{ url_for('register') }}">注册</a></li>
                    {% endif %}
                </ul>
            </div>
        </nav>
    </header>
    
    <main class="container">
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="alert alert-{{ category }}">{{ message }}</div>
                {% endfor %}
            {% endif %}
        {% endwith %}
        
        {% block content %}{% endblock %}
    </main>
    
    <footer>
        <div class="container">
            <p>&copy; 2025 我的博客</p>
        </div>
    </footer>
</body>
</html>

<!-- templates/index.html -->
{% extends 'base.html' %}

{% block title %}首页{% endblock %}

{% block content %}
    <h1>最新文章</h1>
    
    {% for post in posts.items %}
        <article class="post">
            <h2><a href="{{ url_for('post', post_id=post.id) }}">{{ post.title }}</a></h2>
            <div class="post-info">
                <span>作者: {{ post.author.username }}</span>
                <span>发布时间: {{ post.timestamp.strftime('%Y-%m-%d %H:%M') }}</span>
            </div>
            <div class="post-content">
                {{ post.content[:200] }}{% if post.content|length > 200 %}...{% endif %}
            </div>
            <a href="{{ url_for('post', post_id=post.id) }}" class="read-more">阅读更多</a>
        </article>
    {% endfor %}
    
    <!-- 分页 -->
    <div class="pagination">
        {% if posts.has_prev %}
            <a href="{{ url_for('index', page=posts.prev_num) }}">&laquo; 上一页</a>
        {% endif %}
        
        {% for page_num in posts.iter_pages() %}
            {% if page_num %}
                {% if page_num == posts.page %}
                    <span class="current-page">{{ page_num }}</span>
                {% else %}
                    <a href="{{ url_for('index', page=page_num) }}">{{ page_num }}</a>
                {% endif %}
            {% else %}
                <span>...</span>
            {% endif %}
        {% endfor %}
        
        {% if posts.has_next %}
            <a href="{{ url_for('index', page=posts.next_num) }}">下一页 &raquo;</a>
        {% endif %}
    </div>
{% endblock %}

CSS样式

/* static/css/style.css */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    line-height: 1.6;
    color: #333;
    background-color: #f4f4f4;
}

.container {
    width: 80%;
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 15px;
}

/* 导航栏 */
header {
    background-color: #333;
    color: #fff;
    padding: 1rem 0;
}

nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

nav h1 {
    margin: 0;
}

nav a {
    color: #fff;
    text-decoration: none;
}

nav ul {
    display: flex;
    list-style: none;
}

nav ul li {
    margin-left: 1rem;
}

/* 主内容区 */
main {
    padding: 2rem 0;
}

/* 文章 */
.post {
    background-color: #fff;
    margin-bottom: 2rem;
    padding: 1.5rem;
    border-radius: 5px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.post h2 {
    margin-bottom: 0.5rem;
}

.post h2 a {
    color: #333;
    text-decoration: none;
}

.post-info {
    color: #777;
    margin-bottom: 1rem;
    font-size: 0.9rem;
}

.post-info span {
    margin-right: 1rem;
}

.post-content {
    margin-bottom: 1rem;
}

.read-more {
    display: inline-block;
    background-color: #333;
    color: #fff;
    padding: 0.5rem 1rem;
    text-decoration: none;
    border-radius: 3px;
}

/* 表单 */
form {
    background-color: #fff;
    padding: 1.5rem;
    border-radius: 5px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.form-group {
    margin-bottom: 1rem;
}

label {
    display: block;
    margin-bottom: 0.5rem;
}

input, textarea {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #ddd;
    border-radius: 3px;
}

textarea {
    height: 200px;
}

button {
    background-color: #333;
    color: #fff;
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 3px;
    cursor: pointer;
}

/* 提示消息 */
.alert {
    padding: 1rem;
    margin-bottom: 1rem;
    border-radius: 3px;
}

.alert-success {
    background-color: #d4edda;
    color: #155724;
}

.alert-danger {
    background-color: #f8d7da;
    color: #721c24;
}

/* 分页 */
.pagination {
    margin-top: 2rem;
    text-align: center;
}

.pagination a, .pagination span {
    display: inline-block;
    padding: 0.5rem 1rem;
    margin: 0 0.2rem;
    border-radius: 3px;
}

.pagination a {
    background-color: #333;
    color: #fff;
    text-decoration: none;
}

.pagination .current-page {
    background-color: #777;
    color: #fff;
}

/* 页脚 */
footer {
    background-color: #333;
    color: #fff;
    text-align: center;
    padding: 1rem 0;
    margin-top: 2rem;
}

16. 今日总结

  • Flask是一个轻量级、灵活的Python Web框架
  • Flask的核心组件包括路由、视图函数、模板和静态文件
  • Flask使用Jinja2作为模板引擎,可以将Python变量传递到HTML模板中
  • Flask提供了处理表单、会话和Cookie的功能
  • Flask可以轻松集成数据库,如SQLAlchemy
  • Flask适合构建RESTful API
  • 随着应用规模增长,可以使用蓝图组织代码
  • 在生产环境中,应该使用WSGI服务器(如Gunicorn或uWSGI)部署Flask应用

17. 明日预告

明天我们将学习Django框架,这是Python中另一个流行的Web框架。与Flask不同,Django是一个全栈框架,提供了更多内置功能,如ORM、管理后台、表单处理等。我们将比较Flask和Django的异同,并学习如何使用Django构建Web应用。