Make a client POST request to Flask render_template endpoint - javascript

After long searching on here and on the web I could not find what I require. Here is a distilled minimum verifiable example of my issue.
To set the background for the problem, here is the actual use case that I intend to implement. There is a heatmap on endpoint /heatmap, users can click on the cells and they should be redirected to a different endpoint /more-info-on-cell. Depending on the cell clicked, the user will get different results. I almost get the intended result. The only issue is that the page is not really properly rendered because the javascript of /more-info-on-cell does not get executed for some reason.
Here is the folder structure that you require for this example:
root
|-templates
| -index.html
| -page.html
|-index.py
And here are the files:
index.html
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<a id="clickMe" style="cursor: pointer">Click here to send POST to render template on the backend</a>
</body>
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
$("#clickMe").on('click', function() {
console.log('clicked, making a request')
$.ajax({
type: 'POST',
url: '/',
data: JSON.stringify({}),
contentType: 'application/json',
success: function(response) { window.document.write(response); }
})
})
})
</script>
</html>
page.html
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
The content of the document......
</body>
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
alert('the doc is ready');
})
</script>
</html>
index.py
#!/usr/bin/env python3
from flask import Flask, request, render_template, jsonify
app = Flask(__name__)
#app.route("/", methods=["GET", "POST"])
def index():
if request.method == "GET":
return render_template('index.html')
elif request.method == "POST":
return render_template('page.html')
You can verify that if you click on the link in index.html you are successfully redirected to page.html but its javascript is never executed. And so you never get the alert that we have defined in page.html.
The problem should be solved by completely working with the flask backend here, since the templates in my actual use case have jinja variables in them too. Everything works perfectly if you make a request through a form with method having the correct url and action being POST. Although, as soon as I start making javascript client POST requests, it sort of works in that I get to the right page, but it is rendered incorrectly + javascript on document ready never gets executed.

For anyone who has such a use case too, ensure that you use a method post like below (put it inside the index.html in this case):
<script>
$(document).ready(function() {
$("#clickMe").on('click', function() {
console.log('clicked, making a request')
post('/', {'key-1': 'val-1'});
})
})
function post(path, params, method='post') {
const form = document.createElement('form');
form.method = method;
form.action = path;
for (const key in params) {
if (params.hasOwnProperty(key)) {
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = key;
hiddenField.value = params[key];
form.appendChild(hiddenField);
}
}
document.body.appendChild(form);
form.submit();
}
</script>
This creates a hidden form on the web page and then submits the form, from then onwards, flask processes everything properly.

Related

Flask returning a generator and handling it in JavaScript [duplicate]

I have a view that generates data and streams it in real time. I can't figure out how to send this data to a variable that I can use in my HTML template. My current solution just outputs the data to a blank page as it arrives, which works, but I want to include it in a larger page with formatting. How do I update, format, and display the data as it is streamed to the page?
import flask
import time, math
app = flask.Flask(__name__)
#app.route('/')
def index():
def inner():
# simulate a long process to watch
for i in range(500):
j = math.sqrt(i)
time.sleep(1)
# this value should be inserted into an HTML template
yield str(i) + '<br/>\n'
return flask.Response(inner(), mimetype='text/html')
app.run(debug=True)
You can stream data in a response, but you can't dynamically update a template the way you describe. The template is rendered once on the server side, then sent to the client.
One solution is to use JavaScript to read the streamed response and output the data on the client side. Use XMLHttpRequest to make a request to the endpoint that will stream the data. Then periodically read from the stream until it's done.
This introduces complexity, but allows updating the page directly and gives complete control over what the output looks like. The following example demonstrates that by displaying both the current value and the log of all values.
This example assumes a very simple message format: a single line of data, followed by a newline. This can be as complex as needed, as long as there's a way to identify each message. For example, each loop could return a JSON object which the client decodes.
from math import sqrt
from time import sleep
from flask import Flask, render_template
app = Flask(__name__)
#app.route("/")
def index():
return render_template("index.html")
#app.route("/stream")
def stream():
def generate():
for i in range(500):
yield "{}\n".format(sqrt(i))
sleep(1)
return app.response_class(generate(), mimetype="text/plain")
<p>This is the latest output: <span id="latest"></span></p>
<p>This is all the output:</p>
<ul id="output"></ul>
<script>
var latest = document.getElementById('latest');
var output = document.getElementById('output');
var xhr = new XMLHttpRequest();
xhr.open('GET', '{{ url_for('stream') }}');
xhr.send();
var position = 0;
function handleNewData() {
// the response text include the entire response so far
// split the messages, then take the messages that haven't been handled yet
// position tracks how many messages have been handled
// messages end with a newline, so split will always show one extra empty message at the end
var messages = xhr.responseText.split('\n');
messages.slice(position, -1).forEach(function(value) {
latest.textContent = value; // update the latest value in place
// build and append a new item to a list to log all output
var item = document.createElement('li');
item.textContent = value;
output.appendChild(item);
});
position = messages.length - 1;
}
var timer;
timer = setInterval(function() {
// check the response for new data
handleNewData();
// stop checking once the response has ended
if (xhr.readyState == XMLHttpRequest.DONE) {
clearInterval(timer);
latest.textContent = 'Done';
}
}, 1000);
</script>
An <iframe> can be used to display streamed HTML output, but it has some downsides. The frame is a separate document, which increases resource usage. Since it's only displaying the streamed data, it might not be easy to style it like the rest of the page. It can only append data, so long output will render below the visible scroll area. It can't modify other parts of the page in response to each event.
index.html renders the page with a frame pointed at the stream endpoint. The frame has fairly small default dimensions, so you may want to to style it further. Use render_template_string, which knows to escape variables, to render the HTML for each item (or use render_template with a more complex template file). An initial line can be yielded to load CSS in the frame first.
from flask import render_template_string, stream_with_context
#app.route("/stream")
def stream():
#stream_with_context
def generate():
yield render_template_string('<link rel=stylesheet href="{{ url_for("static", filename="stream.css") }}">')
for i in range(500):
yield render_template_string("<p>{{ i }}: {{ s }}</p>\n", i=i, s=sqrt(i))
sleep(1)
return app.response_class(generate())
<p>This is all the output:</p>
<iframe src="{{ url_for("stream") }}"></iframe>
5 years late, but this actually can be done the way you were initially trying to do it, javascript is totally unnecessary (Edit: the author of the accepted answer added the iframe section after I wrote this). You just have to include embed the output as an <iframe>:
from flask import Flask, render_template, Response
import time, math
app = Flask(__name__)
#app.route('/content')
def content():
"""
Render the content a url different from index
"""
def inner():
# simulate a long process to watch
for i in range(500):
j = math.sqrt(i)
time.sleep(1)
# this value should be inserted into an HTML template
yield str(i) + '<br/>\n'
return Response(inner(), mimetype='text/html')
#app.route('/')
def index():
"""
Render a template at the index. The content will be embedded in this template
"""
return render_template('index.html.jinja')
app.run(debug=True)
Then the 'index.html.jinja' file will include an <iframe> with the content url as the src, which would something like:
<!doctype html>
<head>
<title>Title</title>
</head>
<body>
<div>
<iframe frameborder="0"
onresize="noresize"
style='background: transparent; width: 100%; height:100%;'
src="{{ url_for('content')}}">
</iframe>
</div>
</body>
When rendering user-provided data render_template_string() should be used to render the content to avoid injection attacks. However, I left this out of the example because it adds additional complexity, is outside the scope of the question, isn't relevant to the OP since he isn't streaming user-provided data, and won't be relevant for the vast majority of people seeing this post since streaming user-provided data is a far edge case that few if any people will ever have to do.
Originally I had a similar problem to the one posted here where a model is being trained and the update should be stationary and formatted in Html. The following answer is for future reference or people trying to solve the same problem and need inspiration.
A good solution to achieve this is to use an EventSource in Javascript, as described here. This listener can be started using a context variable, such as from a form or other source. The listener is stopped by sending a stop command. A sleep command is used for visualization without doing any real work in this example. Lastly, Html formatting can be achieved using Javascript DOM-Manipulation.
Flask Application
import flask
import time
app = flask.Flask(__name__)
#app.route('/learn')
def learn():
def update():
yield 'data: Prepare for learning\n\n'
# Preapre model
time.sleep(1.0)
for i in range(1, 101):
# Perform update
time.sleep(0.1)
yield f'data: {i}%\n\n'
yield 'data: close\n\n'
return flask.Response(update(), mimetype='text/event-stream')
#app.route('/', methods=['GET', 'POST'])
def index():
train_model = False
if flask.request.method == 'POST':
if 'train_model' in list(flask.request.form):
train_model = True
return flask.render_template('index.html', train_model=train_model)
app.run(threaded=True)
HTML Template
<form action="/" method="post">
<input name="train_model" type="submit" value="Train Model" />
</form>
<p id="learn_output"></p>
{% if train_model %}
<script>
var target_output = document.getElementById("learn_output");
var learn_update = new EventSource("/learn");
learn_update.onmessage = function (e) {
if (e.data == "close") {
learn_update.close();
} else {
target_output.innerHTML = "Status: " + e.data;
}
};
</script>
{% endif %}

How would I transfer information like a variable from javascript to python using AJAX and Flask

I am messing around with Flask and Ajax to see what I can do with it for a bigger project I'm working on. I am trying to get some data in a variable into my python program to be able to use and manipulate. I have a script in an html file that returns the current URL. How can I get the url into my python program with AJAX?
I will attach my relevant code below:
index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HELP</title>
</head>
<body>
<p>I am {{person}} and i am {{age}} years old, I am a {{ql}}</p>
<form name="passdata" action="." method="post">
<label>Name:</label>
<input type="text" name="name">
<label>Age:</label>
<input type="text" name="age">
<label>Date of Birth:</label>
<input type="text" name="dateofbirth">
<input type="submit" value="submit">
</form>
<p id="demo"></p>
<script>
let geturl = window.
document.getElementById("demo").innerHTML =
"The full URL of this page is:<br>" + geturl;
</script> //getting the current url
</body>
</html>
main.py
from flask import Flask, render_template, request
app = Flask(__name__)
name = "random"
age = "21"
qualification = "software engineer"
#app.route('/')
def index():
return render_template('index.html', person=name, age=age, ql=qualification)
#app.route('/', methods=['POST'])
def getvalue():
name2 = request.form['name']
age = request.form['age']
db = request.form['dateofbirth']
return name2
if __name__ == '__main__':
app.run(debug=True)
let me know if you need any clarification. Thanks in advance.
Usually when you are sending variables to routes in flask you will have the route designed as:
#app.route('/route_to_send_to/<variable>', methods=['GET'])
def route_to_send_to(variable):
variable_sent = variable
return jsonify({'variable':variable})
Another hint usually when working with ajax calls the response may be a json response which you can send back with jsonify.
All you need to do is call it to the route replacing with the variable you want to send.
You can also send with Ajax a post request to have the data hidden, but then you will need to change the route to handle post requests.
The Ajax call may be something like:
var url = '/route_to_send_to/ + name;
return $.ajax({
type: "GET",
url: url,
}).then(function(data) {// Do something with the responce})
});
Considering the comments below I might suggest you read this article to get a better understanding of how flask and ajax work together.
Also This amazing Flask tutorial by Miguel Grinberg is probably one of the best resources I have ever come across for learning flask, the framework and good conducts with it. It goes through everything from the absolute basic to extremely advanced topics and is well worth the time.

Django AJAX returns undefined instead of the variables

So I have a simple Django script which I've found online for an AJAX function that runs a Python script and gets the output via stdout.
views.py
from django.shortcuts import render
def index(request):
return render(request,'homepage/page.html')
homepage/page.html
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>test</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript">
$(function()
{
$('#clickme').click(function(){
alert('Im going to start processing');
$.ajax({
url: "static/homepage/js/external_func.py",
type: "POST",
datatype:"json",
data: {'key':'value','key2':'value2'},
success: function(response){
console.log(response.keys);
console.log(response.message);
}
});
});
});
</script>
</head>
<body>
<button id="clickme"> click me </button>
</body>
</html>
So you can see my url is linked to external_func.py which runs after the button is clicked. The script then returns a json.
external_func.py
import sys
import json
import cgi
fs = cgi.FieldStorage()
sys.stdout.write("Content-Type: application/json")
sys.stdout.write("\n")
sys.stdout.write("\n")
result = {}
result['success'] = True
result['message'] = "The command Completed Successfully"
result['keys'] = ",".join(fs.keys())
d = {}
for k in fs.keys():
d[k] = fs.getvalue(k)
result['data'] = d
sys.stdout.write(json.dumps(result, indent=1))
sys.stdout.write("\n")
sys.stdout.close()
However, when I run the server and clicked on the button, the console shows undefined for both values, meaning response.keys and response.message is undefined.
Now, when I instead switch the code to console.log(response) in homepage/page.html. The console prints out the entire external_func.py code in text.
I couldn't find a solution online. It seems like people rarely calls a Python script in an AJAX request, I see a lot of forum posts about AJAX calling for a php code instead.
EDIT1:
I have to clarify one thing. This is just a small section of my project which I want to run some test on. In my actual project, I will have a function in python that takes a long time to compute, hence I prefer to have a webpage partially rendered with a waiting icon while the function processes. The output from the function will then be displayed on a webpage.
You have a django app, and yet you are using CGI for this function? Why? Why not simply make the function another django view? Serving your response with django is much superior to CGI, unless that function significantly bloats or slows down your django. It is as easy as this:
from django.http import JsonResponse
def func(request):
result = ...
return JsonResponse(result)
If you really want to separate this into a CGI script, the most likely reason you are failing to get a response is your web server not being configured to process the CGI request. (Your Developer Tools Network tab is a great help for diagnosing exactly what kind of response you got.) For security reasons CGI is not enabled by default. You need to tell Apache (or whatever web server you are using) that CGI should be enabled for that directory, and that it should be associated with .py files.

Failing to pass data from client (JS) to server (Flask) with Ajax

First time ever using jQuery to pass a dictionary filled through a JS script to my server written in Python (Flask) when the user hits a submit button (I read somewhere that AJAX was the way to go).
Something must be broken since in the server function described below, request.get_data() or request.form are None. If any seasoned programmer could give recommendation on how to set up a jQuery request, it would be much appreciated.
In Python, I have the following:
#app.route('/submit',methods=['POST'])
def submit():
info = request.form['info']
action = request.form['action']
...
To handle the dictionary info. On my html file, I load AJAX through:
<head>
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
</script>
</head>
Define my "submit" button as followed:
<button class="btn" onclick="send(event,'s')" id="SUBMIT">Submit</button>
And handle the action through the script:
<script>
var dict = [];
function send(event,action) {
$.post('/submit', {
info: dict,
action: action
}).done(function() {
}).fail(function() {
});
}
...
</script>
Convert request.form to dictionary and print it you can able get the values
print(request.form.to_dict())

Jinja2 : call function on click

I'm hacking a cms-like system that use Jinja2 and Javascript in frontend and Python in backend.
I implemented some Python functions on backend that do stuff on database.
I want to launch that functions from HTML pages, so i used Jinja2.
The problem is that the snippets {% %} and {{ }} are always parsed and processed when HTML is loaded.
I want to execute that functions when I click a button or a link.
How could I make it works?
Jinja2 is a template engine. You are wrong about its use.
You could create a small app in some lightweight web framework, like Flask or Bottle, and route some ajax routes to expected methods.
Here is an example using Flask:
backend.py
import os
from json import dumps
from flask import Flask, render_template
app = Flask(__name__)
#app.route("/")
def index():
return render_template('cmd.html')
#app.route("/cmd")
def cmd():
osname = os.uname()[3]
print(osname)
return dumps({'name': osname})
if __name__ == "__main__":
app.run()
As described in docs, templates must be in a folder called template inside the project folder.
cmd.html
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js"></script>
<script type="text/javascript">
function cmd(){
$.ajax({
type: "GET",
url: "http://0.0.0.0:5000/cmd",
success: function (data) {
$("#result").html("dfsdfds")
},
});
}
</script>
</head>
<body>
Item
<div id="result"></div>
</body>
</html>
To execute it just run python backend.py. Open your browser and go to http://127.0.0.1:500
The app runs a command on backend and returns the result.

Categories

Resources