我正在使用 Python 和 Flask 构建一个应用程序。我正在使用 WTForms 创建一个表单,该表单允许用户编辑客户联系方式,包括动态电话号码。提交表单后,我想使用表单的
Customer
函数将表单中的数据保存回 populate_obj
对象中。
表格代码如下:
class PhoneNumberFormPart(FlaskForm):
class Meta:
csrf = False # Disable CSRF protection, it will confilct with protection on the parent form
number = StringField("Phone Number", widget=Input('tel'))
label = SelectField('Label', choices=(("Cell", "Cell"), ("Home", "Home"), ("Work", "Work")))
preferred = BooleanField('Preferred', default=False)
class CustomerEditForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()])
name2 = StringField('Contact Person Name')
isBusiness = BooleanField('This is a business client', default=False)
phones = FieldList(FormField(PhoneNumberFormPart), min_entries=1)
address = StringField("Address")
city = StringField("City")
state = StringField("State")
zip = StringField("Zip Code")
email = StringField("Email Address", widget=Input('email'), validators=[Email()])
submit = SubmitField('Save Customer Details')
我有以下 JavaScript 在客户端添加其他电话号码字段:
/// Called in forms using a FieldList to duplicate the last FieldList-item
function addFieldListItem(fieldList){
let lastField = fieldList.lastElementChild.previousElementSibling;
// Last child is the "add phone" button added in the template, so the last field is the 2nd-to-last item
let newField = lastField.cloneNode(true);
let newInputs = newField.getElementsByTagName('input');
Array.prototype.forEach.call(newInputs, function(input){
// Increment the number that flask assigns to each field name
input.name = input.name.replace(/(\d+)/, function(n){return ++n});
input.id = input.id.replace(/(\d+)/, function(n){return ++n});
// Clear the input values
input.value = null;
input.checked = false;
});
let newSelects = newField.getElementsByTagName('select');
Array.prototype.forEach.call(newSelects, function(select){
// Increment the number that flask assigns to each field name
select.name = select.name.replace(/(\d+)/, function(n){return ++n});
select.id = select.id.replace(/(\d+)/, function(n){return ++n});
});
let newLabels = newField.getElementsByTagName('label');
Array.prototype.forEach.call(newLabels, function(label){
// Increment the number that flask assigns to each field name
label.htmlFor = label.htmlFor.replace(/(\d+)/, function(n){return ++n});
});
fieldList.insertBefore(newField, fieldList.lastElementChild);
}
只要我不在客户端添加额外的电话号码,一切似乎都会按我的预期进行。但是,如果我在客户端添加另一个号码,当我调用
populate_obj
时,我会得到以下异常:
Traceback (most recent call last):
File "c:\python\lib\site-packages\flask\app.py", line 2463, in __call__
return self.wsgi_app(environ, start_response)
File "c:\python\lib\site-packages\flask\app.py", line 2449, in wsgi_app
response = self.handle_exception(e)
File "c:\python\lib\site-packages\flask\app.py", line 1866, in handle_exception
reraise(exc_type, exc_value, tb)
File "c:\python\lib\site-packages\flask\_compat.py", line 39, in reraise
raise value
File "c:\python\lib\site-packages\flask\app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "c:\python\lib\site-packages\flask\app.py", line 1951, in full_dispatch_request
rv = self.handle_user_exception(e)
File "c:\python\lib\site-packages\flask\app.py", line 1820, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "c:\python\lib\site-packages\flask\_compat.py", line 39, in reraise
raise value
File "c:\python\lib\site-packages\flask\app.py", line 1949, in full_dispatch_request
rv = self.dispatch_request()
File "c:\python\lib\site-packages\flask\app.py", line 1935, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "c:\python\lib\site-packages\flask_login\utils.py", line 261, in decorated_view
return func(*args, **kwargs)
File "C:\Users\Techris Design\Project Spiderman\spiderman\spiderman\views\customers.py", line 40, in customer_landing
form.populate_obj(customer)
File "c:\python\lib\site-packages\wtforms\form.py", line 96, in populate_obj
field.populate_obj(obj, name)
File "c:\python\lib\site-packages\wtforms\fields\core.py", line 962, in populate_obj
field.populate_obj(fake_obj, 'data')
File "c:\python\lib\site-packages\wtforms\fields\core.py", line 829, in populate_obj
raise TypeError('populate_obj: cannot find a value to populate from the provided obj or input data/defaults')
TypeError: populate_obj: cannot find a value to populate from the provided obj or input data/defaults
我很确定这是因为相应
phones
对象中的 PhoneNumber
属性(这是 Customer
对象的列表)没有足够的项目来将所有表单数据包含到列表中。
我查看了 WTForms 文档,看看是否有一种方法可以将工厂函数或类分配给
FormField
,以便在我调用 PhoneNumber
时可以创建额外的 customer.phones
对象以添加到 populate_obj
如果表单数据中的项目多于目标对象中的项目。但是,据我从文档中可以看出,没有这样的选项。
有人知道最好的方法吗?
好吧,我花了一些时间研究了 Github 上的 WTForms 源代码并弄清楚了。将类或工厂函数传递给
default
实例的 FormField
参数,如下所示:
phones = FieldList(FormField(PhoneNumberFormPart, default=PhoneNumber), min_entries=1)
其实很简单,虽然调用参数
default
让我有点难找。从来没想过要去看那里......
这里接受的答案在没有更多上下文的情况下有点令人困惑,所以我想添加一些信息以便清楚起见:
在代码片段
FieldList(FormField(PhoneNumberFormPart, default=PhoneNumber)
中,类PhoneNumber
可能指的是PhoneNumber条目的数据模型,OP未将其包含在其示例代码中。
在不知道其实际数据模型是什么样子的情况下,假设(对于本示例)PhoneNumber 如下所示可能会有所帮助:
class PhoneNumber():
def __init__(self) -> None:
self.number = ''
self.label = 'Cell'
self.preferred = False
这意味着当实例化表单时,每个
phones
条目都将填充 PhoneNumber
的实例,该实例只是“空白”(从某种意义上说)。这就是正在发生的事情:
phones = FieldList(FormField(PhoneNumberFormPart, default=PhoneNumber), min_entries=1)
FWIW,当使用 FormField 时,您可以通过继承
Form
而不是 FlaskForm
来定义可重用的表单类。这给您带来的好处是不必手动删除 CSRF 字段。这看起来像:
class PhoneNumberFormPart(Form):
number = StringField("Phone Number", widget=Input('tel'))
label = SelectField('Label', choices=(("Cell", "Cell"), ("Home", "Home"), ("Work", "Work")))
preferred = BooleanField('Preferred', default=False)