'How to correctly call url_for and specify path parameters?
This is the function in views.py:
@application.route("/students/<int:id>", methods=["GET", "POST"])
def students(id):
return render_template("students.html")
I have used this code on my HTML page:
<a href="{{ url_for('students', id=student.id) }}">{{ student.id }}</a>
But url_for is generating this:
http://127.0.0.1:5000/students?id=1
I want to generate this:
http://127.0.0.1:5000/students/1
Note that I have another function which was written before that first function:
@application.route("/students")
def students():
return render_template("students.html", students=Student.query.all())
I think url_for is using this route.
Solution 1:[1]
The problem is that you have 2 endpoints that have almost the same pattern:
/students
/students/<int:id>
It then becomes a matter of in what order are those endpoints defined, because the call url_for('students', id=student.id) can correctly match on both:
- If it matches
/studentsfirst, as explained inurl_fordocs: "Variable arguments that are unknown to the target endpoint are appended to the generated URL as query arguments.". Sinceid=1234is not a known parameter on this endpoint, it becomes a query parameter?id=1234. - If it matches
/students/<int:id>first, then you get your expected behavior of/students/1234
Relying on the order of the endpoints is a brittle and error-prone strategy, so if you think you should just reorder the endpoint definitions, no, that's not the way to go.
What I recommend instead are these options:
Rename the functions to help differentiate
url_for(and readers of your code) that one endpoint is for getting all the students, and the other one is for accesing just one student by id. Then explicitly call the correct one inurl_for:@application.route("/students/<int:id>", methods=["GET", "POST"]) def student_by_id(id): return {"student": id} @application.route("/students") def all_students(): return {"students": "all"}<a href="{{ url_for('student_by_id', id=student.id) }}">{{ student.id }}</a>Similar to option 1, but instead of renaming the functions, pass-in an
endpoint=...keyword parameter to theroute(...)decorator to specify different endpoint names. See the docs for theroutedecorator and the section on URL Route Registrations for more details. By default, "The endpoint name for the route defaults to the name of the view function if theendpointparameter isn’t passed.". So here, name the endpoint that accepts anidas"student_by_id"and explicitly use that inurl_for.@application.route( "/students/<int:id>", methods=["GET", "POST"], endpoint="student_by_id", # <--------- ) def students(id): return {"student": id} @application.route("/students") def students(): return {"students": "all"}<a href="{{ url_for('student_by_id', id=student.id) }}">{{ student.id }}</a>Combine both into 1 endpoint. Then the endpoint function should first check if
idwas specified. If it was, then handle processing for just 1 student. Else, handle processing for all students. Theurl_forcall doesn't need to be changed.@application.route("/students") @application.route("/students/<int:id>", methods=["GET", "POST"]) def students(id=None): if id: return {"student": id} else: return {"students": "all"}<a href="{{ url_for('students', id=student.id) }}">{{ student.id }}</a>
Pick one which works best for you.
As a side note, as mentioned in the comments, I also recommend not using id as a parameter or variable name, because you might accidentally shadow the built-in id() function. It might be actually be more readable to change it to student_id.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
