我想制作一个字段数量不固定的表单,以便用户可以根据需要添加任意数量的新字段。
如何在不提前知道 n 个字段的情况下获取 Python 端的所有表单数据(通过 WTForms)?
forms.py
class Form(FlaskForm):
field = StringField('field')
submit = SubmitField('submit')
HTML
<!-- I want my form to be viewed in Bootstrap Modal. -->
<div class="modal" tabindex="-1">
<div class="modal-body">
<form name="create-form" method="POST">
<div class="input-group">
{{ form.hidden_tag() }}
{{ form.field(class="form-control") }}
<!-- The place to insert additional fields -->
{{ form.submit(class="btn btn-outline-secondary) }}
</div>
</form>
<button id="add" type="button" class="btn btn-outline-secondary">Add</button>
</div>
</div>
<template id="new-field">
{{ form.field(class="form-control") }}
</template>
JS
modal.querySelector('#add').addEventListener('click', (ev) => {
modal.querySelector(/*Place to insett*/).before(/*new field from <template>*/);
});
modal.querySelector('form').addEventListener('submit', async (ev) => {
ev.preventDefault();
let form = modal.querySelector('form');
let resp = await fetch('/ajax', {method: 'POST', body: new FormData(form)});
});
routes.py
@bp.route('/ajax', methods=['GET', 'POST'])
def ajax():
# request.form contains all values
form = Form()
if form.validate():
# form.data has the only one value (from first field)
return {}
return {}
是的,似乎我需要为每个输入提供唯一的 wtf 字段。好吧,回到绘图板。
forms.py
class Form(FlaskForm):
field = StringField('field')
submit = SubmitField('submit')
def add_filed(self, i):
field = StringField(f'field{i}')
setattr(self.__class__, f'field{i}', field)
return self.__class__()
HTML
<template id="new-field">
{{ form.add_field(7)|attr("field7")(class="form-control") }}
</template>
什么也没有。
TypeError: 'UnboundField' object is not callable
。
还有一次尝试:
modal.querySelector('#add').addEventListener('click', async (ev) => {
// At this point I'm sending ajax-request to call Form.add_field(7)
// on the Python side, bu as result I got {'field': 'value', 'field7': None}.
modal.querySelector(/*Place to insett*/).before(/*new field from <template>*/);
});
我错过了什么?
我读了很多有关此的主题,但我不明白。
以下解决方案使用具有至少一个字段的
FieldList
。在列表中,ID 和名称属性由连续的数字补充。data
的 FieldList
属性以及迭代其包含的字段来查询输入。
from flask import (
Flask,
render_template,
)
from flask_wtf import FlaskForm
from wtforms import FieldList, StringField, SubmitField
app = Flask(__name__)
app.secret_key = 'your secret here'
class MyForm(FlaskForm):
fields = FieldList(StringField('Field'), min_entries=1)
submit = SubmitField('Submit')
@app.route('/', methods=['GET', 'POST'])
def index():
form = MyForm()
if form.validate_on_submit():
print(form.fields.data)
return render_template('index.html', **locals())
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Index</title>
</head>
<body>
<form method="post">
{{ form.hidden_tag() }}
{% for field in form.fields -%}
<div>
{{ field.label() }}
{{ field() }}
</div>
{% endfor -%}
{{ form.submit() }}
</form>
<button id="add" type="button" class="btn btn-outline-secondary">Add</button>
<script>
(function() {
document.getElementById('add').addEventListener('click', () => {
const fieldGroup = document.querySelector('form div:last-of-type')
const newFieldGroup = fieldGroup.cloneNode(true);
newFieldGroup.querySelectorAll('input[name^="fields-"]').forEach(field => {
const nameAttr = field.name,
newNameAttr = nameAttr.replace(
/^fields-(\d+)$/,
(match, p1) => `fields-${parseInt(p1)+1}`
);
field.id = field.name = newNameAttr;
field.value = '';
const label = newFieldGroup.querySelector(`label[for="${nameAttr}"]`);
label.setAttribute('for', newNameAttr);
});
fieldGroup.after(newFieldGroup)
});
})();
</script>
</body>
</html>