I have the following code:
class Company(enum.Enum):
EnterMedia = 'EnterMedia'
WhalesMedia = 'WhalesMedia'
@classmethod
def choices(cls):
return [(choice.name, choice.name) for choice in cls]
@classmethod
def coerce(cls, item):
print "Coerce", item, type(item)
if item == 'WhalesMedia':
return Company.WhalesMedia
elif item == 'EnterMedia':
return Company.EnterMedia
else:
raise ValueError
And this is my wtform field:
company = SelectField("Company", choices=Company.choices(), coerce=Company.coerce)
This is the html generated in my form:
<select class="" id="company" name="company" with_label="">
<option value="EnterMedia">EnterMedia</option>
<option value="WhalesMedia">WhalesMedia</option>
</select>
Somehow, when I click submit, I keep getting "Not a Valid Choice".
This is my terminal output:
When I look at my terminal I see the following:
Coerce None <type 'NoneType'>
Coerce EnterMedia <type 'unicode'>
Coerce EnterMedia <type 'str'>
Coerce WhalesMedia <type 'str'>
WTForm will either pass in strings, None
, or already coerced data to coerce
; this is a little annoying but easily handled by testing if the data to coerce is already an instance:
isinstance(someobject, Company)
The coerce
function must otherwise raise a ValueError
or TypeError
when coercing.
You want to use the enum names as the values in the select box; these are always going to be strings. If your enum values are suitable as labels, then that's great, you can use those for the option readable text, but don't confuse those with the option values, which must be unique, enum values do not need to be.
Enum
classes let you map a string containing an enum name to the Enum
instance by using subscription:
enum_instance = Company[enum_name]
See Programmatic access to enumeration members and their attributes in the enum
module documentation.
Next, we could leave conversion of the enum objects to unique strings (for the value="..."
attribute of <option>
tags) and to label strings (to show to the users) to standard hook methods on the enum class, such as __str__
and __html__
.
Together, for your specific setup, use:
from markupsafe import escape
class Company(enum.Enum):
EnterMedia = 'Enter Media'
WhalesMedia = 'Whales Media'
def __str__(self):
return self.name # value string
def __html__(self):
return self.value # label string
def coerce_for_enum(enum):
def coerce(name):
if isinstance(name, enum):
return name
try:
return enum[name]
except KeyError:
raise ValueError(name)
return coerce
company = SelectField(
"Company",
# (unique value, human-readable label)
# the escape() call can be dropped when using wtforms 3.0 or newer
choices=[(v, escape(v)) for v in Company],
coerce=coerce_for_enum(Company)
)
The above keeps the Enum class implementation separate from the presentation; the cource_for_enum()
function takes care of mapping KeyError
s to ValueError
s. The (v, escape(v))
pairs provide the value and label for each option; str(v)
is used for the <option value="...">
attribute value, and that same string is then used via Company[__html__result]
to coerce back to enum instances. WTForms 3.0 will start using MarkupSafe
for labels, but until then, we can directly provide the same functionality with escape(v)
, which in turn uses __html__
to provide a suitable rendering.
If having to remember about what to put in the list comprehension, and to use coerce_for_enum()
is becoming tedious, you can generate the choices
and coerce
options with a helper function; you could even have it verify that there are suitable __str__
and __html__
methods available:
def enum_field_options(enum):
"""Produce WTForm Field instance configuration options for an Enum
Returns a dictionary with 'choices' and 'coerce' keys, use this as
**enum_fields_options(EnumClass) when constructing a field:
enum_selection = SelectField("Enum Selection", **enum_field_options(EnumClass))
Labels are produced from str(enum_instance.value) or
str(eum_instance), value strings with str(enum_instance).
"""
assert not {'__str__', '__html__'}.isdisjoint(vars(enum)), (
"The {!r} enum class does not implement __str__ and __html__ methods")
def coerce(name):
if isinstance(name, enum):
# already coerced to instance of this enum
return name
try:
return enum[name]
except KeyError:
raise ValueError(name)
return {'choices': [(v, escape(v)) for v in enum], 'coerce': coerce}
and for your example, then use
company = SelectField("Company", **enum_field_options(Company))
Note that once WTForm 3.0 is released, you can use a __html__
method on enum objects without having to use markdownsafe.escape()
, because the project is switching to using MarkupSafe for the label values.
I think you need to convert the argument passed to coerce
method into an instance of the enum.
import enum
class Company(enum.Enum):
EnterMedia = 'EnterMedia'
WhalesMedia = 'WhalesMedia'
@classmethod
def choices(cls):
return [(choice.name, choice.value) for choice in cls]
@classmethod
def coerce(cls, item):
item = cls(item) \
if not isinstance(item, cls) \
else item # a ValueError thrown if item is not defined in cls.
return item.value
# if item.value == 'WhalesMedia':
# return Company.WhalesMedia.value
# elif item.value == 'EnterMedia':
# return Company.EnterMedia.value
# else:
# raise ValueError
I've just been down the same rabbit hole. Not sure why, but coerce
gets called with None
when the form is initialised. After wasting a lot of time, I decided it's not worth coercing, and instead I just used:
field = SelectField("Label", choices=[(choice.name, choice.value) for choice in MyEnum])
and to get the value:
selected_value = MyEnum[field.data]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With