[英]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
必須否則引發ValueError
或TypeError
。
您想使用枚舉名稱作為選擇框中的值; 這些總是字符串。 如果您的枚舉值適合作為標簽,那就太好了,您可以將它們用於選項可讀文本,但不要將它們與選項值混淆,選項值必須是唯一的,枚舉值不需要是唯一的。
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()
變得乏味,您可以使用輔助函數生成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 發布,您就可以在 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.