我有以下代码:
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
这是我的wtform字段:
company = SelectField("Company", choices=Company.choices(), coerce=Company.coerce)
这是以我的形式生成的html:
<select class="" id="company" name="company" with_label="">
<option value="EnterMedia">EnterMedia</option>
<option value="WhalesMedia">WhalesMedia</option>
</select>
不知何故,当我点击提交时,我不断得到“不是一个有效的选择”。
这是我的终端输出:
当我看到我的终端时,我看到以下内容:
Coerce None <type 'NoneType'>
Coerce EnterMedia <type 'unicode'>
Coerce EnterMedia <type 'str'>
Coerce WhalesMedia <type 'str'>
答案 0 :(得分:2)
我认为您需要将传递给coerce
方法的参数转换为枚举的实例。
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
答案 1 :(得分:2)
这比公认的解决方案更清洁,因为您不需要多次提供选项。
默认情况下,Python会使用路径将对象转换为字符串,这就是您最终使用Company.EnterMedia等的原因。在下面的解决方案中,我使用__str__
告诉python应该使用该名称,然后使用[]表示法按名称查找枚举对象。
class Company(enum.Enum):
EnterMedia = 'EnterMedia'
WhalesMedia = 'WhalesMedia'
def __str__(self):
return self.name
@classmethod
def choices(cls):
return [(choice, choice.value) for choice in cls]
@classmethod
def coerce(cls, item):
return item if type(item) == Company else Company[item]
答案 2 :(得分:2)
WTForm将以字符串None
或已强制数据的形式传递给coerce
;这有点烦人,但是可以通过测试强制数据是否已经是实例来轻松处理:
isinstance(someobject, Company)
强制时,coerce
函数必须引发ValueError
或TypeError
。
您要使用枚举名称作为选择框中的值;这些永远都是字符串。如果您的枚举 values 可以用作标签,那很好,您可以将其用于选项可读文本,但不要将它们与选项值混淆,选项值必须是唯一的,枚举值不要需要成为。
Enum
类可让您通过订阅将包含枚举名称的字符串映射到Enum
实例:
enum_instance = Company[enum_name]
请参见enum
模块文档中的Programmatic access to enumeration members and their attributes。
接下来,我们可以将枚举对象转换为唯一的字符串(用于value="..."
标签的<option>
属性),并将标签的字符串(向用户显示)转换为标准的钩子方法。枚举类,例如__str__
和__html__
。
针对您的特定设置,一起使用:
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)
)
以上内容使Enum类的实现与表示分离; cource_for_enum()
函数负责将KeyError
映射到ValueError
。 (v, escape(v))
对为每个选项提供值和标签; str(v)
用于<option value="...">
属性值,然后通过Company[__html__result]
使用相同的字符串强制返回到枚举实例。 WTForms 3.0将开始使用MarkupSafe
来标记标签,但是在那之前,我们可以直接使用escape(v)
提供相同的功能,而__html__
则使用coerce_for_enum()
提供合适的呈现方式。
如果必须记住要放入列表中的内容,并且使用choices
变得很乏味,则可以使用辅助函数生成coerce
和__str__
选项;您甚至可以验证是否有合适的__html__
和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}
方法可用:
company = SelectField("Company", **enum_field_options(Company))
以您的示例为例,然后使用
__html__
请注意,一旦WTForm 3.0发布,您就可以对枚举对象使用markdownsafe.escape()
方法,而不必使用"X [Y,Z,V]"
,因为项目是switching to using MarkupSafe for the label values。
答案 3 :(得分:1)
我刚刚在同一个兔子洞里。不确定原因,但在初始化表单时coerce
会调用None
。在浪费了很多时间之后,我觉得它不值得强迫,而是我刚刚使用过:
field = SelectField("Label", choices=[(choice.name, choice.value) for choice in MyEnum])
并获取值:
selected_value = MyEnum[field.data]
答案 4 :(得分:0)
由coerce
参数指向的函数需要将浏览器传递的字符串(由<select>
编辑的<option>
的值)转换为您在中指定的值的类型。您的choices
:
选择字段保留
choices
属性,该属性是(value
,label
)对的序列。从理论上讲,值部分可以是任何类型,但是由于表单数据是由浏览器以字符串形式发送的,因此您需要提供一个可以将字符串表示形式强制转换为可比较对象的函数。
https://wtforms.readthedocs.io/en/2.2.1/fields.html#wtforms.fields.SelectField
这样coerced provided value可以是compared with the configured ones。
由于您已经使用枚举项目的名称 strings 作为值(choices=[(choice.name, choice.name) for choice in Company]
),因此您无需强迫。
如果您决定使用 integer Enum::value
作为<option>
的值,则必须将返回的字符串强制转换回int
进行比较。
choices=[(choice.value, choice.name) for choice in Company],
coerce=int
如果要从表单中获取枚举项,则必须在choices
([(choice, choice.name) for choice in Company]
)中进行配置,并强制其字符串序列化(例如Company.EnterMedia
)返回放入Enum
实例中,处理其他答案中提到的问题,例如None
和强制枚举实例被传递到您的函数中:
鉴于您在Company::name
中返回了Company::__str__
并使用EnterMedia
作为默认值:
coerce=lambda value: value if isinstance(value, Company) else Company[value or Company.EnterMedia.name]
Hth,dtk
答案 5 :(得分:0)
这里有一个不同的方法,它仅创建一个新的WTF EnumField并对enum-type进行一些类操作,以使其可与以下功能无缝使用:
import enum
@enum.unique
class MyEnum(enum.Enum):
foo = 0
bar = 10
然后在某个地方创建EnumField定义,该定义只是将SelectField扩展为使用Enum类型:
import enum
from markupsafe import escape
from wtforms import SelectField
from typing import Union, Callable
class EnumField(SelectField):
def coerce(enum_type: enum.Enum) -> Callable[[Union[enum.Enum, str]], enum.Enum]:
def coerce(name: Union[enum.Enum, str]) -> enum.Enum:
if isinstance(name, enum_type):
return name
try:
return enum_type[name]
except KeyError:
raise ValueError(name)
return coerce
def __init__(self, enum_type: enum.Enum, *args, **kwargs):
def attach_functions(enum_type: enum.Enum) -> enum.Enum:
enum_type.__str__ = lambda self: self.name
enum_type.__html__ = lambda self: self.name
return enum_type
_enum_type = attach_functions(enum_type)
super().__init__(_enum_type.__name__,
choices=[(v, escape(v)) for v in _enum_type],
coerce=EnumField.coerce(_enum_type), *args, **kwargs)
现在在您的代码中,您可以天真地使用这些东西:
class MyForm(FlaskForm):
field__myenum = EnumField(MyEnum)
submit = SubmitField('Submit')
@app.route("/action", methods=['GET', 'POST'])
def action():
form = MyForm()
if form.validate_on_submit():
print('Enum value is: ', form.field__myenum) #<MyEnum.foo: 0>
return redirect(url_for('.action'))
elif request.method == 'GET': # display the information on record
form.field__myenum.data = MyEnum.foo
form.field__myenum.default = MyEnum.foo
return render_template('action.html', form=form)
答案 6 :(得分:-3)
class Company(enum.Enum):
WhalesMedia = 'WhalesMedia'
EnterMedia = 'EnterMedia'
@classmethod
def choices(cls):
return [(choice, choice.value) for choice in cls]
@classmethod
def coerce(cls, item):
"""item will be both type(enum) AND type(unicode).
"""
if item == 'Company.EnterMedia' or item == Company.EnterMedia:
return Company.EnterMedia
elif item == 'Company.WhalesMedia' or item == Company.WhalesMedia:
return Company.WhalesMedia
else:
print "Can't coerce", item, type(item)
所以我乱砍了,这很有效。
在我看来,强制将在选择中应用于(x,y)(x,y)。
我似乎无法理解为什么我会一直看到:Can't coerce None <type 'NoneType'>
但