如何重写这个Flask视图函数以遵循post/redirect/get模式?

8

我有一个小的日志浏览器。它根据用户的输入检索并显示先前记录的列表。它不会更新任何内容。

代码非常简单,工作正常。这是简化版:

@app.route('/log', methods=['GET', 'POST'])
def log():
    form = LogForm()
    if form.validate_on_submit():
        args = parse(form)
        return render_template('log.html', form=form, log=getlog(*args))
    return render_template('log.html', form=form)

然而它并不遵循post/redirect/get模式,我想要修复这个问题。

在post和get之间,我应该把已发布的数据(即args)存储在哪里?什么是标准或推荐的方法?我应该设置一个cookie吗?我应该使用flask.session对象,在那里创建一个缓存?你能指点我正确的方向吗?大多数时候我都在写后端...


更新:

我正在发布生成的代码。

@app.route('/log', methods=['POST'])
def log_post():
    form = LogForm()
    if form.validate_on_submit():
        session['logformdata'] = form.data
        return redirect(url_for('log'))
    # either flash errors here or display them in the template
    return render_template('log.html', form=form)

@app.route('/log', methods=['GET'])
def log():
    try:
        formdata = session.pop('logformdata')
    except KeyError:
        return render_template('log.html', form=LogForm())

    args = parse(formdata)
    log = getlog(args)
    return render_template('log.html', form=LogForm(data=formdata), log=log)
3个回答

8
因此,发布/重定向/获取模式 最终保护免受提交表单数据多次的影响。由于您的POST实际上没有进行任何数据库更改,所以您使用的方法似乎很好。通常在该模式中,POST会对基础数据结构进行更改(例如UPDATE/INSERT/DELETE),然后在重定向时查询更新的数据(SELECT),因此通常您不需要在重定向和获取之间“存储”任何内容。

话虽如此,我处理这个问题的方法是使用 Flask session 对象,它是 Flask 为您管理的 cookie。您可以像这样做:

@app.route('/log', methods=['GET', 'POST'])
def log():
    form = LogForm()
    if form.validate_on_submit():
        args = parse(form)
        session['log'] = getlog(*args)
        return redirect(url_for('log'))
    saved = session.pop('log', None)
    return render_template('log.html', form=form, log=saved)

此外,要使用会话,您必须在应用程序配置中设置secret_key

Flask Session API

更新1/9/16

根据ThiefMaster的评论,重新排列了逻辑顺序,以允许使用WTForms验证方法来处理无效表单提交,以便无效表单提交不会丢失。


另一个使用该模式的原因是在重新加载页面时,浏览器会询问用户是否要重新发送数据。即使用户理解了发生了什么,他们可能也会觉得很烦人。 - VPfB
我很感激这个例子。与会话(session)一起工作比我之前想象的要容易得多。我已经根据你的答案更新了我的代码,它现在按照我想要的方式工作。谢谢你。回答被接受了。 - VPfB
我之前不知道这个模式。感谢您的问题和答案。在这里找到了一个很好的模式解释:http://chase-seibert.github.io/blog/2012/10/19/post-redirect-get-prg-pattern.html - MrLeeh
1
这是错误的,因为您将无法看到任何验证错误,并且在验证失败的情况下还会失去表单输入。 - ThiefMaster

1
Flask中执行P/R/G的常用方法如下:

@app.route('/log', methods=('GET', 'POST'))
def log():
    form = LogForm()
    if form.validate_on_submit():
        # process the form data
        # you can flash() a message here or add something to the session
        return redirect(url_for('log'))
    # this code is reached when the form was not submitted or failed to validate
    # if you add something to the session in case of successful submission, try
    # popping it here and pass it to the template
    return render_template('log.html', form=form)

在表单验证失败时,通过停留在已提交的页面,WTForms 可以用用户输入的数据填充字段,并且您可以在表单渲染期间显示每个字段的错误(通常人们编写一些 Jinja 宏来轻松渲染 WTForm)。请保留 HTML 标签。

谢谢,非常好的观点。我更新了我的答案以反映您的评论。我没有使用WTForms的这个功能,所以我没有考虑过这个问题。 - abigperson
感谢您解释了这些细节。我并没有直接复制采纳答案中的代码,只是学会了如何使用会话对象。这是一个小但重要的缺失部分。我的程序中的错误报告很好。经过一些测试,我已经将我满意的版本附加到了问题中。 - VPfB
1
如果在POST请求中表单验证失败,代码将呈现模板而不是重定向,并且仍然不遵循Post-Redirect-Get模式。 - Mir

0

这种方法在重定向时保留表单数据以及字段错误。如果您的表单包含小数或日期,请使用Flask-Session,因为使用常规的Flask会出现问题。

@app.route("/log", methods=["get", "post"])
def log():

    if request.method == "GET":
        form = session2form(LogForm) if "form" in session else LogForm()
        return render_template("log.html", form=form)
    else: # POST
        form = LogForm()
        if form.validate_on_submit():
            return redirect(url_for("...")) # your "success" endpoint
        # validation failed, so put form/errors in session and redirect to self:
        form2session(form)
        return redirect(url_for(request.endpoint))

两个帮助函数,让代码更加DRY:

def session2form(form_cls):
    form_data, field_errors, form_errors = session["form"]

    form = form_cls(**form_data)
    form.form_errors = form_errors
    for field in form:
        field.errors = field_errors[field.name]
    session.pop("form", None)
    return form


def form2session(form):
    """can't store WTForm in session as it's not serializable,
    but can store form data and errors"""
    session["form"] = (
        form.data,
        {field.name: field.errors for field in form},
        form.form_errors,
    )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接