所以,有一个文件上传表单。
forms.py
from flask_wtf import FlaskForm
class Form(FlaskForm):
file = FileField('File',
validators=[FileRequired(msg),
FileAllowed(ext, msg),
FileSize(max_, min_, msg)])
upload = SubmitField('Upload')
upload.html
<form method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
{{ form.file(class="form-control", id="file") }}
<div id="error" data-error="{{ form.file.errors[0] }}"></div> <!-- * -->
{{ form.upload(class="btn btn-outline-secondary", id="upload") }}
</form>
Bootstrap toast
元素中显示错误消息,该元素需要在 JS 中初始化。script.js
let fu = document.querySelector('#upload');
fu.addEventListener('click', (ev) => {
let el = document.querySelector('#error');
if (el.dataset.error) {
// Show error message and do nothing else
let toast = document.querySelector('.toast');
document.querySelector('.toast-body').innerText = el.dataset.error;
bootstrap.Toast.getOrCreateInstance(toast).show(); // initialize
}
// Let Flask do its job
});
显然,当我按下上传按钮时,JS 函数会立即触发。仍然没有有关发生错误的信息(如果有的话),因为服务器端的验证尚未发生。
我还尝试解决 websockets 的问题:
Python
@app.route('/upload', methods=['GET', 'POST'])
def upload():
form = Form()
if request.method == 'POST':
if not form.validate():
socketio.emit('err', {'error': form.file.errors[0]})
# ...
JS
const ws = io();
ws.on('err', (ev) => (
// let error = ev.error;
// Yhe same logic
));
但在客户端收到
Invalid frame header
错误。
终于得到了我想要的。有大量代码和重复的 AJAX 请求调用。 我确信还有更优雅的解决方案。也许我错过了什么。 谢谢。
一种可能性是使用 Jinja 创建 toast 并在直接执行的脚本中显示它们。在这种情况下,您不需要 Ajax 或 websockets。整个页面将重新加载并显示 toast。
class UploadForm(FlaskForm):
file = FileField('File',
validators=[
FileRequired(msg),
FileAllowed(ext, msg),
FileSize(max_, min_, msg)
]
)
submit = SubmitField('Upload')
@app.route('/upload', methods=['GET', 'POST'])
def upload():
form = UploadForm()
if form.validate_on_submit():
# ...
return render_template('upload.html', **locals())
{% if form.errors -%}
<div class="toast-container position-fixed bottom-0 end-0 p-3">
{% for k,v in form.errors.items() -%}
<div class="toast align-items-center" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
{{ v[0] }}
</div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
{% endfor -%}
</div>
{% endif -%}
(function() {
const toastElList = document.querySelectorAll('.toast')
toastElList.forEach(toastEl => {
bootstrap.Toast.getOrCreateInstance(toastEl).show();
});
})();
如果你确实想使用Ajax,你也可以通过脚本创建toast,然后显示它们,如下例所示。页面并未完全重新加载,只是动态创建个别部分。
@app.route('/')
def index():
form = UploadForm()
return render_template('index.html', **locals())
@app.post('/upload')
def upload():
form = UploadForm()
if form.validate():
# ...
return jsonify(errors=[])
return jsonify(errors=form.errors)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Index</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous">
</head>
<body>
<main class="container my-4">
<form name="upload-form" method="POST" enctype="multipart/form-data">
<div class="mb-3">
{{ form.hidden_tag() }}
{{ form.file(class_='form-control') }}
</div>
<div class="mb-3 d-grid gap-2">
{{ form.submit(class_='btn btn-outline-secondary') }}
</div>
</form>
</main>
<div class="toast-container position-fixed bottom-0 end-0 p-3"></div>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<script>
(function() {
const formEl = document.querySelector('form[name="upload-form"]');
formEl.addEventListener('submit', function(event) {
event.preventDefault();
fetch('/upload', {
method: 'POST',
body: new FormData(this)
}).then(resp => resp.json())
.then(data => {
if (data && data.errors && Object.keys(data.errors).length) {
const toastsEl = document.querySelector('.toast-container');
toastsEl.innerHTML = Object.keys(data.errors).map(k => {
return `
<div class="toast align-items-center" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
${ data.errors[k][0] }
</div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
`;
}).join('');
toastsEl.querySelectorAll('.toast').forEach(toastEl => {
bootstrap.Toast.getOrCreateInstance(toastEl).show();
});
} else {
this.reset();
}
})
});
})();
</script>
</body>
</html>
我认为没有必要使用websockets。