问题
I am trying to create a SelectField
or SelectMultipleField
that allows me to add attributes to it's <option>
tags. I am trying to add attributes like data-id
or another data-____
. I have not been able to figure out how to do this as it only seems possible to add attributes to the <select>
tag itself and not the options.
The end result should be something like:
<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>
I assume I have to create a custom widget. If I look at the source for WTForms I see that select
widget calls:
html.append(self.render_option(val, label, selected))
If I look at that method:
@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))))
So it does not seem that you can pass any extra params to the method that renders the option
tags.
回答1:
I just wanted to say that this is possible without monkey patching or rewriting wtforms. The library code does support it although not very straightforwardly. I found this out because I attempted to write a fix for WTForms and submitted a PR myself and found out afterwards that you can just do this (I've spent days trying to figure this out):
>>> 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>
See the convo here:
https://github.com/wtforms/wtforms/pull/81
回答2:
If you (like me) want to store the custom attributes on the choices array, per choice, rather than supplying at render time, the following customised "AttribSelectField" and widget should help. The choices become a 3-tuple of (value, label, render_args) instead of a 2-tuple of (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'))
An Example of usage:
choices = [('', 'select a name', dict(disabled='disabled'))]
choices.append(('alex', 'Alex', dict()))
select_field = AttribSelectField('name', choices=choices, default='')
which outputs the following for the first option
tag:
<option disabled="disabled" selected ...
回答3:
As an alternative to Mark's answer, here's a custom widget (which is the field 'renderer') that allows passing option attributes at render time.
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))
When declaring the field, pass an instance of CustomSelect
as the widget
parameter.
customselect = SelectField(
"Custom Select",
choices=[("option1", "Option 1"), ("option2", "Option 2")],
widget=CustomSelect(),
)
When calling the field to render, pass a dictionary of option ids ("{field_id}-{option_index}") which define a dictionary of attributes to be passed to the option.
form.customselect(option_attr={"customselect-0": {"data-id": "value"} })
来源:https://stackoverflow.com/questions/23460857/create-selectfield-options-with-custom-attributes-in-wtforms