2

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?

Jekson
  • 2,892
  • 8
  • 44
  • 79

1 Answers1

3

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 might help, or the problem likely lies elsewhere.

Update - 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) }}

Chris
  • 18,724
  • 6
  • 46
  • 80
  • Where should CustomURLProcessor be added to the template? I'm trying to add to the same file as the `decline_event_invite` function. At the beginning of it is defined `templates = Jinja2Templates(directory="templates")` and then I add the `templates.env.globals['CustomURLProcessor'] = CustomURLProcessor` but got `jinja2.exceptions.UndefinedError: 'CustomURLProcessor' is undefined` – Jekson Feb 10 '22 at 16:49
  • strange, I did the same as in the link but got the same result. – Jekson Feb 10 '22 at 17:13
  • I've updated the question. In `event.py` I declare the `templates` variable again, as the other routers in this module use `templates.TemplateResponse`. Could this be the problem? – Jekson Feb 10 '22 at 17:22
  • 1
    adding `CustomURLProcessor` to the global env in `events.py` same way as in `main.py` but same get error. But I think the answer to the question I asked has been answered. I'll look into it. – Jekson Feb 10 '22 at 18:58
  • I try both method with import and with setup in the events. py – Jekson Feb 10 '22 at 19:19
  • 1
    As I suspected, the problem was in the structure of my code. It worked the first way, I think all the other options work too. Great hack for passing qwery parameters, thanks for the help – Jekson Feb 11 '22 at 08:38