'How to use url_for() to pass path and query data to a route using FastAPI and Jinja2
I want to call a FastAPI route from a jinja2 template and pass both path and query data to the called route. All my attempts fail.
I tried something in the jinja2 template like {{ url_for('function1', uustr=data.uustr, interval=1) }}
Here is the FastAPI route I want to call (syntax simplified for demo purposes):
@app.get("/updates/data/{uustr}",response_class=HTMLResponse)
async def function1(request: Request, uustr:str, interval:int):
return"""
<html>
<head>
<title>{{ uustr }}</title>
</head>
<body>
<h1>{{ interval }}</h1>
</body>
</html>
"""
I get this error:
raise ValueError('context must include a "request" key')
ValueError: context must include a "request" key
Does anybody have an idea?
Solution 1:[1]
I found a very easy solution without using url_for()
In case someone coming from flask world has a similar problem here my solution: I created a simple HTML button in my jinja2 template:
<button onclick="myFunction()">pass data to route</button>
and I created a very simple javascript function for passing data to route:
<script>
function myFunction() {
window.location.href = "/updates/data/{{data.uustr}}?interval=2";
}
</script>
that's it.
Solution 2:[2]
This is not FastAPI's issue, but rather Starlette's issue (i.e., request.url_for() receives path parameters, not query parameters). So, nspired by #560 and #1385, I have created the following working example for calling FastAPI routes from within jinja2 templates, and passing query params (alone or along with path params, as well).
Please note that this is a feature that is probably about to be introduced into the next version of Starlette #1385. Thus, best to use that one, when it is out.
app.py
import uvicorn
from fastapi import FastAPI, Response
from fastapi.templating import Jinja2Templates
from fastapi import Request
from fastapi.responses import StreamingResponse, HTMLResponse
import urllib
app = FastAPI()
class CustomURLProcessor:
def __init__(self):
self.path = ""
self.request = None
def url_for(self, request: Request, name: str, **params: str):
self.path = request.url_for(name, **params)
self.request = request
return self
def include_query_params(self, **params: str):
parsed = list(urllib.parse.urlparse(self.path))
parsed[4] = urllib.parse.urlencode(params)
return urllib.parse.urlunparse(parsed)
templates = Jinja2Templates(directory='templates')
templates.env.globals['CustomURLProcessor'] = CustomURLProcessor
@app.get('/updates/page/{page_no}/item/{item_id}')
async def updates(request: Request, page_no: int, item_id: int, user: str, msg: str):
return templates.TemplateResponse("item.html", {"request": request, "page_no": page_no, "item_id":item_id, "user": user, "msg": msg})
@app.get('/updates_query_only')
async def updates_query_only(request: Request, user: str, msg: str):
return templates.TemplateResponse("item.html", {"request": request, "user": user, "msg": msg})
@app.get('/')
async def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
if __name__ == '__main__':
uvicorn.run(app, host='127.0.0.1', port=8000, debug=True)
templates/index.html
<!DOCTYPE html>
<html>
<body>
<div class="container">
{% set cu = CustomURLProcessor() %}
{% set _url = cu.url_for(request, 'updates', page_no=5, item_id=3).include_query_params(user='foo', msg='bar') %}
<!-- if only query params required, use as follows: -->
{# {% set _url = cu.url_for(request, 'updates_query_only').include_query_params(user='foo', msg='bar') %} #}
<iframe src="{{ _url }}" width = 300 height = 300 style= "border: none;"></iframe>
</div>
</body>
</html>
templates/item.html
<!DOCTYPE html>
<html>
<body>
<h1>Page No {{ page_no }}</h1>
<h2>Item {{ item_id }}</h2>
<h3>{{ user }}</h3>
<h4>{{ msg }}</h4>
</body>
</html>
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 | Mips |
Solution 2 |