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.
CodePudding user response:
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
/students
first, as explained inurl_for
docs: "Variable arguments that are unknown to the target endpoint are appended to the generated URL as query arguments.". Sinceid=1234
is 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 theroute
decorator 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 theendpoint
parameter isn’t passed.". So here, name the endpoint that accepts anid
as"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
id
was specified. If it was, then handle processing for just 1 student. Else, handle processing for all students. Theurl_for
call 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
.