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" class="btn btn-primary">Upload Final CRO Budget File</a> #}
<p id="uploadPara">Please upload the final CRO budget File</p>
<form class="" 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'
)
2
Answers
Flask can’t do both things(render & send file) in a single request.
But you can have an alternative solution↓
In your
generatecleanbudgetfile
request:So, when the user get the response, they can click on download link to retrieve the file generated in the first step.
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: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 ana
tag you just do the download in pure JS.