提问人:sushi 提问时间:7/23/2022 更新时间:11/25/2022 访问量:974
Flask 在流式传输数据后加载新页面
Flask Load New Page After Streaming Data
问:
我有一个简单的 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>
蟒
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")
答:
以下是一些更改。
在输入 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>
评论
请考虑使用 send_file() 或 send_from_directory()
发送文件。
从 1 个请求中获得 2 个响应是不可能的,但您可以借助一些 JS 将问题拆分为块,遵循这个简单的图表(不是很精确的 UML,但仅此而已):
此图引用了代码的先前和更简单的版本,该版本后来在 OP 要求调用 flash()
后进行了更新
POST 通过从表单调用的函数 ,这样除了保存文件之外,您还可以在那里有一些逻辑,例如检查响应状态
/uploader
onsubmit
处理文件(我通过以下方式对您的处理进行了模拟
upper()
)如果服务器响应 (“Created”),那么您可以保存文件并打印 “Download Complete”(我使用了它,因为它只有一个标签,我们可以替换所有以前的 DOM;它不应该用于更改复杂的 HTML)
201
window.document.body.innerHTML
否则,如果服务器使用其他状态代码(如 )进行响应,则 POST 以获取要呈现的新 - 可能闪烁的 - HTML。图中未显示 POST 步骤。
500
/something-went-wrong
要测试错误页面,请在内部处理中犯一些语法错误,例如 aread()upload_file()
data_df = pd.read_csv(BytesIO(uploaded_file.
))
在响应中,我添加了一个 CSP 标头来缓解可能的恶意攻击,因为我们无法足够信任用户。something-went-wrong
代码如下:
main.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"
傅 |
---|
酒吧 |
评论
upload_file()
fetch
您需要两个函数,一个用于处理 uploadFile() 等处理,另一个用于在同一应用路由中返回渲染模板。
当 uploadFile() 函数完成时:completed = True
然后,编写另一个函数来测试全局变量以返回渲染模板。if completed:
最后,使用 Jinja2 将一个变量返回到页面,并使用 Javascript 确定该变量是否存在,以通过 Javascript 加载“下载完成”页面。
蟒:
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 包装您的 uploadFile() 函数以捕获上传错误。
评论