在WTForms中创建带自定义属性的选择字段选项
我正在尝试创建一个 SelectField
或 SelectMultipleField
,希望能给它的 <option>
标签添加一些属性。我想添加的属性像是 data-id
或其他类似的 data-____
。但是我发现只能给 <select>
标签本身添加属性,而不能给选项添加属性,这让我很困惑。
最终我想要的效果应该是这样的:
<select id="regularstuff-here" name="regular-name-here">
<option value="1" data-id="somedata here" >Some Name here</option>
<option value="2" data-id="somedata here" >Some Name here</option>
</select>
我想我需要创建一个自定义的控件。如果我查看 WTForms 的源代码,我发现 select
控件调用了:
html.append(self.render_option(val, label, selected))
如果我查看那个方法:
@classmethod
def render_option(cls, value, label, selected, **kwargs):
options = dict(kwargs, value=value)
if selected:
options['selected'] = True
return HTMLString('<option %s>%s</option>' % (html_params(**options),
escape(text_type(label))))
看起来你不能给渲染 option
标签的方法传递任何额外的参数。
4 个回答
我不太确定我是否正确理解了这个需求,但我遇到过类似的需求——就是在一个选择框(SelectField)中添加选项。在我的情况下,我只是想添加一个选项,上面写着“请选择一个选项...”,因为选择框没有像查询选择框(QuerySelectField)那样的空白选项。这是为了能用JavaScript的onchange事件触发。但是你可以添加数据的id、值或者其他内容。
我在flask的路由中这样做的:
# populate choices for Category drop down
categories = Classification.query.filter_by(selectable=True).all()
all_cats = [cat.service for cat in categories]
unique_cat = list(dict.fromkeys(all_cats)) # remove duplicate names for Category drop down
unique_cat.sort() #sort alphabetically
unique_cat.insert(0, 'Choose a category...') # add this as first option in the drop down so onchange js is triggered
form.category.choices = unique_cat
最后两行是我们需求中最相关的内容。如果我查看生成的HTML,它现在多了一个元素:
<select class="form-control" id="category" name="category">
<option value="Choose a category...">Choose a category...</option>
<option value="Accounts">Accounts</option>
<option value="Business Applications">Business Applications</option>
</select>
作为Mark回答的另一种选择,这里有一个自定义的小部件(就是字段的'renderer'),它允许在渲染时传递选项属性。
from markupsafe import Markup
from wtforms.widgets.core import html_params
class CustomSelect:
"""
Renders a select field allowing custom attributes for options.
Expects the field to be an iterable object of Option fields.
The render function accepts a dictionary of option ids ("{field_id}-{option_index}")
which contain a dictionary of attributes to be passed to the option.
Example:
form.customselect(option_attr={"customselect-0": {"disabled": ""} })
"""
def __init__(self, multiple=False):
self.multiple = multiple
def __call__(self, field, option_attr=None, **kwargs):
if option_attr is None:
option_attr = {}
kwargs.setdefault("id", field.id)
if self.multiple:
kwargs["multiple"] = True
if "required" not in kwargs and "required" in getattr(field, "flags", []):
kwargs["required"] = True
html = ["<select %s>" % html_params(name=field.name, **kwargs)]
for option in field:
attr = option_attr.get(option.id, {})
html.append(option(**attr))
html.append("</select>")
return Markup("".join(html))
在声明字段时,将CustomSelect
的一个实例作为widget
参数传入。
customselect = SelectField(
"Custom Select",
choices=[("option1", "Option 1"), ("option2", "Option 2")],
widget=CustomSelect(),
)
在调用字段进行渲染时,传入一个包含选项ID的字典(格式是"{field_id}-{option_index}"),这个字典定义了要传递给选项的属性。
form.customselect(option_attr={"customselect-0": {"data-id": "value"} })
我只是想说,其实不需要对代码进行复杂的修改或者重写wtforms库,这个功能是可以实现的。虽然实现起来不是特别简单,但这个库本身是支持的。我之所以发现这一点,是因为我尝试为WTForms写一个修复方案,并自己提交了一个请求,结果后来发现其实可以直接这样做(我花了好几天才弄明白这一点):
>>> from wtforms import SelectField, Form
>>> class F(Form):
... a = SelectField(choices=[('a', 'Apple'), ('b', 'Banana')])
...
>>> i = 44
>>> form = F()
>>> for subchoice in form.a:
... print subchoice(**{'data-id': i})
... i += 1
...
<option data-id="44" value="a">Apple</option>
<option data-id="45" value="b">Banana</option>
可以在这里查看讨论:
https://github.com/wtforms/wtforms/pull/81
如果你和我一样,想在选择数组中为每个选项存储自定义属性,而不是在渲染时提供这些属性,那么下面这个定制的“AttribSelectField”和小部件会对你有帮助。这样,选项就变成了一个三元组(value, label, render_args),而不是原来的二元组(value, label)。
from wtforms.fields import SelectField
from wtforms.widgets import Select, html_params, HTMLString
class AttribSelect(Select):
"""
Renders a select field that supports options including additional html params.
The field must provide an `iter_choices()` method which the widget will
call on rendering; this method must yield tuples of
`(value, label, selected, html_attribs)`.
"""
def __call__(self, field, **kwargs):
kwargs.setdefault('id', field.id)
if self.multiple:
kwargs['multiple'] = True
html = ['<select %s>' % html_params(name=field.name, **kwargs)]
for val, label, selected, html_attribs in field.iter_choices():
html.append(self.render_option(val, label, selected, **html_attribs))
html.append('</select>')
return HTMLString(''.join(html))
class AttribSelectField(SelectField):
widget = AttribSelect()
def iter_choices(self):
for value, label, render_args in self.choices:
yield (value, label, self.coerce(value) == self.data, render_args)
def pre_validate(self, form):
if self.choices:
for v, _, _ in self.choices:
if self.data == v:
break
else:
raise ValueError(self.gettext('Is Not a valid choice'))
使用示例:
choices = [('', 'select a name', dict(disabled='disabled'))]
choices.append(('alex', 'Alex', dict()))
select_field = AttribSelectField('name', choices=choices, default='')
这段代码会为第一个 option
标签输出以下内容:
<option disabled="disabled" selected ...