2
  1. I have an HTML page being rendered with FastAPI:

     @app.get("/index", response_class=HTMLResponse)
     def write_home(request: Request):
     return templates.TemplateResponse("index.html", {"request": request})
    

Rendered HTML

The above HTML has a Submit button for a Form, which will result in a POST request the endpoint \SWP

<form id="generate" action="swp" method="post" enctype="multipart/form-data">
  1. It works like I'd need it to - it posts the data to my Python calculator app, which sends a JSON response back:

Output from Py App

  1. With a JSON file saved locally on the hard drive, I was able to code JavaScript which could read its data and create a Chart via Chart.js. I used Fetch API and then used the object array:

     const API_URL = "./Example_Response.json";
     async function createChartAndTable() {
     let ResponseFromAPI = await fetch(API_URL);
     let JSON_Res = await ResponseFromAPI.json();
     let JSON_Data1 = JSON.parse(JSON_Res.output.data);
    

From locally saved JSON file

  1. Now, Im completely puzzled as to how Im to use the Live JSON result from the Python app http://127.0.0.1:8000/swp (Step2) and then render it to the HTML generated in Step 1 to http://127.0.0.1:8000/index like I was able to with the locally stored JSON file as in Step 3.

Note: there's no database involved.

Any guidance and help is much appreciated.

Chris
  • 18,724
  • 6
  • 46
  • 80
hsa591
  • 29
  • 5
  • you should probably not use a form to to that. I guess its better to make a regular request with the fetch api and work with the response. – The Fool Feb 21 '22 at 18:42
  • Alright, Ill try that out. Im kind of new to JavaScript, so Ive been learning how Fetch API works. So not the greatest at it atm. – hsa591 Feb 21 '22 at 19:30
  • Otherwise, since you are rendering the page with a template, form is ok. But then you need to render the new page such that it respects the form data. You essentially moving the work javascript would do to your template engine. Mixing both is kind of strange. – The Fool Feb 21 '22 at 19:47
  • Using the fetch API to capture and then decipher the response from the POST request worked. Ill post an answer. Thanks for the nudge in the right direction! – hsa591 Feb 21 '22 at 20:42

2 Answers2

1

Option 1

Post the request through HTML form and then have the API redirect you to a new page (Jinja2Template), where you can read the data and display the chart. As @tiangolo posted here, when performing a RedirectResponse from a POST request route to GET request route, the response status code has to change to status_code=status.HTTP_303_SEE_OTHER (the example below does that).

Update: Redirection is not really necessary here. You can just render the template as usual:

@app.post("/submitUsingForm", response_class=HTMLResponse)
def submitUsingForm(request: Request, input1: str = Form(...), input2: str = Form(...)):
    return templates.TemplateResponse("chart.html", {"request": request, "json_data": json_data})

Option 2

Use Fetch API to post the request, receive the JSON data and display the chart on the same page.

Both options are demonstrated in the example given below (the data used is sample data for the purposes of this demo; you can change/handle them as required).

app.py

from fastapi import FastAPI, Request, status, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse

app = FastAPI()

templates = Jinja2Templates(directory="templates")
json_data = {"jsonarray": [{"name": "Joe", "age": 32}, {"name": "Tom", "age": 34}]}


@app.get("/chart", response_class=HTMLResponse)
def get_chart(request: Request):
    return templates.TemplateResponse("chart.html", {"request": request, "json_data": json_data})

@app.post("/submitUsingFetch")
def submitUsingFetch(request: Request, input1: str = Form(...), input2: str = Form(...)):
    return json_data 

@app.post("/submitUsingForm", response_class=HTMLResponse)
def submitUsingForm(request: Request, input1: str = Form(...), input2: str = Form(...)):
    #redirect_url = request.url_for('get_chart')
    #return RedirectResponse(redirect_url, status_code=status.HTTP_303_SEE_OTHER)
    return templates.TemplateResponse("chart.html", {"request": request, "json_data": json_data}) 

@app.get("/", response_class=HTMLResponse)
def home(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

templates/index.html

<!DOCTYPE html>
<html>
   <head>
      <title> Home</title>
      <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js"></script>
      <script>
         function send_request() {
            const formElems = document.getElementById('my_form');
            var formData = new FormData(formElems);
            const url = "http://127.0.0.1:8000/submitUsingFetch";
            fetch(url, {
                method: 'POST',
                headers: {'Accept': 'application/json'},
                body: formData
                }).then(resp => {
                    return resp.json();
                }).then(body => {
                    display_chart(body)
                }).catch(error => {
                    console.error(error);
                });
         }
         
         function display_chart(json_data) {
            var labels = json_data.jsonarray.map(function(e) {return e.name;});
            var data = json_data.jsonarray.map(function(e) {return e.age;});
            const ctx = document.getElementById('myChart');
            var config = {
               type: 'line',
               data: {
                  labels: labels,
                  datasets: [{
                     label: 'Graph Line',
                     data: data,
                     backgroundColor: 'rgba(0, 119, 204, 0.3)'
                  }]
               }
            };
         
            var chart = new Chart(ctx, config);
         }
      </script>
   </head>
   <body>
      <form action="/submitUsingForm"  method='post' id='my_form'>
         <label>Input 1:</label><br>
         <input type="text" id="input1" name="input1" value="0"><br>
         <label>Input 2:</label><br>
         <input type="text" id="input2" name="input2" value="0"><br>
         <input type="submit" value="Submit using Form">
         <input type="button" value="Submit using Fetch" onclick="send_request()">
      </form>
    <canvas id="myChart"></canvas>
   </body>
</html>

templates/chart.html

<html>
   <head>
      <head>
         <title> Chart</title>
         <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js"></script>
         <script>
            function display_chart(){
                var json_data = {{json_data|tojson}};
                var labels = json_data.jsonarray.map(function(e) {return e.name;});
                var data = json_data.jsonarray.map(function(e) {return e.age;});
                const ctx = document.getElementById('myChart');
                var config = {
                   type: 'line',
                   data: {
                      labels: labels,
                      datasets: [{
                         label: 'Graph Line',
                         data: data,
                         backgroundColor: 'rgba(0, 119, 204, 0.3)'
                      }]
                   }
                };
                
                var chart = new Chart(ctx, config);
            }
         </script>
   </head>
   <body onload="display_chart()">
    <canvas id="myChart"></canvas>
   </body>
</html>
Chris
  • 18,724
  • 6
  • 46
  • 80
0

Got it to work with the below code. The trick like @The Fool suggested in one of his/her comments

document.forms['myFormId'].addEventListener('submit', (event) => {
    event.preventDefault();
    // TODO do something here to show user that form is being submitted
    fetch(event.target.action, {
        method: 'POST',
        body: new URLSearchParams(new FormData(event.target)) // event.target is the form
    }).then((resp) => {
        return resp.json(); // or resp.text() or whatever the server sends
    }).then((body) => {
        // TODO handle body
    }).catch((error) => {
        // TODO handle error
    });
});

What I did was place my already written JS code within "Todo handle body". That worked!

hsa591
  • 29
  • 5