1. 为什么不用原生form了之前的写法title request.form.get(title)if not title:flash(标题不能为空)能工作但页面一多就会重复很多类似代码。Flask-WTF 帮你集中处理能力原生表单Flask-WTF必填校验自己写 ifDataRequired()错误提示自己 flashform.title.errorsCSRF 防伪造没有自动带csrf_token编辑页回填手动赋值form.title.data row.title入门项目用 WTForms后面会省很多事。2. 安装 Flask-WTFpip install Flask-WTFapp/__init__.py里已有SECRET_KEYWTForms 会用它生成 CSRF token不用额外配置。3. 定义表单app/forms.py新建文件写NoteFormfrom flask_wtf import FlaskFormfrom wtforms import StringField, TextAreaField, SubmitFieldfrom wtforms.validators import DataRequired, Length, Optionalclass NoteForm(FlaskForm):title StringField(标题,validators[DataRequired(请输入标题),Length(max100, message标题最多 100 个字),],)content TextAreaField(内容,validators[Optional()],)submit SubmitField(保存)几个常用校验器DataRequired— 不能为空Length(max100)— 长度限制Optional— 可以为空4. 改造「新增」页面视图app/home/views.pyfrom flask import render_template, redirect, url_for, flashfrom app import dbfrom app.home import homefrom app.forms import NoteFormfrom app.models import Notehome.route(/notes/add/, methods[GET, POST])def note_add():form NoteForm()if form.validate_on_submit():note Note(titleform.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, formform, title新增备忘录)和之前学习的form的区别用form.validate_on_submit()代替手写的if not title校验失败时表单会保留用户已填的内容模板app/templates/home/note_form.html新增和编辑共用这一个模板!DOCTYPE htmlhtmlheadmeta charsetutf-8title{{ title }}/title/headbodyh1{{ title }}/h1{% with messages get_flashed_messages() %}{% for msg in messages %}p stylecolor:green;{{ msg }}/p{% endfor %}{% endwith %}form methodpost{{ form.csrf_token }}p{{ form.title.label }}br{{ form.title(size40) }}{% for err in form.title.errors %}span stylecolor:red;{{ err }}/span{% endfor %}/pp{{ form.content.label }}br{{ form.content(rows5, cols40) }}{% for err in form.content.errors %}span stylecolor:red;{{ err }}/span{% endfor %}/pp{{ form.submit }}/pa 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.titleform.content.data row.contentif 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,formform,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, DeleteFormhome.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 classitemh3{{ note.title }}/h3p{{ note.content or 无内容 }}/pdiv classtime{{ note.addtime.strftime(%Y-%m-%d %H:%M) }}/divpa href{{ url_for(home.note_edit, note_idnote.id) }}编辑/aform methodpostaction{{ url_for(home.note_delete, note_idnote.id) }}styledisplay:inline;{{ delete_form.csrf_token }}button typesubmit οnclickreturn confirm(确定删除)删除/button/form/p/div{% endfor %}列表视图里多传一个delete_formfrom app.forms import DeleteFormhome.route(/notes/)def note_list():page request.args.get(page, 1, typeint)page_data Note.query.order_by(Note.addtime.desc()).paginate(pagepage, per_page10,)return render_template(home/note_list.html,page_datapage_data,delete_formDeleteForm(),)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显示错误复杂表单会加SelectField、DateField、自定义validate_xxx但套路不变。10. 小结这一篇核心三件事表单类 — 字段 校验规则写在一起validate_on_submit()— 统一的「提交且合法」入口CSRF — 每个 POST 表单都要 token三篇连起来你已经会篇内容一项目结构、Blueprint二数据库、列表、分页三WTForms、编辑、删除
Flask 笔记四:用 WTForms 做新增、编辑和删除
1. 为什么不用原生form了之前的写法title request.form.get(title)if not title:flash(标题不能为空)能工作但页面一多就会重复很多类似代码。Flask-WTF 帮你集中处理能力原生表单Flask-WTF必填校验自己写 ifDataRequired()错误提示自己 flashform.title.errorsCSRF 防伪造没有自动带csrf_token编辑页回填手动赋值form.title.data row.title入门项目用 WTForms后面会省很多事。2. 安装 Flask-WTFpip install Flask-WTFapp/__init__.py里已有SECRET_KEYWTForms 会用它生成 CSRF token不用额外配置。3. 定义表单app/forms.py新建文件写NoteFormfrom flask_wtf import FlaskFormfrom wtforms import StringField, TextAreaField, SubmitFieldfrom wtforms.validators import DataRequired, Length, Optionalclass NoteForm(FlaskForm):title StringField(标题,validators[DataRequired(请输入标题),Length(max100, message标题最多 100 个字),],)content TextAreaField(内容,validators[Optional()],)submit SubmitField(保存)几个常用校验器DataRequired— 不能为空Length(max100)— 长度限制Optional— 可以为空4. 改造「新增」页面视图app/home/views.pyfrom flask import render_template, redirect, url_for, flashfrom app import dbfrom app.home import homefrom app.forms import NoteFormfrom app.models import Notehome.route(/notes/add/, methods[GET, POST])def note_add():form NoteForm()if form.validate_on_submit():note Note(titleform.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, formform, title新增备忘录)和之前学习的form的区别用form.validate_on_submit()代替手写的if not title校验失败时表单会保留用户已填的内容模板app/templates/home/note_form.html新增和编辑共用这一个模板!DOCTYPE htmlhtmlheadmeta charsetutf-8title{{ title }}/title/headbodyh1{{ title }}/h1{% with messages get_flashed_messages() %}{% for msg in messages %}p stylecolor:green;{{ msg }}/p{% endfor %}{% endwith %}form methodpost{{ form.csrf_token }}p{{ form.title.label }}br{{ form.title(size40) }}{% for err in form.title.errors %}span stylecolor:red;{{ err }}/span{% endfor %}/pp{{ form.content.label }}br{{ form.content(rows5, cols40) }}{% for err in form.content.errors %}span stylecolor:red;{{ err }}/span{% endfor %}/pp{{ form.submit }}/pa 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.titleform.content.data row.contentif 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,formform,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, DeleteFormhome.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 classitemh3{{ note.title }}/h3p{{ note.content or 无内容 }}/pdiv classtime{{ note.addtime.strftime(%Y-%m-%d %H:%M) }}/divpa href{{ url_for(home.note_edit, note_idnote.id) }}编辑/aform methodpostaction{{ url_for(home.note_delete, note_idnote.id) }}styledisplay:inline;{{ delete_form.csrf_token }}button typesubmit οnclickreturn confirm(确定删除)删除/button/form/p/div{% endfor %}列表视图里多传一个delete_formfrom app.forms import DeleteFormhome.route(/notes/)def note_list():page request.args.get(page, 1, typeint)page_data Note.query.order_by(Note.addtime.desc()).paginate(pagepage, per_page10,)return render_template(home/note_list.html,page_datapage_data,delete_formDeleteForm(),)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显示错误复杂表单会加SelectField、DateField、自定义validate_xxx但套路不变。10. 小结这一篇核心三件事表单类 — 字段 校验规则写在一起validate_on_submit()— 统一的「提交且合法」入口CSRF — 每个 POST 表单都要 token三篇连起来你已经会篇内容一项目结构、Blueprint二数据库、列表、分页三WTForms、编辑、删除