diff --git a/CHANGES.rst b/CHANGES.rst index 83f77c2a..0c4c7ac4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,16 @@ .. currentmodule:: wtforms +Version 3.3.0b3 +--------------- + +Unreleased + +- Add :func:`fields.enum_choices`, :func:`fields.enum_coerce` and + :func:`~wtforms.datalist.enum_datalist` for Enum-backed choices, + replacing ``SelectChoice.from_enum`` and friends. :issue:`922` +- An Enum ``coerce`` is no longer wrapped, and options default to + ``member.value``. :issue:`922` + Version 3.3.0b2 --------------- diff --git a/docs/fields.rst b/docs/fields.rst index 355bc753..8657144a 100644 --- a/docs/fields.rst +++ b/docs/fields.rst @@ -337,9 +337,7 @@ Choice Fields option cannot be applied to your problem you may wish to skip choice validation (see below). - **Select fields with ````**:: - - Use :class:`SelectChoice` to assign an option to an ````. + **Select fields with optgroup**:: class PastebinEntry(Form): language = SelectField('Programming Language', choices=[ @@ -349,6 +347,8 @@ Choice Fields SelectChoice('text', 'Plain Text'), ]) + Use :class:`SelectChoice` to assign an option to an ````. + **Select fields with dynamic choice values**:: def available_groups(): @@ -400,28 +400,44 @@ Choice Fields **Select fields backed by an Enum**:: from enum import Enum + from wtforms.fields import enum_choices, enum_coerce class Color(Enum): - RED = 1 - GREEN = 2 - BLUE = 3 + RED = "red" + GREEN = "green" + BLUE = "blue" class PaintForm(Form): - color = SelectField(choices=SelectChoice.from_enum(Color), coerce=Color) + color = SelectField( + choices=enum_choices(Color), + coerce=enum_coerce(Color), + ) + + :func:`~wtforms.fields.enum_choices` builds the option list from + the Enum members; by default the HTML ``value`` of each option is the + member's ``value`` (``"red"``, ``"green"``, ...). + :func:`~wtforms.fields.enum_coerce` returns the matching ``coerce`` + callable that resolves the submitted string back into a member, so + ``form.color.data`` is a ``Color`` member after submit. Pre-selecting + works the usual way, with an Enum member: ``PaintForm(color=Color.RED)``. - :meth:`SelectChoice.from_enum` builds the option list from the Enum items; - the HTML ``value`` of each option is the item's ``name``. Passing the - Enum class itself as ``coerce`` installs the matching coercion, so - ``form.color.data`` is a ``Color`` item after submit. Pre-selecting - works the usual way, with an Enum item: ``PaintForm(color=Color.RED)``. + Use ``by="name"`` on **both** helpers when the Enum value is not a good + transport identifier — non-unique, non-serialisable, or when you simply + want ``member.name`` on the wire:: + + class PaintForm(Form): + color = SelectField( + choices=enum_choices(Color, by="name"), + coerce=enum_coerce(Color, by="name"), + ) - By default the option label is ``str(item)`` if the Enum defines its + By default the option label is ``str(member)`` if the Enum defines its own ``__str__`` (also the case for :class:`enum.StrEnum`), otherwise - ``item.name``. To customise, pass a ``label`` callable taking an Enum - item and returning the label string:: + ``member.name``. To customise, pass a ``label`` callable taking an Enum + member and returning the label string:: - SelectChoice.from_enum(Color, label=lambda item: item.name.title()) - # → [SelectChoice('RED', 'Red'), SelectChoice('GREEN', 'Green'), SelectChoice('BLUE', 'Blue')] + enum_choices(Color, label=lambda member: member.name.title()) + # → [SelectChoice('red', 'Red'), SelectChoice('green', 'Green'), SelectChoice('blue', 'Blue')] **Skipping choice validation**:: @@ -454,6 +470,10 @@ Choice Fields which are not in the given choices list will cause validation on the field to fail. +.. autofunction:: wtforms.fields.enum_choices + +.. autofunction:: wtforms.fields.enum_coerce + Submit fields ------------- @@ -580,8 +600,6 @@ Data Lists .. currentmodule:: wtforms -.. autoclass:: DataListChoice - .. class:: DataList(choices=None, *, render_kw=None, widget=None) A :mdn-tag:`datalist` of suggestions. Unlike @@ -658,6 +676,9 @@ Data Lists :class:`~wtforms.fields.TextAreaField` is silently ignored by the browser. +.. autoclass:: DataListChoice + +.. autofunction:: wtforms.datalist.enum_datalist .. currentmodule:: wtforms.fields diff --git a/src/wtforms/__init__.py b/src/wtforms/__init__.py index 683f5f43..e25bfdbe 100644 --- a/src/wtforms/__init__.py +++ b/src/wtforms/__init__.py @@ -2,6 +2,7 @@ from wtforms import widgets from wtforms.datalist import DataList from wtforms.datalist import DataListChoice +from wtforms.datalist import enum_datalist from wtforms.fields.choices import Choice from wtforms.fields.choices import RadioField from wtforms.fields.choices import SelectChoice @@ -86,4 +87,5 @@ "Choice", "SelectChoice", "DataListChoice", + "enum_datalist", ] diff --git a/src/wtforms/datalist.py b/src/wtforms/datalist.py index d1b41294..e0d34ba5 100644 --- a/src/wtforms/datalist.py +++ b/src/wtforms/datalist.py @@ -4,9 +4,10 @@ from wtforms import widgets from wtforms._compat import get_signature +from wtforms.fields.choices import _enum_options from wtforms.fields.choices import Choice -__all__ = ("DataList", "DataListChoice") +__all__ = ("DataList", "DataListChoice", "enum_datalist") @dataclass @@ -35,16 +36,6 @@ def __post_init__(self): def __iter__(self): return iter((self.value, self.label, self.render_kw)) - @classmethod - def from_enum(cls, enum_cls, *, label=None): - """Build a list of choices from an :class:`enum.Enum` class. - - See :meth:`SelectChoice.from_enum` for details. - """ - if label is None: - label = str if "__str__" in enum_cls.__dict__ else lambda m: m.name - return [cls(value=m.name, label=label(m)) for m in enum_cls] - @classmethod def from_input(cls, input): """Coerce a value passed by the user into a :class:`DataListChoice`.""" @@ -73,6 +64,20 @@ def from_input(cls, input): return cls(*input) +def enum_datalist(enum_cls, *, by="value", label=None): + """Build a list of :class:`DataListChoice` from an :class:`enum.Enum` class. + + Same semantics as :func:`~wtforms.fields.enum_choices`: ``by`` selects + which member attribute becomes the ``