Skip to content

Commit 514445a

Browse files
committed
Select widget: Add support for mixture of grouped and non-grouped choices
1 parent b47e73d commit 514445a

File tree

4 files changed

+48
-4
lines changed

4 files changed

+48
-4
lines changed

CHANGES.rst

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Version 3.2.1
66
Released 2024-10-21
77

88
- Fix :class:`~fields.SelectMultipleBase` import. :issue:`861` :pr:`862`
9+
- Support for mixture of grouped and non-grouped choices in
10+
:class:`~widgets.Select`. :pr:`870`
911

1012
Version 3.2.0
1113
-------------

src/wtforms/widgets/core.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,11 @@ class Select:
343343
It also must provide a `has_groups()` method which tells whether choices
344344
are divided into groups, and if they do, the field must have an
345345
`iter_groups()` method that yields tuples of `(label, choices)`, where
346-
`choices` is a iterable of `(value, label, selected)` tuples.
346+
`choices` is a iterable of `(value, label, selected)` tuples. If `label`
347+
is `None`, the choices are rendered next to the groups.
348+
349+
.. versionadded:: 3.2.1
350+
Support for mixture of grouped and ungrouped options.
347351
"""
348352

349353
validation_attrs = ["required", "disabled"]
@@ -363,12 +367,14 @@ def __call__(self, field, **kwargs):
363367
html = [f"<select {select_params}>"]
364368
if field.has_groups():
365369
for group, choices in field.iter_groups():
366-
optgroup_params = html_params(label=group)
367-
html.append(f"<optgroup {optgroup_params}>")
370+
if group is not None:
371+
optgroup_params = html_params(label=group)
372+
html.append(f"<optgroup {optgroup_params}>")
368373
for choice in choices:
369374
val, label, selected, render_kw = choice
370375
html.append(self.render_option(val, label, selected, **render_kw))
371-
html.append("</optgroup>")
376+
if group is not None:
377+
html.append("</optgroup>")
372378
else:
373379
for choice in field.iter_choices():
374380
val, label, selected, render_kw = choice

tests/conftest.py

+24
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ def dummy_field_class():
1818
return DummyField
1919

2020

21+
@pytest.fixture
22+
def dummy_grouped_field_class():
23+
return DummyGroupedField
24+
25+
2126
@pytest.fixture
2227
def basic_widget_dummy_field(dummy_field_class):
2328
return dummy_field_class("foo", name="bar", label="label", id="id")
@@ -28,6 +33,17 @@ def select_dummy_field(dummy_field_class):
2833
return dummy_field_class([("foo", "lfoo", True, {}), ("bar", "lbar", False, {})])
2934

3035

36+
@pytest.fixture
37+
def select_dummy_grouped_field(dummy_grouped_field_class):
38+
return dummy_grouped_field_class(
39+
{
40+
"g1": [("foo", "lfoo", True, {}), ("bar", "lbar", False, {})],
41+
"g2": [("baz", "lbaz", False, {})],
42+
None: [("abc", "labc", False, {})],
43+
}
44+
)
45+
46+
3147
@pytest.fixture
3248
def html5_dummy_field(dummy_field_class):
3349
return dummy_field_class("42", name="bar", id="id")
@@ -87,6 +103,14 @@ def ngettext(self, singular, plural, n):
87103
return self._translations.ngettext(singular, plural, n)
88104

89105

106+
class DummyGroupedField(DummyField):
107+
def iter_groups(self):
108+
return self.data.items()
109+
110+
def has_groups(self):
111+
return True
112+
113+
90114
class DummyForm(dict):
91115
pass
92116

tests/test_widgets.py

+12
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,18 @@ def test_select(select_dummy_field):
175175
)
176176

177177

178+
def test_grouped_select(select_dummy_grouped_field):
179+
select_dummy_grouped_field.name = "f"
180+
181+
assert (
182+
Select()(select_dummy_grouped_field) == '<select id="" name="f">'
183+
'<optgroup label="g1"><option selected value="foo">lfoo</option>'
184+
'<option value="bar">lbar</option></optgroup>'
185+
'<optgroup label="g2"><option value="baz">lbaz</option></optgroup>'
186+
'<option value="abc">labc</option></select>'
187+
)
188+
189+
178190
def test_render_option():
179191
# value, label, selected
180192
assert (

0 commit comments

Comments
 (0)