I have this flask app route:
@app.route('/generatecleanbudgetfile', methods=['GET', 'POST'])
def clean_budget():
file = request.files.get('data_file')
app.logger.info('Budget Formatting request has started')
try:
if request.method == 'POST':
file = request.files.get('data_file')
file.seek(0)
buffer = budget_cleaner(file)
buffer.seek(0)
app.logger.info('Conversion Complete')
return send_file(
buffer,
as_attachment=True,
attachment_filename=f'stripped_budget_{dt.today().strftime("%m.%d.%Y")}.xlsx',
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
except:
app.logger.error(f"Budget formatting failed on {file}")
return render_template('error_type.html', title='Unable to process uploaded budget')
My html:
{% extends "base.html" %}
{% block head %}
{% endblock %}
{% block content %}
<div id="vo_budget_file_settings">
{# <a href="/generatecleanbudgetfile" >Upload Final CRO Budget File</a> #}
<p id="uploadPara">Please upload the final CRO budget File</p>
<form action="/generatecleanbudgetfile" method=POST enctype=multipart/form-data>
<input type="file" name="data_file" accept=".xls, .xlsx, .xlsm"/>
<input type="submit" value="Begin Format" onclick="loading();"/>
</form>
</div>
<!-- funtion to show css spinner on button click -->
<script type="text/javascript">
function loading(){
$(".loader").show();
}
</script>
I understand that flask cannot both render_template
and send_file
since it can only return a single item.
Question:
How would I go about downloading a file via JavaScript instead so I could use my return
to render a new template?
I'm wanting to replace this piece:
return send_file(
buffer,
as_attachment=True,
attachment_filename=f'stripped_budget_{dt.today().strftime("%m.%d.%Y")}.xlsx',
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
CodePudding user response:
Flask can't do both things(render & send file) in a single request.
But you can have an alternative solution↓
In your generatecleanbudgetfile
request:
1. Generate a file and save it in server
2. Provide a file link in your html
3. just render_template in this request
So, when the user get the response, they can click on download link to retrieve the file generated in the first step.
CodePudding user response:
To download the file with JS, you could encode it and include it in your template as a string literal inside a JS Blob object, then save that Blob as soon as the page renders. Something like this in your template for success, where you pass your encoded file contents into the file_contents
template variable:
<body>
Page content here
<script>
const myBlob = new Blob(["{{file_contents}}"], {type:"your-file's-mime-type"})
// then google "how to save js blob to local file".
// This other post might help: https://stackoverflow.com/questions/25547475/save-to-local-file-from-blob
</script>
</body>
To further illustrate as an example, making a blob that has the plaintext contents "Hello World" would go like new Blob(["Hello World"], {type:"text/plain"})
. Your case might be more complicated if you file type isn't plaintext or has a weird encoding, but this is the general idea.
Another (probably less hack-y) idea would be do the same Blob thing except use JS's native fetch
API to get the file contents from your server to the Blob instead of passing it through the template. This is similar to the other answer that suggested saving the file on the server, except instead of providing an a
tag you just do the download in pure JS.