Search code examples
pythonhttp-redirectjinja2fastapistarlette

How to redirect to dynamic URL inside a FastAPI endpoint?


I'm doing a feature where the user on their profile page makes changes (not related to the user model). Everything is implemented through static HTML templates. I need the user to click on the button and return to the same page (i.e., their profile page) after processing the request.

Html template

<td><a href="{{ url_for('decline_event_invite', pk=invite.id) }}" class="btn blue lighten-2">Accept</a></td>

endpoints.py

@router.get('/invite/{pk}/decline')
async def decline_event_invite(
        request: Request,
        pk: int,
        user_id: str = Depends(get_current_user),
        service: InviteService = Depends(),
):
    await service.invite_decline(pk)
    ...
    --> here I want redirect to user profile page 
    return RedirectResponse('DYNAMIC URL WITH ARGS')

profile.py

@router.get('/{pk}')
async def user_profile(
        request: Request,
        pk: int,
        service: UserService = Depends()
):
    user = await service.get_user_info(pk)
    events_invites = await service.get_user_events_invite_list(pk)
    return templates.TemplateResponse(
        'profile.html',
        context=
        {
            'request': request,
            'user': user,
            'events_invites': events_invites,
        }
    )

But I can't find anywhere how to do a redirect similar to the logic that applies to templates. For example:

<a href="{{ url_for('user_profile', pk=pk) }}">Sender</a>

Solution

  • You can use url_for() function and pass the (**kwargs) path parameters.

    import uvicorn
    from fastapi import FastAPI, Request
    from fastapi.templating import Jinja2Templates
    from fastapi.responses import RedirectResponse
    import urllib
    from fastapi import APIRouter
    
    app = FastAPI()
    
    templates = Jinja2Templates(directory="templates")
    
    @app.get('/invite/{pk}/decline')
    def decline_event_invite(request: Request, pk: int):
        redirect_url = request.url_for('user_profile', **{ 'pk' : pk})
        return RedirectResponse(redirect_url)    
    
    @app.get('/{pk}')
    def user_profile(request: Request, pk: int):
        return templates.TemplateResponse("profile.html", {"request": request, "pk": pk})
        
    

    To add query params

    In case you had to pass query params as well, you could use the following code (make sure to import urllib). Alternatively, you could use the CustomURLProcessor, as described in this and this answer (which pretty much follows the same approach).

    If the endpoint expected query params, for example:

    @router.get('/{pk}')
    def user_profile(request: Request, pk: int, message: str):
        pass
    

    you could use:

    redirect_url = request.url_for('user_profile', pk=pk)
    parsed = list(urllib.parse.urlparse(redirect_url))
    parsed[4] = urllib.parse.urlencode({**{ 'message' : "Success!"}})
    redirect_url = urllib.parse.urlunparse(parsed)
    

    or even use:

    message = 'Success!'
    redirect_url = request.url_for('user_profile', pk=pk) + f'?message={message}'
    

    Update 1 - About including query parameters

    Another solution would be to use Starlette's starlette.datastructures.URL, which now provides a method to include_query_params. Example:

    from starlette.datastructures import URL
    
    redirect_url = URL(request.url_for('user_profile', pk=pk)).include_query_params(message="Success!")
    

    Update 2 - About including query parameters

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

    redirect_url = request.url_for('user_profile', pk=pk).include_query_params(message="Success!")