html 流式处理数据后Flask加载新页面

blmhpbnm  于 2022-11-20  发布在  其他
关注(0)|答案(3)|浏览(192)

我有一个简单的Flask应用程序,它可以进行CSV上传,进行一些更改,并将结果作为CSV流返回到用户的下载文件夹。
HTML表单

<form action = {{uploader_page}} method = "POST" enctype = "multipart/form-data">
    <label>CSV file</label><br>
    <input type = "file" name = "input_file" required></input><br><br>
    <!-- some other inputs -->
    <div id = "submit_btn_container">
        <input id="submit_btn" onclick = "this.form.submit(); this.disabled = true; this.value = 'Processing';" type = "submit"></input>
    </div>
</form>

Python

from flask import Flask, request, Response, redirect, flash, render_template
from io import BytesIO
import pandas as pd

@app.route('/uploader', methods = ['POST'])
def uploadFile():
    uploaded_file = request.files['input_file']
    data_df = pd.read_csv(BytesIO(uploaded_file.read()))
    # do stuff
    
    # stream the pandas df as a csv to the user download folder
    return Response(data_df.to_csv(index = False),
                            mimetype = "text/csv",
                            headers = {"Content-Disposition": "attachment; filename=result.csv"})

这工作得很好,我在我的下载文件夹中看到了文件。
但是,我希望在下载完成后显示“* 下载完成 *”页面。
我该怎么做?通常我使用return redirect("some_url")来更改页面。

8xiog9wr

8xiog9wr1#

请考虑使用send_file()send_from_directory()发送文件。
从一个请求中得到两个响应是不可能的,但是您可以在一些JS的帮助下将问题拆分成块,遵循下面这个简单的图表(不是非常精确的UML,但就是这样):

  • 此图引用了该代码的一个更简单的早期版本,在提问者要求调用flash()之后,该版本被更新 *

1.通过onsubmit从表单中调用的函数POST到/uploader,这样除了保存文件之外,还可以在那里使用一些逻辑,比如检查响应状态
1.处理文件(我通过upper()模拟了您的处理过程)
1.如果服务器响应201(“Created”),则可以保存文件并输出“Download Complete”(我使用window.document.body.innerHTML是因为它只有一个标记,我们可以替换所有以前的DOM;不应使用它来更改复杂的HTML)
1.否则,如果服务器以其他状态代码(如500)响应,则POST到/something-went-wrong以获得要呈现的新的(可能是闪存的)HTML。
要测试错误页面,请在upload_file()内部的处理中进行一些语法错误,例如data_df = pd.read_csv(BytesIO(uploaded_file.aread()))
something-went-wrong响应中,我添加了一个CSP头来减轻可能的恶意攻击,因为我们不能足够信任用户。
代码如下:

主文件.py

from flask import (Flask,
                   request,
                   redirect,
                   render_template,
                   send_file,
                   url_for,
                   Response, jsonify, flash, make_response)
from flask_wtf.csrf import CSRFProtect

from io import BytesIO
import pandas as pd

app = Flask(__name__)
app.secret_key = "your_secret"

csrf = CSRFProtect(app)

@app.route('/')
def index():
    return render_template("index.html")

@app.route("/uploader", methods=['POST'])
def upload_file():
    try:
        uploaded_file = request.files['input_file']
        data_df = pd.read_csv(BytesIO(uploaded_file.read()))

        # do stuff
        data_df["foo"] = data_df["foo"].str.upper()

        # Stream buffer:
        io_buffer = BytesIO()
        data_df.to_csv(io_buffer)
        io_buffer.seek(0)

    except Exception as ex:
        print(ex)  # and log if needed
        # Here I return the exception string just as an example! Not good in real usage.
        return jsonify({"message": str(ex)}), 500
    else:
        return send_file(io_buffer,
                         download_name="result.csv",
                         as_attachment=True), 201

@app.route("/something-went-wrong", methods=["POST"])
def something_went_wrong():
    flash(request.get_json()["message"])
    response = make_response(render_template("something-went-wrong.html"), 200)
    response.headers['Content-Security-Policy'] = "default-src 'self'"
    return response

具有JS处理程序的表单

<form id="myForm" enctype="multipart/form-data" onsubmit="return submitHandler()">
    <input type="hidden" name="csrfToken" value="{{ csrf_token() }}"/>
    <label>CSV file</label><br>
    <input type="file" id="inputFile" name="input_file" required/><br><br>
    <!-- some other inputs -->
    <div id="submitBtnContainer">
        <input id="submitBtn" type="submit"/>
    </div>
</form>

<script>
    function submitHandler() {
        const csrf_token = "{{ csrf_token() }}";

        let formData = new FormData();
        const file = document.getElementById('inputFile').files[0];
        formData.append("input_file", file);

        fetch("/uploader", {
            method: "POST",
            body: formData,
            headers: {
                "X-CSRFToken": csrf_token,
            },
        })
        .then(response => {
            if (response.status != 201) {
                response.json().then(data => {
                    fetch("/something-went-wrong", {
                        method: "POST",
                        body: JSON.stringify({"message": data["message"]}),
                        headers: {
                            "Content-Type": "application/json",
                            "X-CSRFToken": csrf_token,
                        },
                    })
                    .then(response => response.text())
                    .then(text => {
                        window.document.body.innerHTML = text;
                    })
                });
            }
            else {
                return response.blob().then(blob => {
                    const file = new Blob([blob], { type: 'text/csv' });
                    const fileURL = URL.createObjectURL(file);
                    let fileLink = document.createElement('a');
                    fileLink.href = fileURL;
                    fileLink.download = "result.csv";
                    fileLink.click();
                    window.document.body.innerHTML = "<h1>Download Complete</h1>";
                });
            }
        })
        return false;
    }
</script>

为了完整起见,我的虚拟csv "file.csv"
| 浮|
| - -|
| 棒|

icnyk63a

icnyk63a2#

这里是一些变化。
在输入onclick事件中设置window.open('')
HTML表单

<form action ="/uploader" method = "POST" enctype = "multipart/form-data">
        <label>CSV file</label><br>
        <input type = "file" name = "input_file" required></input><br><br>
        <!-- some other inputs -->
        <div id = "submit_btn_container">
            <input id="submit_btn" onclick = "this.form.submit(); this.disabled = true; this.value = 'Processing'; window.open('your_url');" type = "submit"></input>
        </div>
</form>
sy5wg1nm

sy5wg1nm3#

您需要两个函数,一个用于处理诸如uploadFile()之类的处理,另一个位于同一个应用程序路由中,用于返回渲染模板。
uploadFile()函数完成后:completed = True
然后,编写另一个函数,该函数测试全局变量if completed:以返回渲染模板。
请参阅:How can I use the same route for multiple functions in Flask
最后,使用Jinja2返回一个变量到页面,并使用Javascript确定该变量是否存在,以便通过Javascript加载“download completed”页面。
巨蟒:

from flask import Flask, request, Response, redirect, flash, render_template
    from io import BytesIO
    import pandas as pd

    completed = False
    
    @app.route('/uploader', methods = ['POST'])
    def uploadFile():
        uploaded_file = request.files['input_file']
        data_df = pd.read_csv(BytesIO(uploaded_file.read()))
        # do stuff
        # When stuff is done
        global completed
        completed = True
        # stream the pandas df as a csv to the user download folder
        return Response(data_df.to_csv(index = False),
                                mimetype = "text/csv",
                                headers = {"Content-Disposition": "attachment; filename=result.csv"})

如何加载新页面:https://www.geeksforgeeks.org/how-can-a-page-be-forced-to-load-another-page-in-javascript/
Javascript条件:https://www.w3docs.com/learn-javascript/conditional-operators-if.html
使用Jinja2呈现变量:https://jinja.palletsprojects.com/en/3.0.x/templates/
此外,您还应该使用try和except来 Package uploadFile()函数,以捕获上载错误。

相关问题