[英]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,但就是这样):
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
,这样除了保存文件之外,您还可以在那里进行一些逻辑,例如检查响应状态
process the file (I did a mockup of your processing through uppercase()
)处理文件(我通过uppercase()
做了你的处理模型)
if the server responds with a 201 ("Created") then you can save the file (in xhr.onload
)如果服务器以 201(“已创建”)响应,那么您可以保存文件(在xhr.onload
中)
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.