将 WTForms 与枚举一起使用
Using WTForms with Enum
我有以下代码:
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'>
我认为您需要将传递给 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
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'>
尽管
我刚刚陷入了同一个兔子洞。不知道为什么,但是当初始化表单时 coerce
被调用 None
。在浪费了很多时间之后,我决定不值得强迫,而是使用:
field = SelectField("Label", choices=[(choice.name, choice.value) for choice in MyEnum])
并获取值:
selected_value = MyEnum[field.data]
这比公认的解决方案更简洁,因为您不需要多次放置选项。
默认情况下 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 isinstance(item, Company) else Company[item]
WTForm 将传入字符串,None
,或已经强制转换的数据 到coerce
;这有点烦人,但可以通过测试要强制转换的数据是否已经是一个实例来轻松解决:
isinstance(someobject, Company)
coerce
函数必须在强制转换时引发 ValueError
或 TypeError
。
您想使用 枚举名称 作为 select 框中的值;这些总是字符串。如果您的枚举 values 适合作为标签,那很好,您可以将它们用于选项可读文本,但不要将它们与选项值混淆,它必须是唯一的,enum值不需要。
Enum
classes 允许您使用订阅将包含枚举名称的字符串映射到 Enum
实例:
enum_instance = Company[enum_name]
请参阅 enum
模块文档中的 Programmatic access to enumeration members and their attributes。
接下来,我们可以将枚举对象转换为唯一字符串(针对 <option>
标签的 value="..."
属性)并将标签字符串(向用户显示)保留为标准挂钩方法在枚举 class 上,例如 __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 class 实现与演示分开; 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))
请注意,一旦 WTForm 3.0 发布,您可以在枚举对象上使用 __html__
方法而不必使用 markdownsafe.escape()
,因为该项目是 switching to using MarkupSafe for the label values.
coerce
参数指向的函数需要将浏览器传递过来的字符串(<select>
ed <option>
的值)转换成你的值类型在您的 choices
中指定:
Select fields keep a choices
property which is a sequence of (value
, label
) pairs. The value portion can be any type in theory, but as form data is sent by the browser as strings, you will need to provide a function which can coerce the string representation back to a comparable object.
https://wtforms.readthedocs.io/en/2.2.1/fields.html#wtforms.fields.SelectField
那样 coerced provided value can be compared with the configured ones.
由于您已经使用枚举项的名称 strings 作为值 (choices=[(choice.name, choice.name) for choice in Company]
),因此您无需强制执行。
如果您决定使用 整数 Enum::value
作为 <option>
的值,您必须强制 returned 字符串回到 int
s 进行比较。
choices=[(choice.value, choice.name) for choice in Company],
coerce=int
如果您想从表单中获取枚举项,则必须在 choices
([(choice, choice.name) for choice in Company]
) 中配置这些项并强制其字符串序列化(例如 Company.EnterMedia
) 回到 Enum
个实例,处理其他答案中提到的问题,例如 None
和强制枚举实例被传递到您的函数中:
给你 return Company::__str__
中的 Company::name
并使用 EnterMedia
作为默认值:
coerce=lambda value: value if isinstance(value, Company) else Company[value or Company.EnterMedia.name]
Hth, dtk
这里有一个不同的方法,它只创建一个新的 WTF EnumField 并对枚举类型进行一些 class 操作,使其可以无缝地与这些函数一起使用:
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)
我有以下代码:
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'>
我认为您需要将传递给 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
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'>
尽管
我刚刚陷入了同一个兔子洞。不知道为什么,但是当初始化表单时 coerce
被调用 None
。在浪费了很多时间之后,我决定不值得强迫,而是使用:
field = SelectField("Label", choices=[(choice.name, choice.value) for choice in MyEnum])
并获取值:
selected_value = MyEnum[field.data]
这比公认的解决方案更简洁,因为您不需要多次放置选项。
默认情况下 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 isinstance(item, Company) else Company[item]
WTForm 将传入字符串,None
,或已经强制转换的数据 到coerce
;这有点烦人,但可以通过测试要强制转换的数据是否已经是一个实例来轻松解决:
isinstance(someobject, Company)
coerce
函数必须在强制转换时引发 ValueError
或 TypeError
。
您想使用 枚举名称 作为 select 框中的值;这些总是字符串。如果您的枚举 values 适合作为标签,那很好,您可以将它们用于选项可读文本,但不要将它们与选项值混淆,它必须是唯一的,enum值不需要。
Enum
classes 允许您使用订阅将包含枚举名称的字符串映射到 Enum
实例:
enum_instance = Company[enum_name]
请参阅 enum
模块文档中的 Programmatic access to enumeration members and their attributes。
接下来,我们可以将枚举对象转换为唯一字符串(针对 <option>
标签的 value="..."
属性)并将标签字符串(向用户显示)保留为标准挂钩方法在枚举 class 上,例如 __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 class 实现与演示分开; 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))
请注意,一旦 WTForm 3.0 发布,您可以在枚举对象上使用 __html__
方法而不必使用 markdownsafe.escape()
,因为该项目是 switching to using MarkupSafe for the label values.
coerce
参数指向的函数需要将浏览器传递过来的字符串(<select>
ed <option>
的值)转换成你的值类型在您的 choices
中指定:
Select fields keep a
choices
property which is a sequence of (value
,label
) pairs. The value portion can be any type in theory, but as form data is sent by the browser as strings, you will need to provide a function which can coerce the string representation back to a comparable object.
https://wtforms.readthedocs.io/en/2.2.1/fields.html#wtforms.fields.SelectField
那样 coerced provided value can be compared with the configured ones.
由于您已经使用枚举项的名称 strings 作为值 (choices=[(choice.name, choice.name) for choice in Company]
),因此您无需强制执行。
如果您决定使用 整数 Enum::value
作为 <option>
的值,您必须强制 returned 字符串回到 int
s 进行比较。
choices=[(choice.value, choice.name) for choice in Company],
coerce=int
如果您想从表单中获取枚举项,则必须在 choices
([(choice, choice.name) for choice in Company]
) 中配置这些项并强制其字符串序列化(例如 Company.EnterMedia
) 回到 Enum
个实例,处理其他答案中提到的问题,例如 None
和强制枚举实例被传递到您的函数中:
给你 return Company::__str__
中的 Company::name
并使用 EnterMedia
作为默认值:
coerce=lambda value: value if isinstance(value, Company) else Company[value or Company.EnterMedia.name]
Hth, dtk
这里有一个不同的方法,它只创建一个新的 WTF EnumField 并对枚举类型进行一些 class 操作,使其可以无缝地与这些函数一起使用:
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)