Flask doubles contents of page when refreshing - python

I have a simple Flask script which loads a Pandas-file (created in another .py-file) and displays this.
It shows everything, I want... But as soon as I refresh the page,the same content is added!
here is the Flask-thing:
from flask import Flask,render_template
import Python_script
import pandas as pd
app = Flask(__name__)
#app.route("/")
def home():
a = Python_script.summary()
df=pd.DataFrame(a)
return render_template('simple.html', tables=[df.to_html(classes='overview')], header='true')
if __name__ == "__main__":
app.run(debug=True)
Python_script.summary returns a list
and here's the html-code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
</style>
</head>
<body>
{% for table in tables %}
{{ table|safe }}
{% endfor %}
</body>
</html>
what needs to be done so when you refresh, it doesn't add but it simply updates the page?
Many thanks,
Peter

If I understood correctly from your comment, your Python_script looks like:
e = []
def summary():
a = 2
e.append(a)
return e
If that is the case, then you have a problem. e is a global variable, so any time you call summary() method it will add more values to it. The Flask code looks fine. You need to change your logic for summary in order not to have data extended.

As per your comment, if this is the content of Python_script.py:
e=[]
def summary():
a = 2
e.append(a)
return e
Then you add a debug line to the Flask route:
import Python_script
#app.route("/")
def home():
a = Python_script.summary()
print(Python_script.e)
# ...
You will see that Python_script.e is a global variable and 2 is appended to that list on subsequent requests.
A quick fix may be to make a global within the Flask app, by moving the line which assigns it.
import Python_script
a = Python_script.summary()
#app.route("/")
def home():
print(Python_script.e)
# ...
Now subsequent requests do not append 2 to the list. By doing this a is assigned when the server process starts, rather than when the request is handled, because that line was moved out of the Flask route, and to the global level.
If you're trying to implement a way to maintain this list in real-time, then global variables are not the way to go, this is the job of another storage backend.

Related

Python yield clear output [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 %}

Building an HTML flask web page - input dependent on launch

I am building a website with flask on python. I am new to web development.
I built an HTML page, and now I need it's contents - number of buttons on the page for example - to be possibly different and automatic on each launch of app.py (the flask app running the website). Let's say that the number will be random between 1-10, a number generated in the app.py.
Does this mean that I need to change the HTML on every app.py launch, this by using python and editing the text file "index.html"? Is this bad practice and not a good way of achieving the goal? Are there other better methods to launch an input-dependent HTML page?
Thanks!
Code example:
def change_HTML_page(path,num):
# here read the text file in path, which is an HTML file, page description.
# inside in some place add more rows to describe buttons,
# as many as num.
# Add rows like this one <input type="button" id="i_bnutton" value="i" onclick="change_button_appearence(this)" />
# save text file afer the change
num_of_buttons = randint(0, 10)
page_path = r"docs/pages/index.html"
change_HTML_page(page_path, num_of_buttons);
#app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
Best way is to pass the num_of_buttons inside the html render command and constract all input buttons with a jinja loop inside your html.
Your code should look like below:
FLASK:
#app.route('/')
def index():
num_of_buttons = randint(0, 10)
return render_template('index.html', num_of_buttons=num_of_buttons)
if __name__ == '__main__':
app.run(debug=True)
And inside your HTML:
{% for i in range(0,num_of_buttons) %}
<input type="button" id="{{i}}_bnutton" value="{{i}}" onclick="change_button_appearence(this)" />
{% endfor %}

update a variable in a web app with flask

I have an arduino program and set-up which detects the number of cars from a parking lot. The number of cars is printed on the serial every time when the sensor detects a car in front of the barrier.
I want to print the number of cars from the parking lot into a small web app. I use Tera Term to scan my serial bus and put the output data into a file text ( data.txt) . Then i use python to read the value from that text file and render it into a HTML page on a web app.
Here is the python code :
from flask import Flask, render_template
import logging
import requests
# Initialize the Flask application
app = Flask(__name__)
# Define a route for the default URL, which loads the form
#app.route("/")
def form():
with open('date.txt') as f:
data = []
lines = f.read().splitlines()
data.append(lines)
for i in data:
return render_template('index.html', variable=data)
if __name__ == "__main__":
app.run()
here is index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Arduino Project</title>
<style>
body{
background: antiquewhite;
}
h1 {color:red;
text-align: center;}
h2 {color:blue;
text-align: center;}
</style>
</head>
<body>
<h1> - Arduino - </h1>
<h2> Number of cars in the parking place: {{ variable }}<br></h2>
<script> setTimeout(function(){
window.location.reload(1);
}, 5000);
</script>
</body>
</html>
It works fine, but i want to have only a variable which updates every 5 seconds,when the page is refreshed.I dont want to see all values from the date.txt,just the last one.
This is how my web app looks like with this code until now :
(ignore the error message)
enter image description here
Returning multiples times is not possible.
A return statement is used to end the execution of the function call
and “returns” the result (value of the expression following the return
keyword) to the caller. The statements after the return statements are
not executed.
So this part could be adjusted:
for i in data:
return render_template('index.html', variable=data)
You said you want the last line only. So replace above with this:
return render_template('index.html', variable=data[-1])
Documentation
Getting the last element of a list
https://www.geeksforgeeks.org/python-return-statement/

Replacing text from string with an image using flask

I have written a piece of code that matches if the string has anything of the pattern text.someExtension, In my case, it would be fileName.png in the string, converts it into an img tag and displays on the HTML file using python and flask. Let us take the example string:
"What is the output of this program? e.png"
the code matches e.png and it then replaces e.png by
"<br><img src="{{url_for('static', filename='pics/e.png')}}" /><br>"
The image e.png is put in the folder pics inside the static folder.
If this string is pushed into a flask variable even by adding Markup() to it it isn't rendering the image but showing the following output.
output on html page
why is it so? Any way to make it display the image e.png?
my code is:
import re
from flask import Flask, Markup, render_template
app = Flask(__name__)
def rep(x):
text = re.findall(r'\w+[.]\w+', x)
for i in text:
b ="<img src=\"{{ url_for('static',filename='pics/"+i+"')}}\">"
x=x.replace(i,b)
return x
#app.route('/')
def home():
a = "What is the output of this program? e.png"
a = rep(a)
return render_template('new.html',var=Markup(a))
if __name__ == '__main__':
app.run(debug=True, host='localhost', port=8032)
And the HTML file is,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{var}}
</body>
</html>
The problem is that the value you're passing to your template is a string and even though the string you're inserting is formatted with the {{ brackets, flask doesn't interpret those. Notice if you look at the html being served, it actually contains the string 'url_for'...
You're also not even importing the url_for function from flask, so that wouldn't have worked anyway.
The solution:
import re
from flask import Flask, url_for, Markup, render_template
app = Flask(__name__)
def rep(x):
text = re.findall(r'\w+[.]\w+', x)
for i in text:
b ="<img src='" + url_for('static', filename='pics/'+i) + "'>"
x=x.replace(i,b)
return x
#app.route('/')
def home():
a = "What is the output of this program? e.png"
a = rep(a)
return render_template('new.html', var=Markup(a))
#app.route('/static/<path:path>')
def static_file(path):
return send_from_directory('static', path)
if __name__ == '__main__':
app.run(debug=True, host='localhost', port=8032)
The html file can remain unchanged. This solution
separately tells the flask server to listen and serve the static files under the /static page.
Creates the html with the image url by string concatenation, instead of trying to use template rendering.

Python Flask to change text in a HTML file

I'm trying to create a simple program which will replace {{ test }} with 'Hello world' by following a tutorial, however I am stumped and when I open the HTML file - as {{ test }} is shown on the page instead of 'Hello World' which is what should be appearing.
Any help would be appreciated because I am very unsure on what to do to fix this, thanks.
I am unsure if I have even linked the two files, as to my knowledge it was never specified in the video and I have only just noticed that there is no link between the two files.
Python Code:
from flask import Flask, render_template
app = Flask(__name__)
#app.route('/')
def homepage():
return render_template('index.html', test='hello world')
if __name__ == '__main__':
homepage()
else:
print('Please run this file as main, not as module.')
HTML Code:
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<p> {{ test }} </p>
</body>
</html>
Flask is a webserver. You are not meant to call the functions with app.route. Replace the last part with:
if __name__ == '__main__':
app.run()
and then visit http://127.0.0.1:5000/ in your browser. The template file is not meant to change.
If for some reason you don't want to run a server but you just want to create HTML files, then use Jinja2, the template engine behind Flask.

Categories