WTForms SelectField with Custom Option Attributes
I was surprised to run into this problem in my Flask app, I needed to pass a custom attribute to one of the options in a select list provided by WTForms (in my case I wanted to set the first option as disabled
), but it turns out that this is a common problem with lots of work arounds.
Here’s a nice clean solution to pass those custom attributes, keeping the built-in SelectField
, but using a custom widget which supports providing attributes for any of the options via a keyed dictionary.
I want to generate markup equivalent to this boostrap 4 example, where the first option is disabled.
<select class="custom-select" id="validationCustom04" required>
<option selected disabled value="">Choose...</option>
<option>...</option>
</select>
However there’s no simple way to set a disabled
attribute on a specific option. So after reading through the WTForms source code I wrote this 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))
To use it, you’ll need to first pass an instance of CustomSelect
as the widget
parameter when declaring the field.
customselect = SelectField(
"Custom Select",
choices=[("option1", "Option 1"), ("option2", "Option 2")],
widget=CustomSelect(),
)
Then, when calling the field to render in your template, you can pass a dictionary of option ids (in the format {field_id}-{option_index}
) which defines a dictionary of attributes to be passed to the option.
form.customselect(option_attr={"customselect-0": {"disabled": ""} })
Or perhaps you want to pass a data attribute.
form.customselect(option_attr={"customselect-0": {"data-id": "value"} })
Hope this is helpful.