Home > other >  Flask - show loading page while another time consuming function is running
Flask - show loading page while another time consuming function is running

Time:05-25

Hi everyone. I'm developing my first flask project and I got stuck on the following problem:

I have a simple Flask app:

from flask import Flask, render_template
import map_plotting_test as mpt

app = Flask(__name__)


@app.route('/')
def render_the_map():
    mpt.create_map()
    return render_template("map.html")


if __name__ == '__main__':
    app.run(debug=True)

Problem

mpt.create_map() function here is just making the map, rendering it, then creating the map.html file and saving it to the templates folder: templates/map.html. It works pretty fine, but it takes some noticeable time to finish making the map (around 10-15 seconds).

The problem is that while this function is performed, I see just a blank screen in the browser, and only then does Flask render the finished map.html file.

What I want

What I want to do is to show the loading screen instead of a blank screen while the create_map() function is running. And when the function finishes its work and creates a map.html file - show rendered template to user just like return render_template("map.html") does.

Is there a way to achieve this without much effort? I'm new to Flask, and I would be very grateful for a good explanation.

Thank you!!!

CodePudding user response:

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 ></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:

  1. User opens the page, @app.route('/') function is performed, which is loading page.
  2. Loading page fetching @app.route('/create_map') and runs its functions in the background.
  3. When create_map() function is completed - user is being redirected to the @app.route('/map') which function is rendering all-done map.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 of loading.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 ></div>
</body>

</html>

  • Related