在Enum中使用WTForms

时间:2017-05-19 20:54:56

标签: python flask enums wtforms

我有以下代码:

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'>

7 个答案:

答案 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函数必须引发ValueErrorTypeError

您要使用枚举名称作为选择框中的值;这些永远都是字符串。如果您的枚举 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属性,该属性是(valuelabel)对的序列。从理论上讲,值部分可以是任何类型,但是由于表单数据是由浏览器以字符串形式发送的,因此您需要提供一个可以将字符串表示形式强制转换为可比较对象的函数。

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'>