当前位置: 首页 > news >正文

Flask 笔记四:用 WTForms 做新增、编辑和删除

1. 为什么不用原生<form>

之前的写法:

title = request.form.get("title")

if not title:

flash("标题不能为空")

能工作,但页面一多就会重复很多类似代码。Flask-WTF 帮你集中处理:

能力原生表单Flask-WTF

必填校验

自己写 if

DataRequired()

错误提示

自己 flash

form.title.errors

CSRF 防伪造

没有

自动带csrf_token

编辑页回填

手动赋值

form.title.data = row.title

入门项目用 WTForms,后面会省很多事。


2. 安装 Flask-WTF

pip install Flask-WTF

app/__init__.py里已有SECRET_KEY,WTForms 会用它生成 CSRF token,不用额外配置。


3. 定义表单:app/forms.py

新建文件,写NoteForm

from flask_wtf import FlaskForm

from wtforms import StringField, TextAreaField, SubmitField

from wtforms.validators import DataRequired, Length, Optional

class NoteForm(FlaskForm):

title = StringField(

"标题",

validators=[

DataRequired("请输入标题"),

Length(max=100, message="标题最多 100 个字"),

],

)

content = TextAreaField(

"内容",

validators=[Optional()],

)

submit = SubmitField("保存")

几个常用校验器:

  • DataRequired— 不能为空
  • Length(max=100)— 长度限制
  • Optional— 可以为空

4. 改造「新增」页面

视图app/home/views.py

from flask import render_template, redirect, url_for, flash

from app import db

from app.home import home

from app.forms import NoteForm

from app.models import Note

@home.route("/notes/add/", methods=["GET", "POST"])

def note_add():

form = NoteForm()

if form.validate_on_submit():

note = Note(

title=form.title.data.strip(),

content=(form.content.data or "").strip(),

)

db.session.add(note)

db.session.commit()

flash("保存成功")

return redirect(url_for("home.note_list"))

return render_template("home/note_form.html", form=form, title="新增备忘录")

和之前学习的form的区别:

  • form.validate_on_submit()代替手写的if not title
  • 校验失败时,表单会保留用户已填的内容

模板app/templates/home/note_form.html

新增和编辑共用这一个模板:

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<title>{{ title }}</title>

</head>

<body>

<h1>{{ title }}</h1>

{% with messages = get_flashed_messages() %}

{% for msg in messages %}

<p style="color:green;">{{ msg }}</p>

{% endfor %}

{% endwith %}

<form method="post">

{{ form.csrf_token }}

<p>

{{ form.title.label }}<br>

{{ form.title(size=40) }}

{% for err in form.title.errors %}

<span style="color:red;">{{ err }}</span>

{% endfor %}

</p>

<p>

{{ form.content.label }}<br>

{{ form.content(rows=5, cols=40) }}

{% for err in form.content.errors %}

<span style="color:red;">{{ err }}</span>

{% endfor %}

</p>

<p>{{ form.submit }}</p>

<a href="{{ url_for('home.note_list') }}">返回列表</a>

</form>

</body>

</html>

别忘了{{ form.csrf_token }},漏了 POST 会 400 报错。


5. 做「编辑」页面

编辑和新增逻辑很像:GET 时把数据库里的值填进表单,POST 时更新。

@home.route("/notes/edit/<int:note_id>/", methods=["GET", "POST"])

def note_edit(note_id):

row = Note.query.get_or_404(note_id)

form = NoteForm()

if request.method == "GET":

form.title.data = row.title

form.content.data = row.content

if form.validate_on_submit():

row.title = form.title.data.strip()

row.content = (form.content.data or "").strip()

db.session.commit()

flash("修改成功")

return redirect(url_for("home.note_list"))

return render_template(

"home/note_form.html",

form=form,

title="编辑备忘录",

)

get_or_404(note_id):找不到记录就返回 404,不用自己写 if。


6. 做「删除」

删除不要用 GET(链接一点就删,不安全)。用 POST + 单独的小表单。

表单app/forms.py里加一行

class DeleteForm(FlaskForm):

submit = SubmitField("确认删除")

也可以只放csrf_token,不显示 submit 按钮,用 JS 提交;入门阶段这样写最直观。

视图

from app.forms import NoteForm, DeleteForm

@home.route("/notes/delete/<int:note_id>/", methods=["POST"])

def note_delete(note_id):

row = Note.query.get_or_404(note_id)

form = DeleteForm()

if form.validate_on_submit():

db.session.delete(row)

db.session.commit()

flash("已删除")

return redirect(url_for("home.note_list"))

列表页里加编辑 / 删除

更新app/templates/home/note_list.html

{% for note in page_data.items %}

<div class="item">

<h3>{{ note.title }}</h3>

<p>{{ note.content or '(无内容)' }}</p>

<div class="time">{{ note.addtime.strftime('%Y-%m-%d %H:%M') }}</div>

<p>

<a href="{{ url_for('home.note_edit', note_id=note.id) }}">编辑</a>

<form method="post"

action="{{ url_for('home.note_delete', note_id=note.id) }}"

style="display:inline;">

{{ delete_form.csrf_token }}

<button type="submit" οnclick="return confirm('确定删除?')">删除</button>

</form>

</p>

</div>

{% endfor %}

列表视图里多传一个delete_form

from app.forms import DeleteForm

@home.route("/notes/")

def note_list():

page = request.args.get("page", 1, type=int)

page_data = Note.query.order_by(Note.addtime.desc()).paginate(

page=page, per_page=10,

)

return render_template(

"home/note_list.html",

page_data=page_data,

delete_form=DeleteForm(),

)


7. 一张图串起来

列表页 /notes/

├─ 新增 → /notes/add/ → NoteForm → db.session.add

├─ 编辑 → /notes/edit/1/ → NoteForm → 改 row 字段 → commit

└─ 删除 → POST /notes/delete/1/ → DeleteForm → db.session.delete

到这一步,一个最小的 增删改查 就齐了。


8. 新手常踩的 4 个坑

坑 1:模板里漏了csrf_token

报错类似The CSRF token is missing
每个 POST 表单都要有{{ form.csrf_token }}

坑 2:只写form.validate(),没写validate_on_submit()

validate_on_submit()= 「这次是 POST 提交 并且 校验通过」。
GET 打开页面时不应触发写入逻辑。

坑 3:编辑页 GET 和 POST 混在一个 if 里

推荐顺序:

if request.method == "GET":

form.title.data = row.title # 先回填

if form.validate_on_submit(): # 再处理提交

...

坑 4:删除用 GET 链接

<!-- 不要这样 -->

<a href="/notes/delete/1/">删除</a>

爬虫、预加载都可能误触。删除务必 POST。


9. 和真实项目的关系(不写细节,只讲习惯)

实际项目里常见做法和这篇一致:

  • 一个XxxForm管新增和编辑
  • validate_on_submit()add或改row
  • 删除单独 POST,带 CSRF
  • 模板里循环form.xxx.errors显示错误

复杂表单会加SelectFieldDateField、自定义validate_xxx,但套路不变。


10. 小结

这一篇核心三件事:

  1. 表单类 — 字段 + 校验规则写在一起
  2. validate_on_submit()— 统一的「提交且合法」入口
  3. CSRF — 每个 POST 表单都要 token

三篇连起来,你已经会:

内容

项目结构、Blueprint

数据库、列表、分页

WTForms、编辑、删除


http://www.gsyq.cn/news/1582575.html

相关文章:

  • 2026年AI测试工具深度测评:从技术原理到选型落地全解析
  • 干细胞研究领域最新发展动态观察
  • 基于Python的汽车用品销售系统的设计与实现
  • 基于GLM-4.7-Flash与OpenClaw的智能API自动化测试实践
  • Windows右键菜单终极清理指南:ContextMenuManager让你的桌面效率翻倍
  • 一人公司别再上 Jenkins,真不值
  • 主体阵地建设:如何通过企业微信API确立官方数字身份
  • 高效管理Windows右键菜单:3步打造个性化操作体验
  • 高客单价行业(房产/装修)电销机器人成功案例:话术设计与转化路径拆解
  • 接口自动化测试面试全攻略:从Pytest框架到CI/CD实战
  • Python eval()函数安全风险深度解析:从CVE-2025-2945漏洞看代码注入防御
  • NS-USBLoader:Switch玩家的终极跨平台文件管理工具
  • 智能照明实战:解锁DALI模块的多场景适配密码与案例透视
  • AMD MI300X 显卡上的显存优化与 PagedAttention 调优实战
  • Kyber AI 文档平台变革监管流程,18 个月营收增 40 倍邀你共创未来!
  • Python文件操作:二进制文件的读写(rb/wb模式)
  • 舰艇(VR)虚拟仿真训练系统
  • 9.2 入门案例:简单函数调用机器人
  • 【从0到1构建一个ClaudeAgent】规划与协调-技能
  • 三位24岁博士团队创办映界科技,补齐具身智能感知短板,2026年订单有望超千万!
  • Kimi LeetCode 3348. 最小可整除数位乘积 II Rust实现
  • 开源版Figma:Penpot,设计协同+代码生成,全栈设计平台
  • 杰理之固定通话音量【篇】
  • Xbox成就解锁终极指南:3分钟掌握免费开源工具的完整教程 [特殊字符]
  • 计算机毕业设计之高校社团招新管理系统
  • 轻智能时代开启,谁在夯实智慧家庭的“地基”?
  • NoSleep防休眠助手:5分钟掌握Windows屏幕永不停歇的智能解决方案
  • 如何快速掌握微信小程序逆向分析:wxappUnpacker完整指南与5个实用技巧
  • ripgrep:比 grep 快几十倍的命令行搜索工具
  • 深圳华智信创|华为IdeaHub会议协作平板金牌代理商