9

I would like to call a FastAPI route from within Jinja2 template, and pass both path and query data (parameters) to that route. I tried in the Jinja2 template something like this:

{{ url_for('function1', uustr=data.uustr, interval=1) }}

Here is the FastAPI route that I would like to call (syntax has been 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?

2
  • Have you taken a look at the FastAPI documentation for jinja templates? Looks like they do it in a very different way to yours: fastapi.tiangolo.com/advanced/templates/#using-jinja2templates Commented Mar 5, 2021 at 16:33
  • Yes, I saw that, and you are right. The usage there is different. In the documentation they use url_for() for locating a folder. I want to use it to call a route. At least in flask something like this is possible. I hope also in fastapi Commented Mar 5, 2021 at 16:50

2 Answers 2

11

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 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>

Update 1 - Including query parameters

You can now use Starlette's starlette.datastructures.URL, which provides a method to include_query_params. Example below:

In app.py import the URL class and make it accessible from Jinja2 templates:

from starlette.datastructures import URL

templates = Jinja2Templates(directory="templates")
templates.env.globals['URL'] = URL

In templates/item.html use as follows:

<!DOCTYPE html>
<html>
   <body>
      <div class="container">
         <iframe src="{{ URL(url_for('updates', page_no=5, item_id=3)).include_query_params(user='foo', msg='bar') }}" width = 300 height = 300 style= "border: none;"></iframe>
      </div>
   </body>
</html>

Update 2 - Including query parameters

The request.url_for() function now returns a starlette.datastructures.URL object. Hence, you could simply add query parameters to the URL as follows:

<!DOCTYPE html>
<html>
   <body>
      <div class="container">
         <iframe src="{{ url_for('updates', page_no=5, item_id=3).include_query_params(user='foo', msg='bar') }}" width = 300 height = 300 style= "border: none;"></iframe>
      </div>
   </body>
</html>
Sign up to request clarification or add additional context in comments.

2 Comments

It's worth noting that Starlette/FastAPI's url_for() returns a starlette.datastructures.URL, so you can do: url_for('route_name', path_param=1).include_query_params(extra=2) without wrapping the call in URL.
@Ross In Starlette's earlier versions, that wasn't the case. Thank you for letting me know. The answer has been updated.
-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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.