簡體   English   中英

將 WTForms 與 Enum 一起使用

[英]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'>

WTForm 將傳入字符串、 None已經強制轉換的數據coerce 這有點煩人,但通過測試要強制的數據是否已經是一個實例很容易處理:

isinstance(someobject, Company)

coerce函數在coerce必須否則引發ValueErrorTypeError

您想使用枚舉名稱作為選擇框中的值; 這些總是字符串。 如果您的枚舉適合作為標簽,那就太好了,您可以將它們用於選項可讀文本,但不要將它們與選項值混淆,選項值必須是唯一的,枚舉值不需要是唯一的。

Enum類允許您使用訂閱將包含枚舉名稱的字符串映射到Enum實例:

enum_instance = Company[enum_name]

請參閱enum模塊文檔中對枚舉成員及其屬性的編程訪問

接下來,我們可以將枚舉對象轉換為唯一字符串(對於<option>標簽的value="..."屬性)和標記字符串(向用戶顯示)到枚舉類上的標准鈎子方法,例如__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 s至ValueError秒。 (v, escape(v))對提供每個選項的值和標簽; str(v)用於<option value="...">屬性值,然后通過Company[__html__result]使用相同的字符串強制返回枚舉實例。 WTForms 3.0 將開始使用MarkupSafe作為標簽,但在那之前,我們可以直接使用escape(v)提供相同的功能,而后者又使用__html__來提供合適的渲染。

如果必須記住要在列表coerce_for_enum()放入什么以及使用coerce_for_enum()變得乏味,您可以使用輔助函數生成choicescoerce選項; 你甚至可以讓它驗證有合適的__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 發布,您就可以在 enum 對象上使用__html__方法,而不必使用markdownsafe.escape() ,因為項目正在切換到使用 MarkupSafe 來處理標簽值

我認為您需要將傳遞給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

我剛剛掉進了同一個兔子洞。 不知道為什么,但是在初始化表單時使用None調用coerce 浪費了很多時間后,我認為不值得強制,而是使用了:

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]

coerce參數指向的函數需要將瀏覽器傳遞的字符串( <select> ed <option>的值)轉換為您在choices指定的值的類型:

選擇字段保留一個choices屬性,它是一系列 ( value , label ) 對。 理論上,值部分可以是任何類型,但由於表單數據是由瀏覽器作為字符串發送的,因此您需要提供一個函數來將字符串表示強制回可比較的對象。

https://wtforms.readthedocs.io/en/2.2.1/fields.html#wtforms.fields.SelectField

這樣, 強制提供的值可以與配置的進行比較

由於您已經在使用枚舉項的名稱字符串作為值( choices=[(choice.name, choice.name) for choice in Company] ),因此您無需強制執行。

如果您決定使用integer Enum::value s 作為<option> s 的值,則必須將返回的字符串強制轉換回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和被傳遞到函數中的強制枚舉實例:

鑒於您在Company::__str__返回Company::name並使用EnterMedia作為默認值:

coerce=lambda value: value if isinstance(value, Company) else Company[value or Company.EnterMedia.name]

hth, dtk

這里有一個不同的方法,它只是創建一個新的 WTF EnumField 並對 enum 類型進行一些類操作,以使其與這些函數無縫地可用:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM