How to send RedirectResponse from a POST to a GET route in FastAPI?
Asked Answered
N

1

6

I want to send data from app.post() to app.get() using RedirectResponse.

@app.get('/', response_class=HTMLResponse, name='homepage')
async def get_main_data(request: Request,
                        msg: Optional[str] = None,
                        result: Optional[str] = None):
    if msg:
        response = templates.TemplateResponse('home.html', {'request': request, 'msg': msg})
    elif result:
        response = templates.TemplateResponse('home.html', {'request': request, 'result': result})
    else:
        response = templates.TemplateResponse('home.html', {'request': request})
    return response
@app.post('/', response_model=FormData, name='homepage_post')
async def post_main_data(request: Request,
                         file: FormData = Depends(FormData.as_form)):
       if condition:
        ......
        ......

        return RedirectResponse(request.url_for('homepage', **{'result': str(trans)}), status_code=status.HTTP_302_FOUND)

    return RedirectResponse(request.url_for('homepage', **{'msg': str(err)}), status_code=status.HTTP_302_FOUND)
  1. How do I send result or msg via RedirectResponse, url_for() to app.get()?
  2. Is there a way to hide the data in the URL either as path parameter or query parameter? How do I achieve this?

I am getting the error starlette.routing.NoMatchFound: No route exists for name "homepage" and params "result". when trying this way.

Update:

I tried the below:

return RedirectResponse(app.url_path_for(name='homepage')
                                + '?result=' + str(trans),
                                status_code=status.HTTP_303_SEE_OTHER)

The above works, but it works by sending the param as query param, i.e., the URL looks like this localhost:8000/?result=hello. Is there any way to do the same thing but without showing it in the URL?

Nappe answered 22/7, 2022 at 7:19 Comment(2)
Please have a look at the answers here and here (you should rather use the method's name in request.url_for(), i.e., get_main_data). As for hiding the data in the URL, please take a look at this answer.Ilona
I tried. I am getting the same error in both cases. Using a router as well. Tried with get_main_data also, same resultNappe
I
3

For redirecting from a POST to a GET method, please have a look at this and this answer on how to do that and the reason for using status_code=status.HTTP_303_SEE_OTHER (example is given below).

As for the reason for getting starlette.routing.NoMatchFound error, this is because request.url_for() receives path parameters, not query parameters. Your msg and result parameters are query ones; hence, the error.

A solution would be to use a CustomURLProcessor, as suggested in this and this answer, allowing you to pass both path (if need to) and query parameters to the url_for() function and obtain the URL. As for hiding the path and/or query parameters from the URL, you can use a similar approach to this answer that uses history.pushState() (or history.replaceState()) to replace the URL in the browser's address bar.

Complete working example can be found below (you can use your own TemplateResponse in the place of HTMLResponse).

from fastapi import FastAPI, Request, status
from fastapi.responses import RedirectResponse, HTMLResponse
from typing import Optional
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)
        

@app.get('/', response_class=HTMLResponse)
def event_msg(request: Request, msg: Optional[str] = None):
    if msg:
        html_content = """
        <html>
           <head>
              <script>
                 window.history.pushState('', '', "/");
              </script>
           </head>
           <body>
              <h1>""" + msg + """</h1>
           </body>
        </html>
        """
        return HTMLResponse(content=html_content, status_code=200)
    else:
        html_content = """
        <html>
           <body>
              <h1>Create an event</h1>
              <form method="POST" action="/">
                 <input type="submit" value="Create Event">
              </form>
           </body>
        </html>
        """
        return HTMLResponse(content=html_content, status_code=200)

@app.post('/')
def event_create(request: Request):
    redirect_url = CustomURLProcessor().url_for(request, 'event_msg').include_query_params(msg="Succesfully created!")
    return RedirectResponse(redirect_url, status_code=status.HTTP_303_SEE_OTHER)

Update 1 - About including query parameters

Regarding adding query params to url_for() function, another solution would be using 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('event_msg')).include_query_params(msg="Succesfully created!")

Update 2 - About including query parameters

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

redirect_url = request.url_for('event_msg').include_query_params(msg="Succesfully created!")
Ilona answered 23/7, 2022 at 7:23 Comment(1)
Thanks for the reply. Yes, I tried a similar approach.Nappe

© 2022 - 2024 — McMap. All rights reserved.