Flask App | render template and start automatic download afterwards - python

I created a Flask app, which receives an input file, extracts the content, converts it to another format and returns a compressed file with the converted content.
#app.route('/', methods=['GET', "POST"])
#app.route('/home', methods=['GET', "POST"])
def home():
form = UploadFileForm()
file = form.file.data
if form.validate_on_submit():
file.save(os.path.join(os.path.abspath(os.path.dirname(__file__)), app.config['UPLOAD_FOLDER'], secure_filename(
file.filename)))
# 1. unzip files in directory
unzip(file.filename)
# 2. starts conversion process
conversion_status = converter().failed_files
# 3. compress generated DICOM files into export .zip file
zip_dir("static/results/conversion")
# 4. routes to downloading page with corresponding download action
return redirect(url_for('download'))
return render_template('index.html', form=form)
So far everything works. Currently I'm routing in step 4 to another function, which renders a result page, telling, if the conversion was fine. It also prints the export files and by clicking on them, the can be downloaded.
#app.route('/download')
def download():
return render_template('download.html', files=os.listdir('static/results/export'), status=conversion_status)
#app.route('/download/<filename>')
def download_file(filename):
return send_from_directory('static/results/export', filename)
Because I'm running that app on a azure web app url, I want to change the process into:
Rendering the result page
Downloading the content automatically afterwards, without user input
Like this:
return render_template('download.html', files=os.listdir('static/results/export'), status=conversion_status)
return send_from_directory('static/results/export', filename)
But I do not have any idea how to render two things after each other

It's not feasible to render two things consecutively within one endpoint.
However, there is a possibility to download the file automatically using JavaScript.
To find your current result within the list of files, you can pass the name as a parameter to the endpoint for the results.
return redirect(url_for('download', f=filename))
Within the endpoint, you ask for this parameter and pass it on to the template along with the list of files found. You can use a simple comparison to identify the file and use an attribute to mark the anchor for download as current.
#app.route('/download')
def download():
current = request.args.get('f')
files = os.listdir('static/results/export')
return render_template('download.html', **locals())
<ul>
{% for file in files -%}
<li>
<a
class="{{ ('', 'dwl-current')[current and current == file] }}"
href="{{ url_for('download_file', filename=file) }}"
download
>{{file}}</a></li>
{% endfor -%}
</ul>
If you add the anchor to run the download, it is now possible to click it automatically.
<script type="text/javascript">
(() => {
const elem = document.querySelector('a.dwl-current');
elem && elem.click();
})();
</script>

Related

Python Flask / HTML - what is the proper way of displaying an output after HTML form submission?

I am very new to web-development (first project) and have started playing around in Flask. The other day I made a very simple temperature converter which I was running on my local host. The page had a form input to type a value, two radio buttons with Fahrenheit and Celsius to define the system of the value, then a convert button. Here is a screenshot:
Here is my Flask code ("main.py"):
from flask import Flask, render_template
from flask import request, redirect
import temperature, convert, determine_system
app = Flask(__name__)
#app.route('/')
def html():
return render_template('index.html')
#app.route('/convert', methods = ['POST'])
def convert():
temp = request.form['temperature']
system = request.form['system']
new_temp, destination_system = determine_system.determine_system(temp, system)
return render_template('convert.html', temp=temp, system=system, new_temp=new_temp, destination_system=destination_system)
if __name__ == "__main__":
app.run()
As you can see, the first function called "html()" initially renders the "index.html" file and the function "convert()" is executed upon clicking the "Convert" button. There are a few other functions that I have in other .py files in the directory that convert the number to the new system.
Here is the body of my "index.html" code:
<body>
<div id="banner">
<h1>Temperature Converter</h1>
<p class="lead">Use this tool to convert temperature between measurement systems</p>
</div>
<form action="/convert" method="post" target="dummyframe">
<input type="text" name="temperature"></input>
<input type="radio" name="system" value="Fahrenheit">Fahrenheit</input>
<input type="radio" name="system" value="Celsius">Celsius</input>
<br>
<br>
<input type="submit" value="Convert"></input>
</form>
</body>
</html>
To display the converted temperature on the webpage, I currently have another HTML file called "convert.html" in my templates directory that is an exact copy of the "index.html" file, except it includes the following three lines of code in the body after the :
div id="output"></div>
<p class="output-statement">{{ temp }}° {{ system }} is equal to {{ new_temp }}° {{ destination_system }}</p>
</div>
In my Flask file ("main.py), I instruct the "convert()" function to render the "convert.html" template which includes the output statement in the code above:
return render_template('convert.html', temp=temp, system=system, new_temp=new_temp, destination_system=destination_system)
This then results in the following (notice the new web address):
I suspect that my way of outputting the converted temperature by redirecting to a new HTML file and web address (http://127.0.0.1:5000/convert) is not efficient or even the correct way of showing accomplishing this. What is the proper way to output something like this? Is there something I can add to the "index.html" file that would allow me to get rid of the "convert.html" file completely? If so, what would I change the last line of the "convert()" function in my Flask ("main.py") file to?
Thank you in advance and any links with more information on this concept are very appreciated!
Yes there is a more efficient solution where you do not need the convert.html:
This is what you will want in your main route. (note: I suggest renaming your route function to something like "index" or "temp" other than "html")
#app.route('/', methods=["GET","POST"])
def html():
output = ""
if request.method == "POST":
temp = request.form['temperature']
system = request.form['system']
new_temp, destination_system = determine_system.determine_system(temp, system)
output = f"{ temp}° { system } is equal to { new_temp }° { destination_system }"
return render_template('index.html', output=output)
Make sure to import request. using: from flask import request
and in your index.html you will now have:
<div id="output"></div>
<p class="output-statement">{{output}}</p>
</div>
And make sure to change form action to action="#" or action=""

Building an HTML flask web page - input dependent on launch

I am building a website with flask on python. I am new to web development.
I built an HTML page, and now I need it's contents - number of buttons on the page for example - to be possibly different and automatic on each launch of app.py (the flask app running the website). Let's say that the number will be random between 1-10, a number generated in the app.py.
Does this mean that I need to change the HTML on every app.py launch, this by using python and editing the text file "index.html"? Is this bad practice and not a good way of achieving the goal? Are there other better methods to launch an input-dependent HTML page?
Thanks!
Code example:
def change_HTML_page(path,num):
# here read the text file in path, which is an HTML file, page description.
# inside in some place add more rows to describe buttons,
# as many as num.
# Add rows like this one <input type="button" id="i_bnutton" value="i" onclick="change_button_appearence(this)" />
# save text file afer the change
num_of_buttons = randint(0, 10)
page_path = r"docs/pages/index.html"
change_HTML_page(page_path, num_of_buttons);
#app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
Best way is to pass the num_of_buttons inside the html render command and constract all input buttons with a jinja loop inside your html.
Your code should look like below:
FLASK:
#app.route('/')
def index():
num_of_buttons = randint(0, 10)
return render_template('index.html', num_of_buttons=num_of_buttons)
if __name__ == '__main__':
app.run(debug=True)
And inside your HTML:
{% for i in range(0,num_of_buttons) %}
<input type="button" id="{{i}}_bnutton" value="{{i}}" onclick="change_button_appearence(this)" />
{% endfor %}

Why download button on Python website doesn't work

I have Flask website in which I want to add download button which downloads .csv file with scraped data.
In my html file I have this code:
<a href="cms_scrape.csv" ><button>Download!</button></a>
And only output I get is error: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
File is in its proper folder.
My folder structure:
└───Project
│ cms_scrape.csv
│
└───templates
index.html
You will need to specify some sort of route on the backend of your site.
For instance, somewhere in your flask site, you probably have a route #app.route('/') for your index. You will need a similar route for your file. That route will go out onto your file system and return the file itself.
#app.route('/csv_file')
def csv_file():
return flask.send_file('path/to/file/cms_scrape.csv',
attachment_filename='cms_scrape.csv',
as_attachment=True)
You will also need to modify your html to access a route and not the file name directly (unless you create your routes dynamically, of course):
<a href="/csv_file" ><button>Download!</button></a>
Not exactly sure about this but I think the tag has a download attribute you can use. Then you don't need the button.
Usage:
<a href="/path/to/file" download>
Source: https://www.w3schools.com/tags/att_a_download.asp
You can make links to files with the
{{ url_for('static', filename='filename.foo') }}
function inside your template. You have to store the file in a folder named 'static' which should be located in the directory where the main scipt is.
The link in your template should look like this:
<a href=" {{ url_for('static', filename='cms_scrape.csv') }} " download>Download!</a>

Play a downloaded video with flask [duplicate]

This question already has an answer here:
Can't play HTML5 video using Flask
(1 answer)
Closed 7 years ago.
I have a simple flask server. I downloaded, using pafy, a video from a youtube link provided by the user.
#app.route('/')
def download():
return render_template('basic.html')
The basic.html template has a form that submits an action to download:
<form action="download_vid" method="post">
Link: <input type="text" name="download_path"><br>
<input type="submit" value="Submit">
</form>
I have another end point, /download_vid that looks like this.
#app.route('/download_vid', methods=['POST'])
def download_vid():
url = request.form['download_path']
v = pafy.new(url)
s = v.allstreams[len(v.allstreams)-1]
filename = s.download("static/test.mp4")
return redirect(url_for('done'))
The desired link is indeed downloaded as a .mp4 file in my static folder. I can watch it and I can also use it as a source for a tag in an HTML file, if I open it locally.
#app.route('/done')
def done():
return app.send_static_file('test.mp4')
From what I understand, 'send_static_file' serves files from the static directory. However, I get a 404 error when I run the server, even though the video is clearly there.
I have also tried a different version for done():
#app.route('/done')
def done():
return return render_template('vid.html')
Here, vid.html resides in templates and has a hard coded path to static/test.mp4. It is loaded after the download is complete. I do not have a 404 error in this case, but the tag don't do anything, it's just gray. If I open vid.html locally (double click on it), it works, it shows the video.
Can you please help me understand what is going on?
What I want to achieve is this:
Take an input from the user [ Done ]
Use that input to download a video [ Done ]
Serve that video back to the user [ ??? ]
I think you have something going on with file paths or file permissions.
Is the video being downloaded into static directory?
Is the static directory in the same directory, along with your main.py file?
Does your flask app have permissions to read the file?
I think the reason your file did not load in html template is because you referenced it as static/test.mp4 from an url - /done which translates the video path to be /done/static/test.mp4.
Instead of trying to push the file using Flask, you can redirect to the actual media file.
#app.route('/done')
def done():
return redirect('/static/test.mp4')

Dynamically update image using Python Flask AJAX

I have 1 very simple web application I am building right now but am very new to flask and jinja (and web development as a whole actually).
I have a watch folder, which will be getting an image sent to it via ftp on a pulse for ever. This wtch folder will only ever have one image in. Every 1 minute, the old image is replaced by a new image, with a new timestamp.
I would like to dynamically update the page, (and displayed timestamp) on a pulse as well, without having to reload any banners or static images that I will add later. I only want to update the following two lines out of the "Channels.Jinja" sample to follow.
<br>{{screenshot_datetime}}<br/>
<img src={{screenshot_location}} width="100%"/>
Channels.Jinja
<!DOCTYPE HTML>
<html>
<head>
<title>Training</title>
</head>
<body bgcolor=white>
<div id=main>
<br>Date and Time of Screenshot <br/>
<br>{{screenshot_datetime}}<br/>
<img src={{screenshot_location}} width="100%"/>
</div>
<div id='test'>
<p>
<script>
var myVar=setInterval(function(){get_image()},1000);
function get_image() {
$.ajax({
type: 'GET',
cache: false,
url: 'get_data',
success: function({{data}}) {
$('img').attr('src', data);
}
});
}
</script>
</p>
</div>
</body>
</html>
Channels.py
def render_channel_route(cr):
static_folder = os.path.join('static',cr)
file_list = os.listdir(static_folder)
channel_files = [f for f in file_list if f.startswith(cr)]
if not channel_files :
logger.error('Could not find image file for Channel. File should start with {0}'.format(cr))
abort(404)
img = os.path.join(static_folder,file_list[0])
ts = get_time_from_filename(file_list[0],cr)
return render_template('Channels.jinja',screenshot_datetime=time.strftime('%c',ts),screenshot_location=img)
#app.route('/channel01-10')
def first_tab():
return render_channel_route('channel01-10')
#app.route('/get_data', methods=['GET'])
def get_data():
return render_template('Channels.jinja',
screenshot_datetime=time.strftime('%c',ts),screenshot_location=img)
Im at a loss, Ive been bumbling around for a while now. Any and all advice is welcome! I am seeing a 304 response upon refresh, but not even the timer i am trying to put on it is working. Pardon sloppy code, highly volatile code is getting changed often -_-
I don't know it there is a "special" way to deal with Ajax using some Flask extension, but in the "normal" Ajax flow first you need to use url_for to put the correct url in your Ajax call and return the data formatted in some way (in my example in JSON) and not to render the template again:
$.ajax({
type: 'GET',
cache: false,
url: "{{ url_for('get_data') }}",
success: function(resp){
$('img').attr('src', resp.url);
$('#sst').html(resp.time);
}
});
So, in your get_data function in your controller you have to get the time and the path again for your image an then return some like this (to fit in my example before):
from flask import json
#app.route('/get_data', methods=['GET'])
def get_data():
#get time and path
time=...
path=...
return json.dumps({time:time,url:path}), 200, {'Content-Type':'application/json'}
Look that I use $('#sst') so you have to put in your HTML:
<br><span id='sst'>{{screenshot_datetime}}</span><br/>

Categories