esreli
esreli

Reputation: 5061

Python Flask WTForm SelectField with Enum values 'Not a valid choice' upon validation

My Python Flask app is using WTForms with built in python Enum support. I'm attempting to submit a form (POST) where a SelectField is populated by all values of a Enum.

When I hit 'Submit' i'm given the error, 'Not a valid choice.' This seems strange because when checking the values of the incoming form, the form seemingly does contain a valid choice from the list of Enum values provided.

I'm using a subclass of Enum named AJBEnum which is formatted like so:

class UserRole(AJBEnum):
    admin = 0
    recipient = 1

I chose to do this because I use many Enums through the project and wanted to write a helper function that gathers all choices and formats them WTForm SelectField tuple friendly. AJBEnum is formatted like so:

class AJBEnum(Enum):

    @classmethod
    def choices(cls, blank=True):
        choices = []
        if blank == True:
            choices += [("", "")]
        choices += [(choice, choice.desc()) for choice in cls]
        return choices

Which means I can give WTForms all choices for UserRole during the creating of the SelectField like so:

role = SelectField('Role', choices=UserRole.choices(blank=False), default=UserRole.recipient)

Note the function parameter blank provides an additional blank SelectField option in case the SelectField is optional. In this case, it is not.

When I hit the Submit button I check the incoming incoming request in my routes and by printing the form.data i'm given this content:

{'email': '[email protected]', 'password': 'fake', 'plan': 'A', 'confirm': 'fake', 'submit': True, 'id': None, 'role': 'UserRole.recipient'}

As you can see, it appears WTForms has stringified UserRole.recipient. Is there a way to coerce WTForms into converting the incoming POST request value back to the Enum value that it was intended to be?

Upvotes: 2

Views: 4299

Answers (1)

Mike Haboustak
Mike Haboustak

Reputation: 2296

Is there a way to coerce WTForms

The argument you're looking for is actually called coerce, and it accepts a callable that converts the string representation of the field to the choice's value.

  1. The choice value should be an Enum instance
  2. The field value should be str(Enum.value)
  3. The field text should be Enum.name

To accomplish this, I've extended Enum with some WTForms helpers:

class FormEnum(Enum):
    @classmethod
    def choices(cls):
        return [(choice, choice.name) for choice in cls]

    @classmethod
    def coerce(cls, item):
        return cls(int(item)) if not isinstance(item, cls) else item

    def __str__(self):
        return str(self.value)

class UserRole(FormEnum):
    Owner = 1
    Contributor = 2
    Guest = 3

You can then edit a FormEnum derived value using a SelectField:

role = SelectField(
        "Role",
        choices = UserRole.choices(),
        coerce = UserRole.coerce)

Upvotes: 8

Related Questions