Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Render dynamically changing images with same filenames in Flask

Tags:

python

html

flask

I have a flask view function as below:

@app.route('/myfunc', methods = ['POST', 'GET'])
def myfunc():
    var = request.form["samplename"]
    selected_ecg=ecg.loc[ecg['Patient ID'].isin([var])]
    selected_ecg = selected_ecg.drop('Patient ID', 1)
    arr = np.array(selected_ecg)
    y = arr.T
    x=np.array(range(1,189))
    plot.plot(x,y)

    #Remove the old file
    os.remove("static\graph.png")
    #Now save the new image file
    plot.savefig("static\graph.png")

    return render_template("outputs.html")

Outputs.html:

<html>
  <head>

  </head>
   <body>
     <h1>Output page</h1>

      <img src="static/graph.png" />

   </body>

</html>

I use the flask view function to display an image through the outputs.html file. The catch here is that the static image file that is served keeps changing every time based on user inputs. In other words, I keep overwriting the image file based on the inputs the user has selected.

But the problem is that the changing image file is not served. The old image file that was used for first time render is only displayed for every new input of the user.

I have already referred to old posts regarding serving dynamic content in flask. But none of them served useful.

like image 666
Karthick Mohanraj Avatar asked Nov 08 '18 06:11

Karthick Mohanraj


2 Answers

thebjorn's solution is valid. I have found multiple posts on Stack Overflow which suggest identical solutions. To view them, search for how to not cache images on Google. link link2 link3

Below is my solution to your problem. This will delete graph file and create new one with plot.savefig on every GET request to /myfunc. I was not sure on which request you wanted this behavior.

@app.route('/myfunc', methods = ['POST', 'GET'])
def myfunc():
    var = request.form["samplename"]
    selected_ecg=ecg.loc[ecg['Patient ID'].isin([var])]
    selected_ecg = selected_ecg.drop('Patient ID', 1)
    arr = np.array(selected_ecg)
    y = arr.T
    x=np.array(range(1,189))
    plot.plot(x,y)

    new_graph_name = "graph" + str(time.time()) + ".png"

    for filename in os.listdir('static/'):
        if filename.startswith('graph_'):  # not to remove other images
            os.remove('static/' + filename)

    plot.savefig('static/' + new_graph_name)

    return render_template("outputs.html", graph=new_graph_name)

Outputs.html

<html>
  <head>

  </head>
   <body>
     <h1>Output page</h1>

      <img src="{{ url_for('static', filename=graph) }}" />

   </body>

</html>
like image 79
Dinko Pehar Avatar answered Oct 10 '22 07:10

Dinko Pehar


You're running into a caching issue. Static resources, like images, are cached at every point in the chain between your server and the browser. This is a good thing. Most reasonable systems are set up to cache images for at least 1 year at the server (and that's if they're not cached in the browser).

To bust through this cache issue, you'll need to either (i) give the files new names, (ii) reconfigure Vary headers to indicate they shouldn't be cached, or (iii) add a uniqueness fragment -- e.g. instead of using static/graph.png, add a timestamp 'static/graph.png?v=' + (new Date()).valueOf() or a md5 hash.

update: Dinko has given you a fine answer (do read the links he provides). To add cache-busting on the server side, without creating new files, you can calculate an md5 checksum (disadvantage: you'll need to read the entire file):

from hashlib import md5
fname = 'static/graph.png'
with open(fname, 'rb') as fp:
    checksum = md5.new(fp.read()).hexdigest()
fname += "?v" + checksum

or use the last-modified attribute (not always reliable):

from hashlib import md5
fname = 'static/graph.png'
modified_tstamp = str(int(os.stat(fname).st_mtime * 10**6))
fname += "?v" + checksum

both of these methods will serve a cached version as long as the file doesn't change.

like image 32
thebjorn Avatar answered Oct 10 '22 05:10

thebjorn