Flask rendering a unique file name in browser - python

I am attempting to make an app where a user can upload a CSV file of data and view the data thru the browser on a different flask route which would also a show the plot of the data.
I having issues in my code trying to show the plot of the data. (referencing the static file .png) I can get the pandas dataframe to HTML to work, but in my table.html file I am trying to reference a png plot created with matplot lib and saved to a static directory.
<img src="{{url_for('static', filename=filename)}}" />
All of this experimenting is due to cache issues with the browser so I am creating a unique filename with next_file_name function, and I think this is where I am getting screwed up in the HTML & Jinja trying to reference this unique file name.. I am hoping that a unique filename may be a fix for the cache issues I am observing. The png files are saving properly with the function plot0, plot1, plot2, plot3, etc...
I was hoping to be able to create something where I can repeat the process over & over of analyzing new data and getting retrieving a fresh new plot of the data. Any tips help, thanks
from flask import Flask, make_response, request, render_template
from werkzeug.utils import secure_filename
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import time
app = Flask(__name__, static_url_path='/static')
num = 0
def next_file_name(num):
return 'static/plot%d.png' % num
#app.route('/')
def form():
return render_template('form.html')
#app.route('/transform', methods=["POST"])
def transform_view():
global num
f = request.files['data_file']
filename = secure_filename(f.filename)
f.save(filename)
df = pd.read_csv(filename, index_col='Date', parse_dates=True)
OAT = pd.Series(df['OAT'])
RAT = pd.Series(df['RAT'])
MAT = pd.Series(df['MAT'])
df_OATrat = (OAT - RAT)
df_MATrat = (MAT - RAT)
plt.scatter(df_OATrat,df_MATrat, color='grey', marker='+')
plt.xlabel('OAT-RAT')
plt.ylabel('MAT-RAT')
plt.title('Economizer Diagnostics')
plt.plot([0,-18],[0,-18], color='green', label='100% OSA during ideal conditions')
plt.plot([0,20],[0,5], color='red', label='Minimum OSA in cooling mode')
plt.plot([0,-38],[0,-9.5], color='blue', label='Minimum OSA in heating mode')
plt.plot([0,0],[-20,10], color='black')
plt.plot([-30,20],[0,0], color='black')
#plt.legend()
plt.text(-3, -28, time.ctime(), fontsize=9)
pic = next_file_name(num)
plt.savefig(pic)
num+=1
resp = make_response(render_template('table.html', tables=[df.to_html(classes='data')], titles=df.columns.values, filename='pic'))
resp.cache_control.no_cache = True
return resp
if __name__ == '__main__':
app.run(debug=True)
table.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv='cache-control' content='no-cache'>
<meta http-equiv='expires' content='0'>
<meta http-equiv='pragma' content='no-cache'>
<title>Title</title>
</head>
<body>
<h1>Economizer Data Plot</h1>
<img src="{{url_for('static', filename=filename)}}" />
</form>
{% for table in tables %}
{{ table|safe }}
{% endfor %}
</body>
</html>

You made pic a string instead of using it's value
... filename=pic

If it's a caching issue, it would be easier to either add automatic cache busting to your image files or disable cache with an after-request callback, which triggers the execution of a function at the end of a request.
#app.after_request
def add_header(response):
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "0"
response.headers['Cache-Control'] = 'public, max-age=0'
return response
See this solution for more information.

Related

Geoviews map 403 load error in python flask Application

I am trying to generate a map with data to be displayed in a flask application. All that is displayed when I run the app is Figure(id='1021', ...). I also get this error in console: [Error] Failed to load resource: the server responded with a status of 403 () (bokeh.min.js.map, line 0). Any Ideas?
app.py
from flask import Flask, render_template
import geoviews as gv
import project_4 as map
import holoviews as hv
import geoviews.tile_sources as gvts
import pandas as pd
from bokeh.models import HoverTool
from geoviews import dim, opts
import numpy as np
app = Flask(__name__)
#app.route("/")
def index():
# Load the data and create the GeoViews points
college_data = pd.read_csv('updated_locations.csv')
college_gv_points = gv.Points(college_data, ['longitude', 'latitude'], ['university', 'num_students_attended'])
# Create the hover tool
tooltips = [('Unviversity', '#university'),
('Attendees', '#num_students_attended'),]
hover = HoverTool(tooltips=tooltips)
# Create the light and dark plots
light_plot = (gvts.CartoLight * college_gv_points).opts(
opts.Points(alpha=0.3,
hover_line_color='black',
color = 'blue', line_color='black', xaxis=None, yaxis=None,
tools=[hover],size=np.sqrt(dim('num_students_attended'))*2,
hover_fill_color='blue', hover_fill_alpha=0.2))
dark_plot = (gvts.CartoDark.options(alpha=0.8) * college_gv_points).opts(
opts.Points(alpha=0.6,
hover_line_color='black',
color = 'orange', line_color=None, xaxis=None, yaxis=None,
tools=[hover],size=np.sqrt(dim('num_students_attended'))*2,
hover_fill_color='orange', hover_fill_alpha=0.4))
# Render the map using Holoviews
hv_points = hv.render(dark_plot)
return render_template('index.html', plot=hv_points)
if __name__ == '__main__':
app.run(port=8000, debug=True)
index.html
<!DOCTYPE html>
<html>
<head>
<link
href="http://cdn.pydata.org/bokeh/release/bokeh-1.3.4.min.css"
rel="stylesheet"
type="text/css"
/>
<script src="http://cdn.pydata.org/bokeh/release/bokeh-1.3.4.min.js"></script>
</head>
<body>
{{ plot | safe }}
</body>
</html>

Dynamic plotly visualization (or image) in Flask Python

I've got a plotly visualization ('viz.html') in a html format. I've embedded it in my webpage using Flask's url_for() syntax, referencing the static folder:
<iframe id="igraph" scrolling="no" style="border:none;" seamless="seamless" src="{{ url_for('static', filename='viz.html') }}" height="400" width="120%"></iframe>
It deploys with no issues. However, my 'viz.html' will be rewritten frequently, updating itself with new data points. The updated visualization won't show with a control + R or webpage refresh. If I press control + F5 and do a cache refresh, then updated visualization will show.
I want users to be able to see the updated visualization without having to manually refresh the cache. So far I've tried:
Reloading flask app when file changes are detected in 'static' folder (Reload Flask app when template file changes)
check_folder = './static'
check_files = []
for dirname, dirs, files in os.walk(check_folder):
for filename in files:
filename = os.path.join(check_folder, filename)
check_files += [filename]
app.run(debug = True, extra_files = check_files)
Disabling browser cacheing on html page (http://cristian.sulea.net/blog/disable-browser-caching-with-meta-html-tags)
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
Disabling Python on Flask Cacheing
resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
resp.headers["Pragma"] = "no-cache"
resp.headers["Expires"] = "0"
Would like to know, is what I'm trying to do even possible?
If it's possible how can I go about doing it? Advice appreciated!
I am not a web developer but I worked with a few closely for 3 years. As far as I know, there is no way to control what other browsers saved as cache. If my browser has a version saved in the cache, it will use that one. Usually, browsers will check if all the files are updated, and eventually, it will refresh for everyone. I actually heard developers being asked "why didn't you fix what I told you?" and they were like, "I have, just hard refresh." So I don't think there is a way. IDK if anything new is out there.
I figured it out!
Instead of writing the plotly fig to a viz.html file, do viz = Markup(fig) in flask:
#app.route("/index/",methods=["POST", "GET"])
def foo():
from flask import Markup
# include_plotlyjs = True builds in the js library
# output_type = 'div' outputs the html code
viz = plot(fig, include_plotlyjs = True, output_type = 'div')
# Markup directly renders the html code
viz = Markup(viz)
return render_template('foo.html', viz = viz)
In foo.html, you can directly use viz as so:
<html>
<body>
viz
</body>
<html>
viola!

Chart Pandas DataFrame columns using Holoviews DynamicMap served via Flask

Following this tutorial, I am trying to visualise a dataset using Holoviews instead of Bokeh (sample data available here as a CSV file), serving the results using Flask. I decided to use Flask and not Bokeh Server because I am building a larger workflow using the former.
My code is the following:
from flask import Flask, render_template, request
import numpy as np
import pandas as pd
from datetime import datetime
from bokeh.embed import components
from bokeh.io import curdoc
import holoviews as hv
hv.extension("bokeh")
app = Flask(__name__)
renderer = hv.renderer('bokeh')
infile = "./uploads/test.csv"
def loadRegionData(regionProperty, **kwargs):
df = pd.read_csv(infile, parse_dates=['Datetime'])
df1 = df[regionProperty]
df = pd.concat([df['Datetime'],df1], axis=1)
return hv.Curve(df)
colNames = ((pd.read_csv(infile, nrows=1)).drop(['Datetime'], axis=1)).columns.values
dmap = hv.DynamicMap(loadRegionData, kdims='RegionProperty').redim.values(RegionProperty=colNames)
hvplot = renderer.get_plot(dmap)
plot = hvplot.state
plot.name = 'plot'
curdoc().add_root(plot)
#app.route("/")
def index():
# Embed plot into HTML via Flask Render
script, div = components(plot)
return render_template("index.html", script=script, div=div)
if __name__ == '__main__':
app.run(port=5000, debug=True)
I am running into the following (unrelated issues)
When I deploy using Flask, the dropdowns to select the columns do not appear. I suspect that is because I am not returning/referring to the correct variables from the index() function into my index.html:
<html>
<head>
<link
href="http://cdn.bokeh.org/bokeh/release/bokeh-1.0.2.min.css"
rel="stylesheet" type="text/css">
<link
href="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-1.0.2.min.css"
rel="stylesheet" type="text/css">
<script src="http://cdn.bokeh.org/bokeh/release/bokeh-1.0.2.min.js"></script>
<script src="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-1.0.2.min.js"></script>
</head>
<body>
<h1>Holoview test</h1>
{{ script|safe }}
{{ div|safe }}
</body>
</html>
How can I get Flask to also show the dropdown selector?
An unrelated issue which I found when I tested this app using Bokeh Server, and which could also arise in the Flask implementation, is that the scales do not adjust dynamically based on my column selection. Perhaps this can go as a separate question on SO, but I thought to include it here for now to keep things together.

Flask stuck loading after opening a certain route with a function

I'm relatively new to Python and Flask. I've been trying to create a web application that reads data readings from a .txt file and plots them onto a matplotlib plot. In this web-app, I have a front page with 3 buttons. These buttons redirect to different routes with functions that read data and plot them onto a matplotlib plot. The web-app works perfectly only for the first time I go to either of these routes. After that nothing loads anymore. I guess I have an infinite loop of some sort but I can't figure it out. Also, after the website gets stuck the Python process starts consuming more resources.
The problem persists only when I open this route on the web-app:
#app.route("/temperature/")
This loads without problems on the web-page, but only for one time and the whole web-app gets stuck and I cannot access any of the other routes either.
Thanks in advance!
EDIT 1 - Whole code below
cloudapp.py (Python sourcecode that runs Flask and the functions)
from flask import Flask
from flask import render_template
from flask import request
import numpy as np
import matplotlib.pyplot as plt, mpld3
from datetime import datetime
app = Flask(__name__, template_folder='C:\Users\Valtteri\Desktop\cloudapp\Templates\HTML')
global all_lines
#app.route("/")
def frontpage():
return render_template('frontpage.html')
#app.route("/temperature/")
def temperature():
f = open('C:/Email/file.txt', 'r')
cpt = 0 # Line amount value
all_lines = [] # List that has every Nth value
for line in f:
cpt += 1 # Goes through every line and adds 1 to the counter
# How often values are plotted (every Nth value)
if cpt%100 == 0:
all_lines.append(line) # When 30th line is counted, add that line to all_lines[] list
if cpt == 500: # How many values are plotted (counts from first)
break
dates = [str(line.split(';')[0]) for line in all_lines]
date = [datetime.strptime(x,'%Y.%m.%d_%H:%M') for x in dates]
y = [float(line.split(';')[1]) for line in all_lines]
z = [float(line.split()[2]) for line in all_lines]
fig, ax = plt.subplots()
ax.plot_date(date, y, 'r-')
f.close()
return mpld3.fig_to_html(fig)
if __name__ == "__main__":
app.run()
frontpage.html (HTML template in folder ..\templates\html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Envic Oy Cloud</title>
</head>
<body>
<div class="headers">
<h1>Web-Application</h1>
<h2> Version 0.0.1 </h2>
</div>
<div class="buttons">
<h3 class="buttonheader">Logger 1</h3>
<a class="templink" href="http://127.0.0.1:5000/temperature/" target="_blank"> Check temperature </a>
</body>
</html>
I use Bash on windows to run the code with
export FLASK_APP=myCloud.py
flask run
EDIT 2
I tried to solve the issue for a long time, but couldn't find a solution. It has something to do with Flask/mpld3 compatibility for me. I made the very same web app, but this time using a simple pyramid WSGI. I can now refresh the plot as many times and redirect myself into any view without the server hanging. I'll still leave the post unsolved, because I would still like to use Flask. I will continue my research too. Here is the pyramid version that works for me:
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
import numpy as np
import matplotlib.pyplot as plt, mpld3
from datetime import datetime
def hello_world(request):
return Response('<h1>Testing the Pyramid version!</h1>Check temperature')
def second_view(request):
with open('C:/Email/file.txt') as f:
cpt = 0 # Line amount value
all_lines = [] # List that has every Nth value
for line in f:
cpt += 1 # Goes through every line and adds 1 to the counter
# How often values are plotted (every Nth value)
if cpt%100 == 0:
all_lines.append(line) # When 30th line is counted, add that line to all_lines[] list
if cpt == 500: # How many values are plotted (counts from first)
break
dates = [str(line.split(';')[0]) for line in all_lines]
date = [datetime.strptime(x,'%Y.%m.%d_%H:%M') for x in dates]
y = [float(line.split(';')[1]) for line in all_lines]
z = [float(line.split()[2]) for line in all_lines]
plt.figure(figsize=(10,5))
plt.title('Humidity', fontsize=15)
plt.ylabel('Humidity RH', fontsize=15)
fig = plt.figure()
plot = plt.plot_date(date, z, 'b-')
myfig = mpld3.fig_to_html(fig, template_type='simple')
return Response(myfig)
if __name__ == '__main__':
config = Configurator()
config.add_route('hello_world', '/hello_world')
config.add_route('second_view', '/second_view')
config.add_view(hello_world, route_name='hello_world')
config.add_view(second_view, route_name='second_view')
app = config.make_wsgi_app()
server = make_server('', 8888, app)
server.serve_forever()
Here is what I attempted. I didn't get any issues with the server hanging after refreshing or opening a new tab of the link. However, after I shutdown the server (Ctrl + C), the console threw some exceptions that suggests that mpld3 or matplotlib had opened some new threads. Specifically, the exception is RuntimeError: main thread is not in main loop.
I did some googling and came across this link. The guy suggested using fig_to_dict with json. I tried his solution and I was still getting the exception.
Now, I'm going to write down both approaches and let you decide which to use. I don't know if either of them will work for you. For my setup, the app runs fine despite the exceptions after closing the server.
I'm also going to use your example that didn't read the txt file. I have debug set to True so I can make sure the GET requests are being processed when I refresh the chart or open a new instance of the link.
Approach 1 (fig_to_html)
app.py
from flask import Flask
from flask import render_template
import matplotlib.pyplot as plt
import mpld3
app = Flask(__name__, template_folder='/path/to/templates')
#app.route("/")
#app.route("/index")
def index():
return render_template('frontpage.html')
#app.route('/temperature')
def temperature():
date = ([1, 2, 3, 4])
y = ([1, 2, 3, 4])
fig = plt.figure(figsize=(10, 5))
plt.title('Temperature', fontsize=15)
plt.ylabel('Temperature' + u'\u2103', fontsize=15)
plt.plot(date, y, 'b-')
plt.ylim([0, 40])
myfig = mpld3.fig_to_html(fig, template_type='simple')
plt.clf() # clear figure
plt.cla() # clear axes
plt.close('all') # close all figures
# Print as HTML
return myfig
if __name__ == "__main__":
app.run(debug=True) # run on debug mode
Approach 2 (fig_to_dict)
app.py
from flask import Flask
from flask import render_template
import matplotlib.pyplot as plt
import mpld3
import json
app = Flask(__name__, template_folder='/path/to/templates')
#app.route("/")
#app.route("/index")
def index():
return render_template('frontpage.html')
#app.route('/temperature')
def temperature():
date = ([1, 2, 3, 4])
y = ([1, 2, 3, 4])
fig = plt.figure(figsize=(10, 5))
plt.title('Temperature', fontsize=15)
plt.ylabel('Temperature' + u'\u2103', fontsize=15)
plt.plot(date, y, 'b-')
plt.ylim([0, 40])
single_chart = dict()
single_chart['id'] = "temp_figure"
single_chart['json'] = json.dumps(mpld3.fig_to_dict(fig))
plt.clf() # clear figure
plt.cla() # clear axes
plt.close('all') # close figure
# Print as HTML
return render_template('temperature.html', single_chart=single_chart)
if __name__ == "__main__":
app.run(debug=True) # run on debug mode
And here are the template files. I noticed you used a static link in your frontpage.html so I replaced that with a placeholder that allows Flask to auto-populate the URL. You also had a div tag that you didn't close.
frontpage.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Envic Oy Cloud</title>
</head>
<body>
<div class="headers">
<h1>Web-Application</h1>
<h2> Version 0.0.1 </h2>
</div>
<div class="buttons"></div>
<h3 class="buttonheader">Logger 1</h3>
<a class="templink" href="{{ url_for('temperature') }}" target="_blank"> Check temperature </a>
</body>
</html>
temperature.html (for 2nd approach only)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Sample Page</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="http://mpld3.github.io/js/mpld3.v0.2.js"></script>
</head>
<body>
<div id="{{single_chart.id}}">
</div>
<script type="text/javascript">
var figureId = "{{single_chart.id}}";
var json01 = {{single_chart.json|safe}};
mpld3.draw_figure(figureId, json01);
</script>
</body>
</html>
BTW, I'm using Python 3.5.4, Flask==0.12.2, matplotlib==2.1.2, and mpld3==0.3. I tested using Chrome Version 67.0.3396.87.

Return a response with an download option on the same HTML page using flask

I have a basic flask app where data frames are formed from two CSVs and some transformations happen and on the HTML page , a final result dataframe can be seen in a tabular format. It works fine till here.
Apart from that, I also want the user to have an option to download the same table as a CSV.
Below is my flask code:
from flask import *
import pandas as pd
app = Flask(__name__)
#app.route("/tables")
def show_tables():
df1 = pd.read_csv('daily.csv')
df2 = pd.read_csv('companies.csv')
df1['date']= pd.to_datetime(df1['date'], format='%m/%d/%y')
df3 = pd.merge(df1,df2,how='left',on='id')
dates = pd.DataFrame({"date": pd.date_range("2017-01-01", "2017-01-10")})
df4 = (df3.groupby(['id', 'name'])['date', 'value']
.apply(lambda g: g.merge(dates, how="outer"))
.fillna(0)
.reset_index(level=[0,1])
.reset_index(drop=True))
df4 = df4.sort_values(by=['id','date'])
df4.value = df4.value.astype(int)
df4['difference'] = df4.groupby('id')['value'].diff()
return render_template('view.html',tables=[df4.to_html(classes='Company_data')],
titles = [ 'Company_data'],filename=df4.to_csv())
#app.route('/tables_download/<filename>')
def tables_download(filename):
return response(filename) //--right way to pass the csv file?
if __name__ == "__main__":
app.run()
Below is my HTML code:
<!doctype html>
<title>Simple tables</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class=page>
<h1>Company data</h1>
{% for table in tables %}
<h2>{{titles[loop.index]}}</h2>
{{ table|safe }}
{% endfor %}
</div>
Download
On my HTML page, I don't even see the Download option.
Struggling to figure out what's wrong so looking for help
As documented in the Flask API, I believe that send_file or send_from_directory would be the way to implement this.
#app.route('/uploads/<path:filename>')
def download_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename, as_attachment=True)
These are both documented on http://flask.pocoo.org/docs/0.12/api/
send_from_directory is more secure (if used correctly) as it limits the file available to download to just those in a specific directory preventing any 'hackers' from downloading your private information.

Categories