Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SelectField Enum support #338

Open
vincentwhales opened this issue May 19, 2017 · 9 comments
Open

SelectField Enum support #338

vincentwhales opened this issue May 19, 2017 · 9 comments
Labels
enhancement New feature, or existing feature improvement

Comments

@vincentwhales
Copy link

vincentwhales commented May 19, 2017

Enums are part of the standard lib right now and they are becoming mainstream.

Can you do a tutorial on how we can integrate enums with SelectField?

@ftm
Copy link
Contributor

ftm commented Jun 13, 2018

This is something we'll consider supporting and adding to the documentation, however currently SelectField does not directly support Enum.

In the meantime, it's possible to use them by converting the Enum into the form SelectField understands:

>>> from wtforms.fields import SelectField
>>> from enum import Enum
>>> class Colour(Enum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3
... 
>>> SelectField(u'Favourite colour', choices=[(member.value, name.capitalize()) for name, member in Colour.__members__.items()])
<UnboundField(SelectField, ('Favourite colour',), {'choices': [(1, 'Red'), (2, 'Green'), (3, 'Blue')]})>

@ftm ftm closed this as completed Jun 13, 2018
@killthekitten
Copy link

@ftm how can I follow a discussion around adding enums?

@WilliamWalker
Copy link

Using the above workaround, the form will not validate.

@frankier
Copy link

Why was this issue closed?

@azmeuk azmeuk changed the title Can we get a tutorial to get SelectField to work with Enum? SelectField Enum support Sep 20, 2021
@azmeuk
Copy link
Member

azmeuk commented Sep 20, 2021

Sure, we can reopen the ticket and discuss it here.

@azmeuk azmeuk reopened this Sep 20, 2021
@azmeuk azmeuk added the enhancement New feature, or existing feature improvement label Sep 20, 2021
@ItsDrike
Copy link

I also need something like this, I'm currently able to use this:

from wtforms import Form, SelectField
from enum import IntEnum

class Role(IntEnum):
    USER = 0
    ADMIN = 1

class UserForm(Form):
    user_role = SelectField("Role", choices=[(role, role.name) for role in Role])

Which almost works, as doing UserForm(user_role=Role.ADMIN) is fine, and I'm able to see the options properly in the HTML, however the validation fails and form.user_role.data is set to 1 rather than Role.ADMIN (presumably from calling str(). I'm not sure how to fix this though (I'm fairly new to WTForms), any ideas?

I'd love to see something like EnumField get added directly, however as that's not currently available, I'm happy to just use some kind of workaround with SelectField, issue is that I can't for the life of me figure out how to get it to work.

@ItsDrike
Copy link

ItsDrike commented Oct 30, 2023

If anyone is looking for a solution, I ended up creating my own custom field class extending SelectField like this:

from collections.abc import Callable
from enum import Enum
from typing import cast

from wtforms import SelectField


class EnumSelectField(SelectField):
    """Extension of ``wtforms.SelectField`` that supports Enum classes.

    Note that this will mutate the passed enum class, by adding __str__ and __html__
    methods (unless already present).
    """

    def __init__(self, label: str, enum_cls: type[Enum], *args, **kwargs) -> None:
        self._attach_functions(enum_cls)
        choices = [(variant, variant.name) for variant in enum_cls]
        coerce = self._coerce_enum(enum_cls)
        super().__init__(
            label,
            *args,
            choices=choices,
            coerce=coerce,  # type: ignore # pyright expects type[str]
            **kwargs,
        )

    @staticmethod
    def _coerce_enum(enum_type: type[Enum]) -> Callable[[Enum | str], Enum]:
        """Create a custom coerce function for enums used in ``wtforms.SelectField``."""

        def coerce(name: Enum | str) -> Enum:
            if isinstance(name, enum_type):
                return name

            name = cast(str, name)  # pyright doesn't auto-infer this for some reason

            try:
                return enum_type[name]
            except KeyError:
                raise ValueError(name)

        return coerce

    @staticmethod
    def _attach_functions(enum_type: type[Enum]) -> None:
        """Attach the necessary __str__ and __html__ functions to the enum class.

        This will only attach these functions if they're not present already.
        """
        if not hasattr(enum_type, "__str__"):
            enum_type.__str__ = lambda self: self.name  # type: ignore
        if not hasattr(enum_type, "__html__"):
            enum_type.__html__ = lambda self: self.name  # type: ignore

It works well enough, although it is a bit hacky as it mutates the enum class you give it and adds the __str__ and __html__ methods to it dynamically. I don't see any other way to do this but to dynamically mutate the enum class though, which is unfortunate, and a built-in way to achieve it would be great to have.

@art-solopov
Copy link

What worked for me was using choices and coerce:

class PointTypes(enum.Enum):
    MUSEUM = 'museum'
    SIGHT = 'sight'
    # etc

# ...

def point_types_coerce(data):
    logger.debug('data %s [%s]', data, type(data))
    if isinstance(data, PointTypes):
        return data.value
    return data


class PointForm(FlaskForm):
    TYPE_CHOICES = [(p.value, p.value.capitalize()) for p in PointTypes]

    # ...
    type = SelectField('Point type', choices=TYPE_CHOICES, coerce=point_types_coerce)

@hasansezertasan
Copy link
Member

What worked for me was using choices and coerce:

There are a dozen of different implementations to use enums with SelectField. I really would love to see out-of-box support for Enum in WTForms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature, or existing feature improvement
Development

No branches or pull requests

9 participants