用户填了个注册表单,点"提交"——数据怎么到你的代码里?答案取决于表单的 method:GET 把数据放 URL(?name=xx),POST 把数据放请求体。服务器端分别用 request.args(GET)和 request.form(POST)读取。
表单处理的核心:读取 → 校验 → 处理 → 反馈。校验尤其重要——用户的输入永远不可信。
💡 关键直觉:表单是"门口的信箱",校验是"保安"。信件(数据)能投进来,但保安(校验)要检查内容是否合格才放行。
<!-- GET 表单:数据进 URL --> <form method="GET" action="/search"> <input type="text" name="q" placeholder="搜索"> <button>搜索</button> </form> <!-- POST 表单:数据进请求体 --> <form method="POST" action="/register"> <input type="text" name="username" placeholder="用户名"> <input type="password" name="password" placeholder="密码"> <button>注册</button> </form>
from flask import Flask, request app = Flask(__name__) # GET 表单 → request.args @app.route('/search') def search(): q = request.args.get('q', '') return f"搜索: {q}" # POST 表单 → request.form @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form.get('username') password = request.form.get('password') # 处理注册... return f"注册成功: {username}" return "显示注册表单页面"
GET vs POST 表单:
| 维度 | GET | POST |
|---|---|---|
| 数据位置 | URL 查询串 | 请求体 |
| 可见性 | 可见(浏览器历史) | 不可见 |
| 长度限制 | 有(URL 长度) | 大得多 |
| 用途 | 搜索、筛选 | 提交、上传 |
| 安全 | 敏感数据勿用 | 相对安全(仍要 HTTPS) |
@app.route('/register', methods=['POST']) def register(): username = request.form.get('username', '').strip() password = request.form.get('password', '') email = request.form.get('email', '') errors = [] if len(username) < 3: errors.append("用户名至少 3 个字符") if len(password) < 8: errors.append("密码至少 8 位") if '@' not in email: errors.append("邮箱格式不正确") if errors: return ",".join(errors), 400 # 校验通过,处理注册 return "注册成功"
校验原则:先 strip 去空格、再检查长度/格式、收集全部错误一次性返回(比"报第一个错就停"体验好)。
import os from werkzeug.utils import secure_filename 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 = request.files.get('file') if not file or not allowed_file(file.filename): return "不支持的文件类型", 400 filename = secure_filename(file.filename) file.save(os.path.join('uploads', filename)) return f"上传成功: {filename}"
文件上传要点:request.files 取文件、扩展名白名单校验、secure_filename 清理文件名防路径穿越。
⚠️ 常见坑:文件扩展名校验不够。只查扩展名可被绕过(伪装扩展名)。生产环境还要校验文件内容(magic bytes)与大小上限。
from flask import Flask, request, render_template_string app = Flask(__name__) FORM_HTML = ''' <form method="POST" action="/register"> <p><input type="text" name="username" placeholder="用户名"></p> <p><input type="password" name="password" placeholder="密码"></p> <p><input type="email" name="email" placeholder="邮箱"></p> <button>注册</button> </form> ''' @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form.get('username', '').strip() password = request.form.get('password', '') email = request.form.get('email', '') if len(username) < 3: return "用户名太短", 400 if len(password) < 8: return "密码太短", 400 if '@' not in email: return "邮箱无效", 400 return f"注册成功:{username}" return render_template_string(FORM_HTML) if __name__ == '__main__': app.run(debug=True)
先 GET 看表单,再提交各种数据观察校验反馈——"读取-校验-处理-反馈"四步完整走一遍。
手写校验代码繁琐且易漏(CSRF、类型转换、错误回显)。Flask-WTF(第三章 3.3 详述)把表单变成"类":
from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField from wtforms.validators import DataRequired, Length, Email class RegisterForm(FlaskForm): username = StringField('用户名', validators=[DataRequired(), Length(min=3)]) password = PasswordField('密码', validators=[DataRequired(), Length(min=8)]) email = StringField('邮箱', validators=[DataRequired(), Email()]) submit = SubmitField('注册')
WTF 的价值:校验声明式(写在类里)、错误回显自动、内置 CSRF 防护。复杂表单用 WTF,简单表单手写即可——按复杂度选工具。
问:request.form.get 和 request.form['key'] 区别?
get 在键不存在时返回默认值(不报错);索引访问不存在会 KeyError。优先用 get。
问:如何防止重复提交?
POST-Redirect-GET 模式:处理成功后重定向到结果页,避免刷新重复提交。
问:CSRF 是什么?
跨站请求伪造——恶意网站诱导用户向你的站点发请求。Flask-WTF 内置 CSRF token 防护,手写表单要自己实现(或用 WTF)。
问:checkbox 和 select 怎么读?request.form.get('checkbox_name') 返回 'on'(勾选)或 None;request.form.get('select_name') 返回选中的 value;多选框用 request.form.getlist('multi')。
| 来源 | 对象 | 适用场景 |
|---|---|---|
| URL 查询参数 | request.args |
GET 表单、搜索、分页 |
| 表单体 | request.form |
POST 表单 |
| JSON 体 | request.json |
AJAX/API 提交 |
小技巧:表单和 JSON 混用时代码容易乱,第三章的 Flask-WTF 会把"取值+校验"统一起来。本章先掌握手写方式,理解底层。
@app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form.get('username', '').strip() password = request.form.get('password', '') confirm = request.form.get('confirm', '') errors = [] if len(username) < 3: errors.append('用户名至少 3 个字符') if len(password) < 8: errors.append('密码至少 8 位') if password != confirm: errors.append('两次密码不一致') if errors: return render_template('register.html', errors=errors, username=username) # 校验通过:处理业务(存库等) return '注册成功!' return render_template('register.html', errors=[])
模板中展示错误:
{% if errors %} <ul class="errors"> {% for e in errors %} <li>{{ e }}</li> {% endfor %} </ul> {% endif %} <form method="post"> <input name="username" value="{{ username }}"> <input type="password" name="password"> <input type="password" name="confirm"> <button>注册</button> </form>
import os from werkzeug.utils import secure_filename from flask import request ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} UPLOAD_DIR = 'uploads' @app.route('/upload', methods=['POST']) def upload(): f = request.files.get('file') if f is None or not allowed_file(f.filename): return '文件类型不允许', 400 # secure_filename 清洗文件名,防止路径穿越 safe_name = secure_filename(f.filename) f.save(os.path.join(UPLOAD_DIR, safe_name)) return f'上传成功:{safe_name}'
安全要点:
../、非法字符,防路径穿越;app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024(16MB);| 现象 | 原因 | 解法 |
|---|---|---|
| POST 路由 405 | 路由没写 methods=['POST'] | 加上 methods |
| 取到的值都是 None | 表单 name 与取值 key 不一致 | 核对 HTML name 属性 |
| 中文乱码 | 页面编码问题 | 模板加 <meta charset="utf-8"> |
| 校验失败后输入丢失 | 没有回填 value | 用 value="{{ username }}" 回填 |
| 上传文件名异常 | 未清洗 | secure_filename + 白名单 |
| 上传大文件超时 | 未限制大小 | MAX_CONTENT_LENGTH |
表单是用户与你的应用交互最频繁的界面,设计得好不好直接决定体验。从用户视角看,好表单有三个标准:
第一,错误要"提前"且"具体"。 密码太短、邮箱格式不对——最好在用户提交时就指出具体问题,而不是笼统一句"输入有误"。本章的逐项校验、逐条错误展示就是为此设计。更进一步的体验是"边输入边校验"(前端配合),但服务端校验永远是最后防线——前端校验可以绕过。
第二,失败要"保数据"。 用户填了十个字段,因为一个密码太短被拒绝——如果十个字段全清空重填,体验极差。这就是本章回填技巧的价值:value="{{ username }}" 把已填内容保留,用户只需改错的那一项。
第三,操作要"可预期"。 提交按钮要明确("注册"而不是"确定"),危险操作(删除)要二次确认,提交中要有反馈(防止重复点击)。这些细节在第四章的项目实战中会体现。
表单与安全的两个提醒:
一个完整的表单流程回顾:GET 访问显示空白表单 → 用户填写提交(POST)→ 服务端校验 → 失败则重新渲染并回填+显示错误 → 成功则处理业务并重定向(避免刷新重复提交)。这个"PRG(Post-Redirect-Get)"模式是 Web 表单的标准流程——成功提交后重定向,能有效防止用户刷新页面导致重复提交。
关于表单的下一步:本章的手写方式让你理解了"取值→校验→回填"的完整机制;第三章的 Flask-WTF 会把"字段声明、校验规则、CSRF 防护"统一封装成表单类——到那时你会更深刻地体会框架带来的效率提升。