Flask表单基础 #

一、表单概述 #

1.1 什么是表单 #

表单是Web应用中用户与服务器交互的主要方式,用于收集用户输入的数据。

1.2 表单处理流程 #

text
用户填写表单
    ↓
提交到服务器
    ↓
Flask接收请求
    ↓
验证数据
    ↓
处理业务逻辑
    ↓
返回响应

二、HTML表单 #

2.1 基本表单 #

templates/login.html:

html
<!DOCTYPE html>
<html>
<head>
    <title>登录</title>
</head>
<body>
    <h1>登录</h1>
    <form method="POST" action="/login">
        <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>
            <label>
                <input type="checkbox" name="remember"> 记住我
            </label>
        </div>
        <button type="submit">登录</button>
    </form>
</body>
</html>

2.2 表单属性 #

属性 说明
method 提交方法(GET/POST)
action 提交URL
enctype 编码类型(multipart/form-data用于文件上传)

2.3 表单控件 #

html
<!-- 文本输入 -->
<input type="text" name="username">

<!-- 密码输入 -->
<input type="password" name="password">

<!-- 邮箱 -->
<input type="email" name="email">

<!-- 数字 -->
<input type="number" name="age" min="0" max="150">

<!-- 日期 -->
<input type="date" name="birthday">

<!-- 单选框 -->
<input type="radio" name="gender" value="male"> 男
<input type="radio" name="gender" value="female"> 女

<!-- 复选框 -->
<input type="checkbox" name="hobby" value="reading"> 阅读
<input type="checkbox" name="hobby" value="music"> 音乐

<!-- 下拉选择 -->
<select name="country">
    <option value="cn">中国</option>
    <option value="us">美国</option>
</select>

<!-- 多行文本 -->
<textarea name="content" rows="5"></textarea>

<!-- 文件上传 -->
<input type="file" name="avatar">

<!-- 隐藏字段 -->
<input type="hidden" name="token" value="xxx">

三、处理表单请求 #

3.1 接收表单数据 #

python
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # 获取表单数据
        username = request.form.get('username')
        password = request.form.get('password')
        remember = request.form.get('remember')
        
        # 处理登录逻辑
        if username and password:
            return f'登录成功: {username}'
        else:
            return '请填写完整信息'
    
    return render_template('login.html')

3.2 获取表单数据的方法 #

python
@app.route('/submit', methods=['POST'])
def submit():
    # 获取单个值
    username = request.form.get('username')
    
    # 获取值,不存在时返回默认值
    remember = request.form.get('remember', 'off')
    
    # 获取多个同名值
    hobbies = request.form.getlist('hobby')
    
    # 获取所有表单数据
    all_data = request.form.to_dict()
    
    return f'用户名: {username}, 爱好: {hobbies}'

3.3 处理文件上传 #

python
import os
from werkzeug.utils import secure_filename

UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

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

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        if 'file' not in request.files:
            return '没有文件'
        
        file = request.files['file']
        
        if file.filename == '':
            return '没有选择文件'
        
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            return '上传成功'
    
    return render_template('upload.html')

四、CSRF保护 #

4.1 什么是CSRF #

CSRF(跨站请求伪造)是一种攻击方式,攻击者诱导用户在已登录的网站上执行非预期的操作。

4.2 手动实现CSRF保护 #

python
import secrets
from flask import session

@app.route('/form', methods=['GET', 'POST'])
def form():
    if request.method == 'GET':
        # 生成CSRF令牌
        token = secrets.token_hex(32)
        session['csrf_token'] = token
        return render_template('form.html', csrf_token=token)
    
    # POST请求验证CSRF令牌
    token = request.form.get('csrf_token')
    if token != session.get('csrf_token'):
        return 'CSRF验证失败', 400
    
    # 处理表单
    return '提交成功'
html
<form method="POST">
    <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
    <!-- 其他表单字段 -->
    <button type="submit">提交</button>
</form>

五、表单验证 #

5.1 手动验证 #

python
@app.route('/register', methods=['GET', 'POST'])
def register():
    errors = {}
    
    if request.method == 'POST':
        username = request.form.get('username', '').strip()
        email = request.form.get('email', '').strip()
        password = request.form.get('password', '')
        confirm = request.form.get('confirm', '')
        
        # 验证用户名
        if not username:
            errors['username'] = '用户名不能为空'
        elif len(username) < 3:
            errors['username'] = '用户名至少3个字符'
        
        # 验证邮箱
        if not email:
            errors['email'] = '邮箱不能为空'
        elif '@' not in email:
            errors['email'] = '邮箱格式不正确'
        
        # 验证密码
        if not password:
            errors['password'] = '密码不能为空'
        elif len(password) < 6:
            errors['password'] = '密码至少6个字符'
        
        # 验证确认密码
        if password != confirm:
            errors['confirm'] = '两次密码不一致'
        
        if not errors:
            # 注册成功
            return '注册成功'
    
    return render_template('register.html', errors=errors)

5.2 在模板中显示错误 #

html
<form method="POST">
    <div>
        <label>用户名:</label>
        <input type="text" name="username" value="{{ request.form.username }}">
        {% if errors.username %}
            <span class="error">{{ errors.username }}</span>
        {% endif %}
    </div>
    
    <div>
        <label>邮箱:</label>
        <input type="email" name="email" value="{{ request.form.email }}">
        {% if errors.email %}
            <span class="error">{{ errors.email }}</span>
        {% endif %}
    </div>
    
    <div>
        <label>密码:</label>
        <input type="password" name="password">
        {% if errors.password %}
            <span class="error">{{ errors.password }}</span>
        {% endif %}
    </div>
    
    <button type="submit">注册</button>
</form>

六、表单重填 #

6.1 保留表单数据 #

python
@app.route('/edit/<int:id>', methods=['GET', 'POST'])
def edit(id):
    item = get_item(id)
    
    if request.method == 'POST':
        # 更新数据
        item.name = request.form.get('name')
        item.save()
        return redirect(url_for('view', id=id))
    
    return render_template('edit.html', item=item)
html
<form method="POST">
    <input type="text" name="name" value="{{ item.name }}">
    <button type="submit">保存</button>
</form>

6.2 错误后保留输入 #

html
<form method="POST">
    <input type="text" name="username" 
           value="{{ request.form.username or user.username }}">
    <input type="email" name="email" 
           value="{{ request.form.email or user.email }}">
    <button type="submit">保存</button>
</form>

七、多步骤表单 #

7.1 使用Session存储 #

python
@app.route('/wizard/step1', methods=['GET', 'POST'])
def wizard_step1():
    if request.method == 'POST':
        session['step1'] = {
            'name': request.form.get('name'),
            'email': request.form.get('email')
        }
        return redirect(url_for('wizard_step2'))
    
    return render_template('wizard/step1.html')

@app.route('/wizard/step2', methods=['GET', 'POST'])
def wizard_step2():
    if request.method == 'POST':
        session['step2'] = {
            'address': request.form.get('address'),
            'phone': request.form.get('phone')
        }
        return redirect(url_for('wizard_step3'))
    
    return render_template('wizard/step2.html')

@app.route('/wizard/step3', methods=['GET', 'POST'])
def wizard_step3():
    if request.method == 'POST':
        # 合并所有步骤数据
        data = {}
        data.update(session.get('step1', {}))
        data.update(session.get('step2', {}))
        data['notes'] = request.form.get('notes')
        
        # 保存数据
        save_data(data)
        
        # 清除session
        session.pop('step1', None)
        session.pop('step2', None)
        
        return redirect(url_for('wizard_done'))
    
    return render_template('wizard/step3.html')

八、表单安全 #

8.1 输入过滤 #

python
from markupsafe import escape

@app.route('/comment', methods=['POST'])
def add_comment():
    content = request.form.get('content')
    
    # 转义HTML
    safe_content = escape(content)
    
    # 或使用bleach库清理HTML
    import bleach
    clean_content = bleach.clean(content, tags=['b', 'i', 'u'])
    
    save_comment(safe_content)
    return '评论成功'

8.2 文件上传安全 #

python
import os
from werkzeug.utils import secure_filename

@app.route('/upload', methods=['POST'])
def upload():
    file = request.files['file']
    
    if file:
        # 安全文件名
        filename = secure_filename(file.filename)
        
        # 检查文件扩展名
        ext = os.path.splitext(filename)[1].lower()
        if ext not in ['.jpg', '.png', '.gif']:
            return '不允许的文件类型'
        
        # 检查文件大小
        file.seek(0, os.SEEK_END)
        size = file.tell()
        file.seek(0)
        
        if size > 5 * 1024 * 1024:  # 5MB
            return '文件太大'
        
        # 保存文件
        file.save(os.path.join(UPLOAD_FOLDER, filename))
        return '上传成功'

九、最佳实践 #

9.1 使用POST处理表单 #

python
# 推荐:使用POST
@app.route('/create', methods=['GET', 'POST'])
def create():
    if request.method == 'POST':
        # 处理表单
        pass
    return render_template('create.html')

# 不推荐:使用GET处理敏感操作
@app.route('/delete/<int:id>')  # 应该用POST
def delete(id):
    delete_item(id)
    return redirect(url_for('list'))

9.2 重定向模式 #

python
@app.route('/create', methods=['GET', 'POST'])
def create():
    if request.method == 'POST':
        # 处理表单
        item = create_item(request.form)
        
        # PRG模式:POST后重定向
        return redirect(url_for('view', id=item.id))
    
    return render_template('create.html')

9.3 表单验证分离 #

python
def validate_registration(form):
    errors = {}
    
    if not form.get('username'):
        errors['username'] = '用户名不能为空'
    
    if not form.get('email'):
        errors['email'] = '邮箱不能为空'
    
    return errors

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        errors = validate_registration(request.form)
        
        if not errors:
            create_user(request.form)
            return redirect(url_for('login'))
        
        return render_template('register.html', errors=errors)
    
    return render_template('register.html')

十、总结 #

10.1 核心要点 #

要点 说明
表单提交 POST方法
获取数据 request.form.get()
文件上传 request.files
CSRF保护 防止跨站请求伪造
数据验证 验证必填项和格式
安全处理 转义和过滤

10.2 下一步 #

现在你已经了解了表单基础,接下来让我们学习 Flask-WTF扩展,使用更强大的表单处理工具!

最后更新:2026-03-28