Render dynamically changing images with same filenames in 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.

2 answers

  • answered 2018-11-08 07:19 thebjorn

    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.

  • answered 2018-11-08 08:03 Dinko Pehar

    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>