简体   繁体   English

Flask 流数据后加载新页面

[英]Flask Load New Page After Streaming Data

I have simple Flask App that takes a CSV upload, makes some changes, and streams the results back to the user's download folder as CSV.我有一个简单的 Flask 应用程序,它需要上传 CSV,进行一些更改,然后将结果作为 CSV 流式传输回用户的下载文件夹。

HTML Form 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 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"})

This works great and I see the file in my downloads folder.这很好用,我在我的下载文件夹中看到了该文件。

However, I'd like to display " Download Complete " page after it finishes.但是,我想在完成后显示“下载完成”页面。

How can I do this?我怎样才能做到这一点? Normally I use return redirect("some_url") to change pages.通常我使用return redirect("some_url")来更改页面。

Consider usingsend_file() or send_from_directory() for sending files.考虑使用send_file()send_from_directory()发送文件。

Getting 2 responses from 1 request is not possible, but you can split the problem into chunks with the help of some JS, following this simple scheme (not very UML precise, but that's it):从 1 个请求中获得 2 个响应是不可能的,但是您可以在一些 JS 的帮助下按照这个简单的方案将问题拆分为多个块(不是非常精确的 UML,但就是这样):

在此处输入图像描述

  1. POST to /uploader through a function called from the form by onsubmit , so that besides saving the file you can also have some logic there, like checking the response status通过onsubmit从表单调用的 function POST 到/uploader ,这样除了保存文件之外,您还可以在那里进行一些逻辑,例如检查响应状态

  2. process the file (I did a mockup of your processing through uppercase() )处理文件(我通过uppercase()做了你的处理模型)

  3. if the server responds with a 201 ("Created") then you can save the file (in xhr.onload )如果服务器以 201(“已创建”)响应,那么您可以保存文件(在xhr.onload中)

  4. now you can redirect - in the same tab - to the proper enpoint: /download-complete for 201 or /something-went-wrong for other status codes (in xhr.onreadystatechange ).现在您可以在同一个选项卡中重定向到正确的端点: /download-complete用于201/something-went-wrong用于其他状态代码(在xhr.onreadystatechange中)。 To test the error page, make some syntax error in the processing inside upload_file() , like data_df = pd.read_csv(BytesIO(uploaded_file. aread() ))要测试错误页面,请在upload_file()内部的处理中产生一些语法错误,例如data_df = pd.read_csv(BytesIO(uploaded_file. aread() ))

Note the secret_key required to let Flask set the CSRF token for you.请注意secret_key为您设置 CSRF 令牌所需的 secret_key。 [ 08/02/2022 ] I forgot to add the code to make both the source files handle the CSRF token, now I've added it. [ 08/02/2022 ] 我忘记添加代码以使两个源文件都处理 CSRF 令牌,现在我已经添加了它。

Here it is the code:这是代码:

main.py主文件

from flask import (Flask,
                   request,
                   redirect,
                   render_template,
                   send_file,
                   url_for,
                   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
        return redirect(url_for("something_went_wrong"))
    else:
        return send_file(io_buffer,
                         download_name="result.csv",
                         as_attachment=True), 201


@app.route("/download-complete")
def download_complete():
    return Response("<h1>Download Complete!</h1>", status=200)


@app.route("/something-went-wrong")
def something_went_wrong():
    return Response("<h1>Something went wrong!</h1>", status=500)

The form with the JS handler (thanks to the author of this article):带有 JS 处理程序的表单(感谢本文作者):

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

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

    var formData = new FormData();
    formData.append("input_file", document.getElementById("inputFile").files[0]);

    const xhr = new XMLHttpRequest();
    xhr.open("POST", "/uploader", true);
    xhr.setRequestHeader("X-CSRFToken", csrf_token);
    xhr.send(formData);

    xhr.onreadystatechange = function() {
        console.log(`xhr.readyState: ${xhr.readyState}`);
        console.log(`xhr.status: ${xhr.status}`);
        if (xhr.readyState === XMLHttpRequest.DONE) {
            if (xhr.status === 201) {
                window.open('/download-complete', target="_self", rel="noopener");
            }
            else {
                window.open('something-went-wrong', target="_self", rel="noopener");
            }
        }
    };

    xhr.onload = function(e) {
        if (this.status == 201) {

            // Create a new Blob object using the
            // response data of the onload object:
            var type = xhr.getResponseHeader('Content-Type');
            var blob = new Blob([this.response], {type: type});

            // Create a link element, hide it, direct
            // it towards the blob, and then 'click' it programmatically:
            let a = document.createElement("a");
            a.style = "display: none";
            document.body.appendChild(a);

            // Create a DOMString representing the blob
            // and point the link element towards it:
            let url = window.URL.createObjectURL(blob);
            a.href = url;
            a.download = 'result.csv';

            // Programatically click the link to trigger the download:
            a.click();

            // Release the reference to the file by revoking the Object URL:
            window.URL.revokeObjectURL(url);

        }
        else {
            window.open('something-went-wrong', target="_self", rel="noopener");
        }
    };

    return false;
}
</script>

For completeness, my dummy csv "file.csv" :为了完整起见,我的虚拟 csv "file.csv"

foo
bar酒吧

Here is some changes.这里有一些变化。

set window.open('') in input onclick event.在输入 onclick 事件中设置window.open('')

HTML Form 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>

You need two functions, one to deal with the processing such as uploadFile(), and another in the same app route to return render template.您需要两个函数,一个处理uploadFile()等处理,另一个在同一应用程序路由中返回渲染模板。

When the uploadFile() function is completed: completed = True当uploadFile() function完成时: completed = True

Then, code another function which tests the global variable if completed: to return render template.然后,编写另一个 function 代码,测试全局变量if completed:返回渲染模板。

See: How can I use the same route for multiple functions in Flask请参阅: Flask 中的多个功能如何使用相同的路由

Finally, return a variable to the page with Jinja2 and use Javascript to identify if that variable exists to load your 'download completed' page by Javascript.最后,使用 Jinja2 将变量返回到页面,并使用 Javascript 确定该变量是否存在,以通过 Javascript 加载您的“下载完成”页面。

Python: Python:

    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"})
                                

How to load a new page: https://www.geeksforgeeks.org/how-can-a-page-be-forced-to-load-another-page-in-javascript/如何加载新页面: https://www.geeksforgeeks.org/how-can-a-page-be-forced-to-load-another-page-in-javascript/

Javascript conditionals: https://www.w3docs.com/learn-javascript/conditional-operators-if.html Javascript 条件: https://www.w3docs.com/learn-javascript/conditional-operators-if.html

Using Jinja2 to render a variable: https://jinja.palletsprojects.com/en/3.0.x/templates/使用 Jinja2 渲染变量: https://jinja.palletsprojects.com/en/3.0.x/templates/

Also, you should really wrap your uploadFile() function with try and except to catch upload errors.此外,您应该使用 try 和 except 来包装您的 uploadFile() function 以捕获上传错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用Python和Flask流数据 - Streaming data with Python and Flask 将数据从 JavaScript 文件发送到 Python (Flask)后,返回重定向()不在浏览器中显示新页面 - return redirect() not displaying the new page in the browser, after sending data from JavaScript file to Python (Flask) Frozen-Flask 分页链接不会加载新页面 - Frozen-Flask pagination links doesn't load the new page 单击 Flask 中的提交按钮时加载新页面 - Load new page when clicking Submit button in Flask Flask:通过写入客户端来传输数据? - Flask: Streaming data by writing to client? 如何重新配置​​html和flask代码以一次加载引导表,然后在字典的每个新项之后重新加载数据表? - How to reconfigure html and flask code to load the bootstrap table once and then reload the data table after each new item of the dictionary? Flask - 在 POST 请求后重定向到新页面 - Flask - get redirected to new page after POST-request 重定向后烧瓶没有重定向到新页面(url_for()) - flask not redirecting to new page after redirect(url_for()) 使用 Jinja 和 Flask 在新的 HTML 页面上打印渲染数据 - Printing rendered data on new HTML page using Jinja and Flask 使用 Flask 在新页面上将 HTML 表单数据显示为 JSON - Display HTML form data as JSON on new page using Flask
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM