I have a Flask app with a GET controller that, given a recipe id as a URL parameter, 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 display just the id in the URL, I'd like the recipe title to be part of the resulting URL, for example recipe/5/lemon-cake . In the first request only the ID is known.
I'm not sure what a neat way to do this is. So far I have come to 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 (ie when the 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 do this?
Note: the recipe object is large and contains several fields and I don't want to pass it over the network unnecessarily.
The easiest solution would be to modify the URL on the client side, as soon as the response is received; therefore, there is no need to redirect and/or query the database twice. This can be achieved using 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 want to preserve the get_with_title() route, in case users bookmark/share the URL (including the title) and want it to be accessible (otherwise a "Not Found" error will be returned on access).
If you don't want to query the database every time a new request comes in, not even to retrieve the title (not all columns) of a recipe entry, and have enough memory to store the data, I'd suggest querying the database once at the start (selecting only id and title ) and create a dictionary, so you can quickly look up a recipe title id . Note that this way, every time an INSERT/DELETE/etc operation is performed on the table, that dictionary must be updated accordingly. So if you frequently have such operations on that table, it might not be the best approach to the problem and it's better to keep querying the table just to retrieve the title file.
recipes = dict((row[0], row[1]) for row in result) # where row[0] is id and row[1] is titleThen at 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)))