Flask 重定向数据

Flask redirect with data

我有一个带有 GET 处理程序的 Flask 应用程序,该处理程序将配方 ID 作为 URL 参数,它从数据库中检索配方,然后呈现它:

@app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
    recipe = get_recipe_from_db(id)
    return render_template('recipe.html', recipe=recipe)

这会导致 URL 类似于 /recipe/5。我希望食谱标题成为结果 URL 的一部分,而不是仅在 URL 中显示 id,例如 recipe/5/lemon-cake。在第一个请求中,只有 id 是已知的。

我不确定这样做的好方法是什么。到目前为止,我已经想出了以下内容:

@app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
    recipe = get_recipe_from_db(id)
    return redirect(url_for('get_with_title', id=id, title=urlify(recipe.title)))

@app.route('/recipe/<int:id>/<title>', methods=['GET'])
def get_with_title(id, title=None):
    recipe = get_recipe_from_db(id)
    return render_template('recipe.html', recipe=recipe)

这是有效的(即当用户访问 /recipe/5 时它重定向到 /recipe/5/lemon-cake)但是从数据库中检索相同的配方两次。

有没有更好的方法来解决这个问题?

注意:recipe object 很大,包含多个字段,我不想不必要地通过网络传递它。

只需将加载的配方作为参数传递:

@app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
    recipe = get_recipe_from_db(id)
    return redirect(
        url_for(
            'get_with_title',
            id=id,
            title=urlify(recipe.title),
            recipe=recipe,
        ))

@app.route('/recipe/<int:id>/<title>', methods=['GET'])
def get_with_title(id, title=None, recipe=None):
    if recipe is None:
        recipe = get_recipe_from_db(id)
    return render_template('recipe.html', recipe=recipe)

您只能研究 将 URL 更改为 javascript,如图 here。这样,您就不会进行任何重定向或重新加载。

如果你不想惹javascript,让我们想想后端解决方案:

从数据库加载两次显着的性能开销值得一个复杂的解决方案吗?如果没有,你的解决方案很好。

如果你真的想避免加载整个 recipe,你可以在你的 get 方法中只加载标题,而不是整个 object.
您可以编写自定义 get_recipe_title(id) ,它(大致) SELECT title FROM recipes WHERE id={id}.
如果使用 SQLAlchemy,您可能会使用 load_only - here

如果您希望 'recipe title' 出现在 URL 中,您的解决方案不适合。您应该在 'request' 之前执行此操作。可能有一个 HTML 页面,您可以在其中提交食谱数据以列出所有食谱。

在您的 HTML 页面中尝试以下操作:

<ul> 
{% for recipe  in recipes %} 
<li><a href="/postdetail/{{recipe.id}}/{{recipe.title}}"></li>
 {% endfor %} 
</ul>

之后,当您单击该食谱时,您还可以看到标题。

选项 1

最简单的解决方案是在收到响应后立即在客户端修改 URL;因此,不需要重定向 and/or 查询数据库两次。这可以通过使用 history.pushState() or history.replaceState().

来实现

客户端

<!DOCTYPE html>
<html>
   <head>
      <script>
         function modify_url() {
           var title = {{recipe.title|tojson}};
           var id = {{recipe.id|tojson}};
           window.history.pushState('', '', id + "/" + title);
         }
      </script>
   </head>
   <h2>Recipe for {{recipe.title}}</h2>
   <body onload="modify_url()"></body>
</html>

服务器端

@app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
    recipe = get_recipe_from_db(id)
    return render_template('recipe.html', recipe=recipe)

您可能还想保留 get_with_title() 路由,以防用户 bookmark/share URL(包括标题)并希望它可以访问(否则,访问时会返回“Not Found”错误。

选项 2

如果您不希望每次收到新请求时都查询数据库——甚至不希望检索菜谱条目的 title(不是每一列)——并且您有足够的内存来保存数据,我建议在启动时查询一次数据库(只选择idtitle)并创建一个字典,这样你就可以快速查找一个title来自食谱 id。请注意,以这种方式,每次对 table 执行 INSERT/DELETE/etc 操作时,必须相应地更新该字典。因此,如果您经常在 table 上进行此类操作,可能不是解决问题的最佳方法,最好继续查询 table 只是为了检索 title.

recipes = dict((row[0], row[1]) for row in result) # where row[0] is id and row[1] is title

然后在您的端点:

@app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
    title = recipes.get(id)
    return redirect(url_for('get_with_title', id=id, title=urlify(title)))