Flask 教程

Flask 表单处理

在 Web 开发中,表单(Form) 是用户与服务器交互的核心方式(如登录、注册、搜索)。

在 Flask 中处理表单主要有两种流派:

  1. 原生方式:直接使用 request.form 获取数据。简单直接,但需要手动处理验证和安全问题。
  2. 扩展方式(推荐):使用 Flask-WTF。它提供了表单类定义、自动数据验证和 CSRF 保护,是工业级开发的标准选择。

⚠️ 核心安全概念:CSRF

跨站请求伪造 (CSRF) 是一种常见攻击。攻击者诱导用户在已登录的状态下点击恶意链接,从而以用户身份执行非预期操作。

  • 防御方法:在每个表单中加入一个隐藏的、随机的 Token(令牌),服务器验证该 Token 是否合法。
  • Flask-WTF 会自动帮你生成和验证这个 Token。

方案一:原生表单处理(理解底层)

适合极简场景或学习原理。

1. HTML 模板 (templates/login.html)

纯文本
plaintext
<form method="POST" action="/login">
    <!-- 注意:原生方式没有 CSRF Token,存在安全隐患 -->
    <input type="text" name="username" placeholder="用户名">
    <input type="password" name="password" placeholder="密码">
    <button type="submit">登录</button>
</form>

2. Python 视图

纯文本
plaintext
from flask import request, render_template

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # 1. 获取数据
        username = request.form.get('username')
        password = request.form.get('password')

        # 2. 手动验证
        if not username or not password:
            return "请输入完整信息", 400

        # 3. 业务逻辑...
        return f"欢迎, {username}"

    return render_template('login.html')

方案二:使用 Flask-WTF(⭐ 最佳实践)

这是 Flask 社区推荐的方式。它将表单抽象为 Python 类,代码更整洁、更安全。

1. 安装与配置

纯文本
plaintext
pip install Flask-WTF

必须配置 SECRET_KEY(用于加密 CSRF Token):

纯文本
plaintext
app.config['SECRET_KEY'] = 'your-secret-key-change-in-production'

2. 定义表单类 (forms.py)

我们将表单结构定义为类,并添加验证规则。

纯文本
plaintext
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo

class RegistrationForm(FlaskForm):
    # 字段定义:标签名, 验证器列表
    username = StringField('用户名', validators=[
        DataRequired(message='用户名不能为空'),
        Length(min=3, max=20, message='长度需在3-20个字符之间')
    ])

    email = StringField('邮箱', validators=[
        DataRequired(),
        Email(message='请输入有效的邮箱地址')
    ])

    password = PasswordField('密码', validators=[DataRequired()])

    confirm_password = PasswordField('确认密码', validators=[
        DataRequired(),
        EqualTo('password', message='两次密码输入不一致')
    ])

    remember = BooleanField('记住我')

    submit = SubmitField('注册')

3. 视图函数处理 (routes.py)

Flask-WTF 提供了 validate_on_submit() 方法,它会自动判断是否是 POST 请求并执行所有验证。

纯文本
plaintext
from flask import render_template, redirect, url_for, flash
from app.forms import RegistrationForm

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()

    # 如果请求是 POST 且所有验证都通过
    if form.validate_on_submit():
        # 获取清洗后的数据
        username = form.username.data
        email = form.email.data

        # TODO: 保存用户到数据库

        flash('注册成功!请登录。', 'success')
        return redirect(url_for('login'))

    # 如果是 GET 请求,或者验证失败,渲染表单并显示错误
    return render_template('register.html', form=form)

4. 模板渲染 (templates/register.html)

在 Jinja2 中渲染表单对象。

纯文本
plaintext
<form method="POST" action="">
    <!-- ⚠️ 关键:生成 CSRF 隐藏令牌 -->
    {{ form.hidden_tag() }}

    <div>
        {{ form.username.label }}<br>
        {{ form.username(size=32) }}<br>
        <!-- 显示验证错误 -->
        {% for error in form.username.errors %}
            <span style="color: red;">[{{ error }}]</span>
        {% endfor %}
    </div>

    <div>
        {{ form.email.label }}<br>
        {{ form.email(size=32) }}<br>
        {% for error in form.email.errors %}
            <span style="color: red;">[{{ error }}]</span>
        {% endfor %}
    </div>

    <div>
        {{ form.password.label }}<br>
        {{ form.password(size=32) }}
    </div>

    <div>
        {{ form.confirm_password.label }}<br>
        {{ form.confirm_password(size=32) }}
    </div>

    <div>
        {{ form.remember() }} {{ form.remember.label }}
    </div>

    <div>
        {{ form.submit() }}
    </div>
</form>

📂 文件上传处理

文件上传是表单的特殊情况,因为数据不是文本,而是二进制流。

关键点:

  1. HTML 表单必须设置 enctype="multipart/form-data"
  2. 使用 request.files 获取文件。
  3. 安全第一:永远不要信任客户端传来的文件名,使用 secure_filename 清洗。
纯文本
plaintext
import os
from werkzeug.utils import secure_filename
from flask import request, current_app

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return 'No file part', 400

    file = request.files['file']

    if file.filename == '':
        return 'No selected file', 400

    if file and allowed_file(file.filename):
        # 清洗文件名,防止路径穿越攻击 (如 ../../etc/passwd)
        filename = secure_filename(file.filename)

        # 保存文件
        save_path = os.path.join(current_app.config['UPLOAD_FOLDER'], filename)
        file.save(save_path)

        return 'File uploaded successfully', 201

    return 'Invalid file type', 400

(注:Flask-WTF 也有 FileFieldFileAllowed 验证器,可以更方便地集成到表单类中。)


💡 最佳实践总结

  1. 始终使用 Flask-WTF:它能帮你处理 CSRF 保护和复杂的数据验证逻辑。
  2. PRG 模式 (Post/Redirect/Get):表单提交成功后,务必重定向到一个新的页面(如首页或登录页),而不是直接返回 HTML。这样可以防止用户刷新页面时重复提交表单。
  3. Flash 消息:使用 flash() 函数在重定向前存储一次性消息,在下一个页面显示“操作成功”或“验证失败”。
  4. 文件安全
  • 限制文件扩展名。
  • 使用 secure_filename
  • 最好将上传的文件存储在非 Web 根目录,或通过 Nginx 专门服务静态文件。
  1. 不要硬编码验证逻辑:将验证规则放在 validators 列表中,保持视图函数干净。

掌握了表单处理,你的应用就能真正接收用户输入了。接下来,你想学习如何将这些数据持久化存储到数据库 (Flask-SQLAlchemy),还是学习如何实现用户登录会话管理 (Flask-Login)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注