Display a ‘loading’ message while a time consuming function is executed in Flask
This can be done by using a div
that contains a 'loading gif' image. When the submit button is clicked, the div is displayed using javascript.
To implement this, you can take a look at this website: http://web.archive.org/web/20181023063601/http://www.netavatar.co.in/2011/05/31/how-to-show-a-loading-gif-image-while-a-page-loads-using-javascript-and-css/
Flask - show loading page while another time consuming function is running
Finally I found the solution!
Thanks to Laurel's answer. I'll just make it more nice and clear.
What I've done
I redesigned my Flask app, so it looks like this:
from flask import Flask, render_template
import map_plotting_module as mpm
app = Flask(__name__)
@app.route('/')
def loading():
return render_template("loading.html")
@app.route('/map')
def show_map():
return render_template("map.html")
@app.route('/create_map')
def create_map():
mpm.create_map()
return "Map created"
if __name__ == '__main__':
app.run()
When user opens the page, flask stars rendering the loading.html
file.
In this file you have to add the following code to the <head>
section:
<script>
function navigate() {
window.location.href = 'map'; // Redirects user to the /map route when 'create_map' is finished
}
fetch('create_map').then(navigate); // Performing 'create_map' and then calls navigate() function, declared above
</script>
Then, add a loading wheel div to your <body>
section:
<body>
<div class="loader"></div>
</body>
If it's still not clear for you - please check my example at the end of the answer
Explanation
In the <script>
section we have to declare navigate()
function. It just redirects the user to the desired reference, /map
in the current case.
fetch()
is the analog to jQuery.ajax()
- read more. It's just fetching the app route to /create_map
, awaits it to be done in the "backround", and then performs action in the .then()
block - navigate()
function in our case.
So the workflow is:
- User opens the page,
@app.route('/')
function is performed, which is loading page. - Loading page fetching
@app.route('/create_map')
and runs its functions in the background. - When
create_map()
function is completed - user is being redirected to the@app.route('/map')
which function is rendering all-donemap.html
template.
Few recommendations from me
- If you want your loading page to have an icon, just add the following tab to your
<head>
section ofloading.html
:
<link rel="icon" href="/static/<icon_file>">
Pay attention, Flask is searching for media in the
/static
folder. Put your media in it. Otherwise, media will not be rendered!
- If you want your loading page to have a nice CSS loader, consider visiting this page: loading.io. I really enjoyed it.
Finally, here's code snippet as an example of loader:
.loader {
position: absolute;
top: 50%;
left: 50%;
margin: -56px 0 0 -56px;
}
.loader:after {
content: " ";
display: block;
width: 110px;
height: 110px;
border-radius: 50%;
border: 1px solid;
border-color: #0aa13a transparent #47a90e transparent;
animation: ring 1.2s linear infinite;
}
@keyframes ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Loading...</title>
</head>
<body>
<div class="loader"></div>
</body>
</html>
Flask - use javascript to display loading page while waiting for task to complete
Since you're using a GET request, you can have your anchor tag route to cooking
, render the cooking.html
template, then use window.location.replace()
to redirect to pasta
. The long task in pasta()
will be performed before pasta.html
is rendered.
In menu.html
:
<a href="{{ url_for('cooking') }}">Pasta</a>
In cooking.html
:
<script> window.location.replace('/pasta'); </script>
In the case of a POST request, you would need to send the form values to cooking
, pass them to the template, then send them again to pasta
using AJAX, and finally redirect in the callback function.
In menu.html
:
<form action="{{ url_for('cooking') }}" method='POST'>
In cooking()
:
@app.route('/cooking')
def cooking():
return render_template('cooking.html', form_data=request.form['form_data'])
In pasta()
:
@app.route('/pasta', methods=['GET', 'POST'])
def pasta():
if request.method == 'GET':
render_template('pasta.html')
# (perform some long task here)
return make_response('POST request successful', 200)
In cooking.html
:
<script>
$.ajax({
type: 'POST',
url: '/pasta',
data: {
form_data: '{{form_data}}',
},
success: function() {
window.location.replace('/pasta');
}
});
</script>
Flask: show contents before time-consuming function completes
As mentioned in comments, you need to make the architecture event-driven by using celery task manager. f()
is defined as a task and called async.
Define f()
as a task with celery in a specific file (e.x. task.py
) like this:
from celery import Celery
# Creating a celery instance with redis as message broker.
app = Celery('my_task', broker='redis://localhost')
@app.task
def f(arguments):
"""
This is a celery task. Just a normal function with task decorator.
Note that we use the decorator from a celery instance.
"""
pass # do f here
# save the result in database or cache and map it to task id
After the abouve definition, you need to import f()
from task.py
and call it by passing arguments like this:
...the usual flask stuff here...
from task import f # import f function from task.py
@app.route('/compute')
def compute():
result = f.delay(arguments) # call f async like this
return render_template('page.html', computed_result_task_id = result.task_id) # You can't return the result unless wait for the result, so just return the task id and get the result later via another API.
# save task result in a database or cache and map to task_id for further retrieval
It was a brief introduction, use the documentation for details of implementations.
And also, you need to execute the task like this:
celery -A task worker -l ERROR --detach
(There are more options to run and manage a task in documentation.)
How to add a loading gif to the page when a function runs in the background in Flask?
This needs to be done on the client side only.
Add jquery in your header section:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
Add this to your submit button code:
onclick="$('#loading').show();"
Get a loading.gif image Add below code after your form in html
<div id="loading" style="display:none;"><img src="loading.gif" alt="Sample Image" />Loading!</div>
References:
1 2
Related Topics
How to Convert SQL Query Result to Pandas Data Structure
How to Customise Qgroupbox Title in Pyqt5
Closest Equivalent of a Factor Variable in Python Pandas
Find in Files Using Ruby or Python
How to Print Variable and String on Same Line in Python
Django HTML Template Can't Find Static CSS and Js Files
What Is the Problem with Shadowing Names Defined in Outer Scopes
How to Get the Index of a Maximum Element in a Numpy Array Along One Axis
How to Serve Multiple Clients Using Just Flask App.Run() as Standalone
Using Beautiful Soup to Convert CSS Attributes to Individual HTML Attributes
Numpy/Scipy Equivalent of R Ecdf(X)(X) Function
Using Perl, Python, or Ruby, How to Write a Program to "Click" on the Screen at Scheduled Time
Create Dynamic Urls in Flask with Url_For()
Possible to Share In-Memory Data Between 2 Separate Processes
Does Python Optimize Modules When They Are Imported Multiple Times