Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Version 3.3.0b4

Unreleased

- Preserve explicitly empty labels when rendering select and radio choices.
- :class:`~datalist.DataList` ``choices`` accepts the shorthand
``{value: label}`` dict syntax. :issue:`886`
- ``choices`` callables on fields nested in a :class:`~fields.FieldList` are
Expand Down
2 changes: 1 addition & 1 deletion src/wtforms/fields/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def __iter__(self):
for i, choice in enumerate(self._iter_choices_normalized()):
opt = self._Option(
id=f"{self.id}-{i}",
label=choice.label or choice.value,
label=choice.value if choice.label is None else choice.label,
**opts,
)
opt.choice = choice
Expand Down
5 changes: 3 additions & 2 deletions src/wtforms/widgets/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ def render_option(cls, choice, **kwargs):
options = {"value": value, **(choice.render_kw or {}), **kwargs}
if choice.selected:
options["selected"] = True
label = escape(choice.label or choice.value)
label = escape(choice.value if choice.label is None else choice.label)
return Markup(f"<option {html_params(**options)}>{label}</option>")

@classmethod
Expand Down Expand Up @@ -482,7 +482,8 @@ def _dispatch_render_option(cls):
accepts_kwargs = any(p.kind == p.VAR_KEYWORD for p in sig.parameters.values())

def adapter(choice):
args = (choice.value, choice.label or choice.value, choice.selected)
label = choice.value if choice.label is None else choice.label
args = (choice.value, label, choice.selected)
if accepts_kwargs:
return ro(*args, **(choice.render_kw or {}))
return ro(*args)
Expand Down
42 changes: 42 additions & 0 deletions tests/fields/test_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
from wtforms.fields import enum_choices
from wtforms.fields import enum_coerce
from wtforms.fields import Field
from wtforms.fields import RadioField
from wtforms.fields import SelectChoice
from wtforms.fields import SelectField
from wtforms.fields import SelectMultipleField
from wtforms.form import Form

if sys.version_info >= (3, 11):
Expand All @@ -25,6 +27,46 @@ def make_form(name="F", **fields):
return type(str(name), (Form,), fields)


@pytest.mark.parametrize("field_type", [SelectField, SelectMultipleField, RadioField])
@pytest.mark.parametrize("optgroup", [None, "Group"])
def test_explicit_empty_choice_labels(field_type, optgroup):
F = make_form(
s=field_type(
choices=[
SelectChoice("blank", "", optgroup=optgroup),
SelectChoice("automatic", optgroup=optgroup),
]
)
)
form = F(s=["blank"] if field_type is SelectMultipleField else "blank")
options = list(form.s)
assert options[0].label.text == ""
assert options[1].label.text == "automatic"
if field_type is RadioField:
assert '<label for="s-0"></label>' in form.s()
else:
assert '<option selected value="blank"></option>' in form.s()
assert '<option value="automatic">automatic</option>' in form.s()


def test_legacy_render_option_preserves_empty_label():
labels = []

class LegacySelect(widgets.Select):
@classmethod
def render_option(cls, value, label, selected, **kwargs):
labels.append(label)
return super().render_option(Choice(value, label, selected, kwargs))

F = make_form(
s=SelectField(choices=[SelectChoice("blank", "")], widget=LegacySelect())
)
with pytest.warns(DeprecationWarning, match="pre-3.3 signature"):
html = F(s="blank").s()
assert labels == [""]
assert '<option selected value="blank"></option>' in html


def test_select_field_copies_choices():
class F(Form):
items = SelectField(choices=[])
Expand Down
Loading