Python 现代开发学习笔记

(十六)新增、修改和删除的页面流程

1. GET 显示,POST 校验并保存

# 续写同一文件:shelf_project/src/shelfweb/books.py
@bp.route("/books/new", methods=["GET", "POST"])
@login_required
def create():
    # GET 显示空表单;POST 失败时用本次 values 重新显示。
    values, errors, status = {"title": "", "finished": False, "tags": ""}, {}, 200
    if request.method == "POST":
        draft, values, errors = read_book_form(request.form)
        status = 422
        if not errors:
            book_id, errors = service.save_book(g.user["id"], draft)
            status = 409
            if not errors:
                # service 已提交事务,随后跳转只负责重新读取。
                flash("已保存书籍", "success")
                return redirect(url_for("shelf.detail", book_id=book_id), code=303)
    return render_template(
        "books/form.html", heading="添加书籍", values=values, errors=errors
    ), status

第一次 GET 使用空的 values 和 errors。POST 失败时沿用这次输入,并交回 422;同名冲突交回 409;成功时保存完成,跳转新书详情。三个结果对应三条明确分支。

修改时先加载有权操作的书,把标签列表拼回输入框文本;然后处理本次提交。删除只接收 POST:

# 续写同一文件:shelf_project/src/shelfweb/books.py
@bp.route("/books/<int:book_id>/edit", methods=["GET", "POST"])
@login_required
def edit(book_id):
    # 先核对归属,再把持久化字段转回输入框需要的形式。
    book = service.owned_book(book_id, g.user["id"])
    values = {**book, "tags": ", ".join(book["tags"])}
    errors, status = {}, 200
    if request.method == "POST":
        draft, values, errors = read_book_form(request.form)
        status = 422
        if not errors:
            saved_id, errors = service.save_book(g.user["id"], draft, book_id)
            status = 409
            if not errors:
                flash("已更新书籍", "success")
                return redirect(url_for("shelf.detail", book_id=saved_id), code=303)
    return render_template(
        "books/form.html", heading="修改书籍", values=values, errors=errors
    ), status


@bp.post("/books/<int:book_id>/delete")
@login_required
def delete(book_id):
    # 删除同样经过登录、CSRF 与记录归属检查。
    service.delete_book(book_id, g.user["id"])
    flash("已删除书籍", "success")
    return redirect(url_for("shelf.index"), code=303)

修改失败显示的是用户这次尝试的值,数据库中的旧记录则保留。成功提交后重读详情页,看到的是数据库的新值。删除成功后回到列表,再访问旧详情得到 404。

2. 用同一份字段模板显示两种表单

<!-- 项目文件:shelf_project/src/shelfweb/templates/books/_fields.html -->
{% from '_macros.html' import field_error %}
<!-- include 使用调用位置传入的 values 和 errors。 -->
{{ field_error(errors, '_form') }}
<label for="title">书名</label>
<input id="title" name="title" value="{{ values.title }}" required maxlength="100">
{{ field_error(errors, 'title') }}
<label class="checkbox" for="finished">
  <input id="finished" type="checkbox" name="finished" value="1" {% if values.finished %}checked{% endif %}> 已经读完
</label>
{{ field_error(errors, 'finished') }}
<label for="tags">标签</label>
<input id="tags" name="tags" value="{{ values.tags }}" placeholder="基础, 网络">
<p class="muted">用英文逗号分隔,最多 10 个标签,每个标签最多 30 个字符。</p>
{{ field_error(errors, 'tags') }}

checked 是 HTML 布尔属性,存在时表示勾选,缺少时表示未勾选。因此模板在 values.finished 为真时才生成它。value="1" 规定勾选后提交的文本,和第九节的转换规则对应。

<!-- 项目文件:shelf_project/src/shelfweb/templates/books/form.html -->
{% extends 'base.html' %}
{% from '_macros.html' import csrf_field %}
{% block title %}{{ heading }}{% endblock %}
{% block content %}
<section class="panel form-panel">
  <a href="{{ url_for('shelf.index') }}">返回书架</a><h1>{{ heading }}</h1>
  <form method="post" action="{{ request.path }}">
    {{ csrf_field() }}
    {% include 'books/_fields.html' %}
    <button type="submit">保存书籍</button>
  </form>
</section>
{% endblock %}

表单骨架只负责标题、提交地址、CSRF 与保存按钮,具体字段由 include 展开。新增与修改使用同一套字段和错误显示,行为差异留给业务函数处理。

GET 加载显示值后渲染表单,POST 转成 BookDraft,字段错误或冲突重新显示本次输入,事务提交成功才 303 跳转并重新查询

笔记正文与全部题解 · Ctrl / ⌘ K