Search code examples
javascriptpythonflaskfetch

Flask render_template() not being called after client POST with data


I have a flask app with an index.html that has a submit input that runs a process. I'm trying to catch the error traceroute, then navigate to an error page to display that traceroute from the process if an error occurs. But on the server, my render_template() is not being called.

index.html that has a submit input that runs a process by redirecting to b.html where a method using the Fetch api fetches the mapped path /run, and the process runs. The process runs fine, but if I insert an error for testing, an error is thrown and it sends the following JSON object to b.html:

{'run_result':'run_failure' , 'error_output':output} 

Above, output is stdout=PIPE from subprocess.run() if returncode is not zero.

In b.html, I have this:

const nav = async() => {
    const response = await fetch('run');
    if( response.status !== 200 ) {
        throw new Error('error message');
    }
    return await response.json();
};

document.addEventListener('DOMContentLoaded', () => {
  nav().then(data => {
    if(data != null) {
      if(typeof(data) === 'object') {
        if(data['run_result'] === 'run_success'){       
            // ...successful run <== works fine!                                    
        } else {
          var error_output = data['error_output'];
          var sent_data = { 'data': error_output};
          fetch(`/run_error`, {                     // posts data to server uri
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(sent_data)
          })

          // ***I used to have '=> response.json()' here, but I got SyntaxError: "JSON.parse: unexpected character at line 1 column 1..." 
          .then(response => response)               

          // ***console only displays 'success:' here, not sent_data value
          .then(console.log('Success:', sent_data));    
        }
      }
    }
  });
});

On the server, I have:

# server output shows the below is called: 
#   127.0.0.1 - - [..date..] "POST /run_error HTTP/1.1" 200
@app.route('/run_error', methods=['POST'])  
    def error():
        if request.method = 'POST':
            request_json = request.json
            error_output = request_json['data']

            # perfectly prints out a str version of error trace
            print('***error:', error_output)    

            # run_error.hthml is **not** rendered with output - Why?
            return render_template('run_error.html', error_output=error_output)

        

Why doesn't render_template() get called?

4/20/21 RESPONSE

I was unable to do like you suggested below due to syntax errors I got when I ran.

.then(
    console.log('Success:', sent_data);
    var response = JSON.parse(response);
    var error_output = response["error_output"]; 
    window.location.href = "display_error/"+error_output;
); 

...but here is where I am now (the commented out then() statement prints the correct value to console):

document.addEventListener('DOMContentLoaded', () => {
  nav().then(data => {
    if(data != null) {
      if(typeof(data) === 'object') {
        if(data['run_result'] === 'run_success'){       
            // ...successful run <== works fine!                                    
        } else {
          var error_output = data['error_output'];
          var sent_data = { 'data': error_output};
          fetch(`/run_error`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(sent_data)
          })
          .then(response => response.text())    
          .then(response => JSON.parse(response))    
          //.then(response => console.log(response['error_output']))    
          .then(response => response['error_output'])    
          .then(response => {
            window.location.href = 'display_error/' + response;
          })    
        }
      }
    }
  });
});


@app.route('/run_error', methods=['POST'])  
def error():
    if request.method = 'POST':
        request_json = request.json
        error_output = request_json['data'] 
        return json.dumps({'error_output': error_ouput})

@app.route('/display_error/<error_output>', methods=['GET'])
def display_error(error_ouput):
    return render_template('run_error.html', error_output=error_output)

When it hits the error, I get a 404. The browser URL field shows:

127.0.0.1:5000/display_error/Traceback....

^^^Traceback has no quotes. Should it?

The server console shows:

127.0.0.1 - - [date] "GET /display_error/Traceback...." 404

Suggestions?


Solution

  • It is because you are returning the template within the handling of a POST request. You are actually parsing the template as a response to the client instead of as a page to be rendered. Instead, I recommend you to redirect to another view function to display the error with a GET request from the client side, which you pass the error_output to as a value of a query string. If you need to run some logic on the output beforehand, you can keep the original view function error. Else, you can just make use of the new one display_error, without having to go through the first one.

    @app.route('/run_error/', methods=['POST'])  
    def error():
        if request.method = 'POST':
            request_json = request.json
            error_output = request_json['data'] 
    
            return json.dumps({'error_output': error_ouput})
    
    @app.route('/display_error/', methods=['GET'])
    def display_error():
        error_output = request.args.get('error_output')
        return render_template('run_error.html', error_output=error_output)
    

    The client side will then after a successfull post request (or without it) redirect to the display_error view function and pass error_output as a parameter:

    .then(response => response)               
    .then(
        console.log('Success:', sent_data);
        var response = JSON.parse(response);
        var error_output = response["error_output"]; 
        window.location.href = "/display_error/?error_output="+error_output;
    );