【问题标题】:Best way to create a download link for a file in Flask?在 Flask 中为文件创建下载链接的最佳方法是什么?
【发布时间】:2017-04-09 20:05:13
【问题描述】:

在我的项目中,当用户单击链接时,AJAX 请求会发送创建 CSV 所需的信息。 CSV 需要很长时间才能生成,因此我希望能够在 AJAX 响应中包含生成的 CSV 的下载链接。这可能吗?

我见过的大多数答案都以以下方式返回 CSV:

return Response(
        csv,
        mimetype="text/csv",
        headers={"Content-disposition":
                 "attachment; filename=myplot.csv"})

但是,我认为这与我发送的 AJAX 响应不兼容:

return render_json(200, {'data': params})

理想情况下,我希望能够在 params 字典中发送下载链接。但我也不确定这是否安全。这个问题通常是如何解决的?

【问题讨论】:

  • 如果通过网络发送数据是安全的,那么通过网络发送指向数据的链接也是安全的。至于 AJAX + 文件下载,您要么必须将 CSV 作为数据发送,然后使用 JavaScript 中的File API 创建文件并在客户端下载,要么在新选项卡/窗口中打开 CSV 链接。

标签: python ajax flask


【解决方案1】:

我认为一种解决方案可能是 futures 库 (pip install futures)。第一个端点可以将任务排队,然后将文件名发回,然后另一个端点可以用于检索文件。我还包括了gzip,因为如果您要发送更大的文件,这可能是个好主意。我认为更强大的解决方案使用 Celery 或 Rabbit MQ 或类似的东西。但是,这是一个简单的解决方案,应该可以完成您的要求。

from flask import Flask, jsonify, Response
from uuid import uuid4
from concurrent.futures import ThreadPoolExecutor
import time
import os
import gzip

app = Flask(__name__)

# Global variables used by the thread executor, and the thread executor itself
NUM_THREADS = 5
EXECUTOR = ThreadPoolExecutor(NUM_THREADS)
OUTPUT_DIR = os.path.dirname(os.path.abspath(__file__))

# this is your long running processing function
# takes in your arguments from the /queue-task endpoint
def a_long_running_task(*args):
    time_to_wait, output_file_name = int(args[0][0]), args[0][1]
    output_string = 'sleeping for {0} seconds. File: {1}'.format(time_to_wait, output_file_name)
    print(output_string)
    time.sleep(time_to_wait)
    filename = os.path.join(OUTPUT_DIR, output_file_name)
    # here we are writing to a gzipped file to save space and decrease size of file to be sent on network
    with gzip.open(filename, 'wb') as f:
        f.write(output_string)
    print('finished writing {0} after {1} seconds'.format(output_file_name, time_to_wait))

# This is a route that starts the task and then gives them the file name for reference
@app.route('/queue-task/<wait>')
def queue_task(wait):
    output_file_name = str(uuid4()) + '.csv'
    EXECUTOR.submit(a_long_running_task, [wait, output_file_name])
    return jsonify({'filename': output_file_name})

# this takes the file name and returns if exists, otherwise notifies it is not yet done
@app.route('/getfile/<name>')
def get_output_file(name):
    file_name = os.path.join(OUTPUT_DIR, name)
    if not os.path.isfile(file_name):
        return jsonify({"message": "still processing"})
    # read without gzip.open to keep it compressed
    with open(file_name, 'rb') as f:
        resp = Response(f.read())
    # set headers to tell encoding and to send as an attachment
    resp.headers["Content-Encoding"] = 'gzip'
    resp.headers["Content-Disposition"] = "attachment; filename={0}".format(name)
    resp.headers["Content-type"] = "text/csv"
    return resp


if __name__ == '__main__':
    app.run()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-28
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多