Search code examples
vue.jsflaskaxiosbyteblob

How to send multiple byteIO files from Flask endpoint to Vue using axios and file encoding


TL;DR

Flask:

docx --> bytesIO --encodebytes()--> bytes --decode()--> string --> array containing multiple of those string.

Vue:

array containing those strings --?--> blob


Question

I am trying to return multiple byteIO files from Flask endpoint to my Vue application at once.

Based on this SO question, it may be done by converting the byteIO files to string using bytesencode() and .decode(). Then the string is attached to an array and json serialized.

I don't know how it can be done however to reverse the convestion in Vue back to a necessary blob object (which will be downloaded).

Any suggestions on how to accomplish this?

How I create my bytesIO document files from docx template (my code is heavily inspired by snakecharmerb's answer in this post):

def from_template_multiple(context): # context is a dict containing custom attributes to be rendered in the document

    template = DocxTemplate(template_document.docx')

    # Create in-memory buffer
    target_file = BytesIO()

    # Render template and save to the buffer
    template.render(context)
    template.save(target_file)

    # Reset the buffer's file-pointer to the beginning of the file
    target_file.seek(0)
 
    # return target file instead of send_file directly
    return target_file

My flask route that receives the axios call that receives the context attributes from Vue. The part with the file encoding is from this answer.

@hagis_api_bp.route('/docx/multiple', methods=['POST'])
def generate_word_multiple():
    request_json  = request.get_json()

    # array to store blob derivations
    blob_array = []

    for item in request_json['body']:
        context = json.loads(item)
        target_file = process.from_template_multiple(context)

        # encode as base64
        from base64 import encodebytes
        encoded_file = encodebytes(target_file.getvalue()).decode('ascii')

        # append target file to blob array
        blob_array.append(encoded_file)

    return jsonify({'result': blob_array})

And finally the Vue part. This axios call is used to send the context information (json) and here I want to return the blob_array and then revert the encoding process to receive the "raw" blob files.

    // axios request + file download
    axios({
      url: 'docx/multiple',
      data: {body: body_array},
      method: 'POST',

    }).then((response) => {
        // looping over each element in response.data (each converted bytesIO string)
        response.data['result'].forEach((element) => {
        
        // convert string to blob here ?
        
        // download blob as docx file
        const url = window.URL.createObjectURL(new Blob([converted_element])); // --> using result from missing conversion above
        const link = document.createElement('a');
        link.href = url;
        link.setAttribute('download', 'title.docx');
        document.body.appendChild(link);
        link.click();
      });
    });

Solution

  • I was able to solve the problem. The following axios request does the trick. This question was very helpful: Creating a BLOB from a Base64 string in JavaScript

    // axios request + file download
            axios({
              url: 'docx/multiple',
              data: {body: body_array},
              method: 'POST',
    
            }).then((response) => {
              response.data['result'].forEach((element) => {
                console.log(element)
    
                // decode string to blob --> solution to question
                const byteCharacters = atob(element);
                const byteNumbers = new Array(byteCharacters.length);
                for (let i = 0; i < byteCharacters.length; i++) {
                  byteNumbers[i] = byteCharacters.charCodeAt(i);
                }
                const byteArray = new Uint8Array(byteNumbers);
                console.log(byteArray)
    
                const url = window.URL.createObjectURL(new Blob([byteArray]));
                const link = document.createElement('a');
                link.href = url;
                link.setAttribute('download', 'test.docx');
                document.body.appendChild(link);
                link.click();
              });
    });