Question
I can't figure out if it's possible to pass query parameters from a static html page to a view for processing. I have now implemented the functionality I need using path parameters. I want to do the same, but with query parameters
main.py
from helpers.utils import CustomURLProcessor
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
templates.env.globals['CustomURLProcessor'] = CustomURLProcessor
events.py
router = APIRouter()
templates = Jinja2Templates(directory="templates")
@router.post('/invite/{event_invite}/sender/{sender}')
async def decline_event_invite(
request: Request,
event_invite: int,
sender: int,
):
#logic
routes.py
api_router.include_router(events.router, prefix="/event")
html
{{ url_for('decline_event_invite', event_invite=invite.id, sender=invite.sender) }}
It's works with "POST /event/invite/15/sender/3 HTTP/1.1"
as a sample
Try with query params
@router.post('/invite')
async def decline_event_invite(
request: Request,
event_invite: Optional[str] = None,
sender: Optional[str] = None,
):
# logic
And get
raise NoMatchFound()
starlette.routing.NoMatchFound
html template without any change
Can I pass query parameters from the template to the fastapi logic to
processing this url /event/?event_invite=15&sender=3
?
Answer
This is Starlette's
issue
(i.e., url_for()
receives path
parameters, not query
parameters).
What you could do is to create a custom URL processor (as shown below) and use
it to pass path
and/or query
parameters as well.
import urllib
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)
Remember to add the CustomURLProcessor
class to the templates' global
environment:
templates.env.globals['CustomURLProcessor'] = CustomURLProcessor
Then, use in Jinja template, as below:
{{ CustomURLProcessor().url_for(request, 'decline_event_invite').include_query_params(event_invite=invite.id, sender=invite.sender) }}
Please have a look at this answer for a complete working example. Also, this feature might be introduced into the next version of Starlette #1385. Thus, you may want to keep an eye on it.
More options on how to make a class accessible from Jinja templates
Alternative options to make CustomURLProcessor
class accessible from Jinja
templates, in case templates.env.globals['CustomURLProcessor'] = CustomURLProcessor
didn't work for you, throwing the following error:
jinja2.exceptions.UndefinedError: 'CustomURLProcessor' is undefined
.
Option 1
templates.env.globals.update(CustomURLProcessor=CustomURLProcessor)
Option 2
import helpers.utils
templates.env.globals['CustomURLProcessor'] = helpers.utils.CustomURLProcessor
# or, equivalently
templates.env.globals.update(CustomURLProcessor=helpers.utils.CustomURLProcessor)
You could even add the whole module, and use in Jinja template like this
helpers.CustomURLProcessor().url_for(request...
.
import helpers.utils
templates.env.globals['helpers'] = helpers.utils
Option 3
The last option would be to pass it as part of your TemplateResponse
.
return templates.TemplateResponse("index.html", {"request": request, "CustomURLProcessor": CustomURLProcessor})
If none of the above worked, perhaps other options described [here](https://stackoverflow.com/questions/6036082/call-a-python-function- from-jinja2) might help, or the problem likely lies elsewhere.
Update 1 - About including query params
Regarding sending query parameters from a Jinja template, you can now use
Starlette's
starlette.datastructures.URL
,
which provides an
include_query_params
method.
To use, 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
Then, use in Jinja template as shown below:
{{ URL(url_for('decline_event_invite')).include_query_params(event_invite=invite.id, sender=invite.sender) }}
Update 2 - About including query params
The
request.url_for()
function now returns a
starlette.datastructures.URL
object. Hence, you could simply use the below, without needing to import the
URL
class in Jinja2 templates:
{{ url_for('decline_event_invite').include_query_params(event_invite=invite.id, sender=invite.sender) }}