Issue
I have a Flask application with a GET handler that given a recipe id as a URL parameter, it retrieves the recipe from the database and then renders it:
@app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
recipe = get_recipe_from_db(id)
return render_template('recipe.html', recipe=recipe)
This results in a URL like /recipe/5
. Rather than showing just the id in the URL, I would like the recipe title to be part of the resulting URL, for example recipe/5/lemon-cake
. On the first request only the id is known.
I'm not sure what a neat way to do this is. So far I've come up with the following:
@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)
This works (i.e. when user visits /recipe/5
it redirects to /recipe/5/lemon-cake
) but suffers from the fact that the same recipe is retrieved from the database twice.
Is there a better way to go about this?
Note: the recipe
object is large containing multiple fields and I do not want to pass it over the network unecessarily.
Solution
Option 1
The easiest solution would be to modify the URL on client side, as soon as the response is received; hence, no need for redirection and/or querying the databse twice. This could be achieved by using either history.pushState()
or history.replaceState()
.
client side
<!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>
server side
@app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
recipe = get_recipe_from_db(id)
return render_template('recipe.html', recipe=recipe)
You may also would like to retain the get_with_title()
route as well, in case users bookmark/share the URL (including the title) and want it to be accessible (otherwise, "Not Found" error would be returned when accessing it).
Option 2
If you wouldn't like to query the database every time a new request arrives - not even jsut for retrieving the title
(not every single column) of a recipe entry - and you have sufficient amount of memory to hold the data, I would suggest to query the database once at startup (selecting only id
and title
) and create a dictionary, so that you can quickly look up for a title
from the recipe id
. Please note, in this way, every time an INSERT/DELETE/etc operation is performed on the table, that dictionary has to be updated accordingly. Thus, if you have such operations frequently on that table, might not be the best approach to the problem, and better keep with querying the table just for retrieving the title
.
recipes = dict((row[0], row[1]) for row in result) # where row[0] is id and row[1] is title
Then in your endpoint:
@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)))
Answered By - Chris
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.