Tengo una aplicación Flask con un controlador GET que, dada una identificación de receta como parámetro de URL, recupera la receta de la base de datos y luego la procesa:
@app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
recipe = get_recipe_from_db(id)
return render_template('recipe.html', recipe=recipe)
Esto da como resultado una URL como /recipe/5. En lugar de mostrar solo la identificación en la URL, me gustaría que el título de la receta sea parte de la URL resultante, por ejemplo recipe/5/lemon-cake. En la primera solicitud solo se conoce la identificación.
No estoy seguro de qué manera ordenada de hacer esto es. Hasta ahora he llegado a lo siguiente:
@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)
Esto funciona (es decir, cuando el usuario lo visita /recipe/5, lo redirige a /recipe/5/lemon-cake) pero adolece del hecho de que la misma receta se recupera de la base de datos dos veces.
¿Hay una mejor manera de hacer esto?
Nota: el recipeobjeto es grande y contiene varios campos y no quiero pasarlo por la red innecesariamente.
La solución más sencilla sería modificar la URL en el lado del cliente, tan pronto como se reciba la respuesta; por lo tanto, no es necesario redirigir y/o consultar la base de datos dos veces. Esto se puede lograr utilizando history.pushState()o history.replaceState().
lado del cliente
<!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>
lado del servidor
@app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
recipe = get_recipe_from_db(id)
return render_template('recipe.html', recipe=recipe)
También es posible que desee conservar la get_with_title()ruta, en caso de que los usuarios marquen/compartan la URL (incluido el título) y deseen que sea accesible (de lo contrario, se devolverá el error "No encontrado" al acceder).
Si no desea consultar la base de datos cada vez que llega una nueva solicitud, ni siquiera para recuperar title(no todas las columnas) de una entrada de receta, y tiene suficiente memoria para almacenar los datos, le sugiero que consulte la base de datos una vez al inicio (seleccionando solo idy title) y cree un diccionario, para que pueda buscar rápidamente una titlede la receta id. Tenga en cuenta que, de esta manera, cada vez que se realiza una operación INSERT/DELETE/etc en la tabla, ese diccionario debe actualizarse en consecuencia. Por lo tanto, si tiene este tipo de operaciones con frecuencia en esa tabla, podría no ser el mejor enfoque para el problema y es mejor seguir consultando la tabla solo para recuperar el archivo title.
recipes = dict((row[0], row[1]) for row in result) # where row[0] is id and row[1] is title
Luego en su punto final:
@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)))