Skip to content

Commit

Permalink
Select widget: Add support for mixture of grouped and non-grouped cho…
Browse files Browse the repository at this point in the history
…ices
  • Loading branch information
Lukas0907 committed Nov 21, 2024
1 parent b47e73d commit 514445a
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Version 3.2.1
Released 2024-10-21

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

Version 3.2.0
-------------
Expand Down
14 changes: 10 additions & 4 deletions src/wtforms/widgets/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,11 @@ class Select:
It also must provide a `has_groups()` method which tells whether choices
are divided into groups, and if they do, the field must have an
`iter_groups()` method that yields tuples of `(label, choices)`, where
`choices` is a iterable of `(value, label, selected)` tuples.
`choices` is a iterable of `(value, label, selected)` tuples. If `label`
is `None`, the choices are rendered next to the groups.
.. versionadded:: 3.2.1
Support for mixture of grouped and ungrouped options.
"""

validation_attrs = ["required", "disabled"]
Expand All @@ -363,12 +367,14 @@ def __call__(self, field, **kwargs):
html = [f"<select {select_params}>"]
if field.has_groups():
for group, choices in field.iter_groups():
optgroup_params = html_params(label=group)
html.append(f"<optgroup {optgroup_params}>")
if group is not None:
optgroup_params = html_params(label=group)
html.append(f"<optgroup {optgroup_params}>")
for choice in choices:
val, label, selected, render_kw = choice
html.append(self.render_option(val, label, selected, **render_kw))
html.append("</optgroup>")
if group is not None:
html.append("</optgroup>")
else:
for choice in field.iter_choices():
val, label, selected, render_kw = choice
Expand Down
24 changes: 24 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ def dummy_field_class():
return DummyField


@pytest.fixture
def dummy_grouped_field_class():
return DummyGroupedField


@pytest.fixture
def basic_widget_dummy_field(dummy_field_class):
return dummy_field_class("foo", name="bar", label="label", id="id")
Expand All @@ -28,6 +33,17 @@ def select_dummy_field(dummy_field_class):
return dummy_field_class([("foo", "lfoo", True, {}), ("bar", "lbar", False, {})])


@pytest.fixture
def select_dummy_grouped_field(dummy_grouped_field_class):
return dummy_grouped_field_class(
{
"g1": [("foo", "lfoo", True, {}), ("bar", "lbar", False, {})],
"g2": [("baz", "lbaz", False, {})],
None: [("abc", "labc", False, {})],
}
)


@pytest.fixture
def html5_dummy_field(dummy_field_class):
return dummy_field_class("42", name="bar", id="id")
Expand Down Expand Up @@ -87,6 +103,14 @@ def ngettext(self, singular, plural, n):
return self._translations.ngettext(singular, plural, n)


class DummyGroupedField(DummyField):
def iter_groups(self):
return self.data.items()

def has_groups(self):
return True


class DummyForm(dict):
pass

Expand Down
12 changes: 12 additions & 0 deletions tests/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,18 @@ def test_select(select_dummy_field):
)


def test_grouped_select(select_dummy_grouped_field):
select_dummy_grouped_field.name = "f"

assert (
Select()(select_dummy_grouped_field) == '<select id="" name="f">'
'<optgroup label="g1"><option selected value="foo">lfoo</option>'
'<option value="bar">lbar</option></optgroup>'
'<optgroup label="g2"><option value="baz">lbaz</option></optgroup>'
'<option value="abc">labc</option></select>'
)


def test_render_option():
# value, label, selected
assert (
Expand Down

0 comments on commit 514445a

Please sign in to comment.