skip to Main Content

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


  1. 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.

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search