在Python的Flask中使用WTForms表單框架的基礎(chǔ)教程
下載和安裝
安裝 WTForms 最簡(jiǎn)單的方式是使用 easy_install 和 pip:
easy_install WTForms # or pip install WTForms
你可以從 PyPI 手動(dòng) 下載 WTForms 然后運(yùn)行 python setup.py install .
如果你是那種喜歡這一切風(fēng)險(xiǎn)的人, 就運(yùn)行來(lái)自 Git 的最新版本, 你能夠獲取最新變更集的 打包版本, 或者前往 項(xiàng)目主頁(yè) 克隆代碼倉(cāng)庫(kù).
主要概念
Forms 類是 WTForms 的核心容器. 表單(Forms)表示域(Fields)的集合, 域能通過表單的字典形式或者屬性形式訪問.
Fields(域)做最繁重的工作. 每個(gè)域(field)代表一個(gè)數(shù)據(jù)類型, 并且域操作強(qiáng)制表單輸入為那個(gè)數(shù)據(jù)類型. 例如, InputRequired 和 StringField 表示兩種不同的數(shù)據(jù)類型. 域除了包含的數(shù)據(jù)(data)之外, 還包含大量有用的屬性, 例如標(biāo)簽、描述、驗(yàn)證錯(cuò)誤的列表.
每個(gè)域(field)擁有一個(gè)Widget(部件)實(shí)例. Widget 的工作是渲染域(field)的HTML表示. 每個(gè)域可以指定Widget實(shí)例, 但每個(gè)域默認(rèn)擁有一個(gè)合理的widget. 有些域是簡(jiǎn)單方便的, 比如 TextAreaField 就僅僅是默認(rèn)部件(widget) 為 TextArea 的
StringField.
為了指定驗(yàn)證規(guī)則, 域包含驗(yàn)證器(Validators)列表.
開始
讓我們直接進(jìn)入正題并定義我們的第一個(gè)表單::
from wtforms import Form, BooleanField, StringField, validators
class RegistrationForm(Form):
username = StringField('Username', [validators.Length(min=4, max=25)])
email = StringField('Email Address', [validators.Length(min=6, max=35)])
accept_rules = BooleanField('I accept the site rules', [validators.InputRequired()])
當(dāng)你創(chuàng)建一個(gè)表單(form), 你定義域(field)的方法類似于很多ORM定義它們的列(columns):通過定義類變量, 即域的實(shí)例.
因?yàn)楸韱问浅R?guī)的 Python 類, 你可以很容易地把它們擴(kuò)展成為你期望的::
class ProfileForm(Form):
birthday = DateTimeField('Your Birthday', format='%m/%d/%y')
signature = TextAreaField('Forum Signature')
class AdminProfileForm(ProfileForm):
username = StringField('Username', [validators.Length(max=40)])
level = IntegerField('User Level', [validators.NumberRange(min=0, max=10)])
通過子類, AdminProfileForm 類獲得了已經(jīng)定義的 ProfileForm 類的所有域. 這允許你輕易地在不同表單之間共享域的共同子集, 例如上面的例子, 我們?cè)黾?admin-only 的域到 ProfileForm.
使用表單
使用表單和實(shí)例化它一樣簡(jiǎn)單. 想想下面這個(gè)django風(fēng)格的視圖函數(shù), 它使用之前定義的 RegistrationForm 類::
def register(request):
form = RegistrationForm(request.POST)
if request.method == 'POST' and form.validate():
user = User()
user.username = form.username.data
user.email = form.email.data
user.save()
redirect('register')
return render_response('register.html', form=form)
首先, 我們實(shí)例化表單, 給它提供一些 request.POST 中可用的數(shù)據(jù). 然后我們檢查請(qǐng)求(request)是不是使用 POST 方式, 如果它是, 我們就驗(yàn)證表單, 并檢查用戶遵守這些規(guī)則. 如果成功了, 我們創(chuàng)建新的 User 模型, 并從已驗(yàn)證的表單分派數(shù)據(jù)給它, 最后保存它.
編輯現(xiàn)存對(duì)象
我們之前的注冊(cè)例子展示了如何為新條目接收輸入并驗(yàn)證, 只是如果我們想要編輯現(xiàn)有對(duì)象怎么辦?很簡(jiǎn)單::
def edit_profile(request):
user = request.current_user
form = ProfileForm(request.POST, user)
if request.method == 'POST' and form.validate():
form.populate_obj(user)
user.save()
redirect('edit_profile')
return render_response('edit_profile.html', form=form)
這里, 我們通過給表單同時(shí)提供 request.POST 和用戶(user)對(duì)象來(lái)實(shí)例化表單. 通過這樣做, 表單會(huì)從 user 對(duì)象得到在未在提交數(shù)據(jù)中出現(xiàn)的任何數(shù)據(jù).
我們也使用表單的populate_obj方法來(lái)重新填充用戶對(duì)象, 用已驗(yàn)證表單的內(nèi)容. 這個(gè)方法提供便利, 用于當(dāng)域(field)名稱和你提供數(shù)據(jù)的對(duì)象的名稱匹配時(shí). 通常的, 你會(huì)想要手動(dòng)分配值, 但對(duì)于這個(gè)簡(jiǎn)單例子, 它是最好的. 它也可以用于CURD和管理(admin)表單.
在控制臺(tái)中探索
WTForms 表單是非常簡(jiǎn)單的容器對(duì)象, 也許找出表單中什么對(duì)你有用的最簡(jiǎn)單的方法就是在控制臺(tái)中玩弄表單:
>>> from wtforms import Form, StringField, validators
>>> class UsernameForm(Form):
... username = StringField('Username', [validators.Length(min=5)], default=u'test')
...
>>> form = UsernameForm()
>>> form['username']
<wtforms.fields.StringField object at 0x827eccc>
>>> form.username.data
u'test'
>>> form.validate()
False
>>> form.errors
{'username': [u'Field must be at least 5 characters long.']}
我們看到的是當(dāng)你實(shí)例化一個(gè)表單的時(shí)候, 表單包含所有域的實(shí)例, 訪問域可以通過字典形式或者屬性形式. 這些域擁有它們自己的屬性, 就和封閉的表單一樣.
當(dāng)我們驗(yàn)證表單, 它返回邏輯假, 意味著至少一個(gè)驗(yàn)證規(guī)則不滿足. form.errors 會(huì)給你一個(gè)所有錯(cuò)誤的概要.
>>> form2 = UsernameForm(username=u'Robert')
>>> form2.data
{'username': u'Robert'}
>>> form2.validate()
True
這次, 我們實(shí)例化 UserForm 時(shí)給 username 傳送一個(gè)新值, 驗(yàn)證表單是足夠了.
表單如何獲取數(shù)據(jù)
除了使用前兩個(gè)參數(shù)(formdata和obj)提供數(shù)據(jù)之外, 你可以傳送關(guān)鍵詞參數(shù)來(lái)填充表單. 請(qǐng)注意一些參數(shù)名是被保留的: formdata, obj, prefix.
formdata比obj優(yōu)先級(jí)高, obj比關(guān)鍵詞參數(shù)優(yōu)先級(jí)高. 例如:
def change_username(request):
user = request.current_user
form = ChangeUsernameForm(request.POST, user, username='silly')
if request.method == 'POST' and form.validate():
user.username = form.username.data
user.save()
return redirect('change_username')
return render_response('change_username.html', form=form)
雖然你在實(shí)踐中幾乎從未一起使用所有3種方式, 舉例說(shuō)明WTForms是如何查找 username 域:
如果表單被提交(request.POST非空), 則處理表單輸入. 實(shí)踐中, 即使這個(gè)域沒有 表單輸入, 而如果存在任何種類的表單輸入, 那么我們會(huì)處理表單輸入.
如果沒有表單輸入, 則按下面的順序嘗試:
- 檢查 user 是否有一個(gè)名為 username 的屬性.
- 檢查是否提供一個(gè)名為 username 的關(guān)鍵詞參數(shù).
- 最后, 如果都失敗了, 使用域的默認(rèn)值, 如果有的話.
驗(yàn)證器
WTForms中的驗(yàn)證器(Validators)為域(field)提供一套驗(yàn)證器, 當(dāng)包含域的表單進(jìn)行驗(yàn)證時(shí)運(yùn)行. 你提供驗(yàn)證器可通過域構(gòu)造函數(shù)的第二個(gè)參數(shù)validators:
class ChangeEmailForm(Form):
email = StringField('Email', [validators.Length(min=6, max=120), validators.Email()])
你可以為一個(gè)域提供任意數(shù)量的驗(yàn)證器. 通常, 你會(huì)想要提供一個(gè)定制的錯(cuò)誤消息:
class ChangeEmailForm(Form):
email = StringField('Email', [
validators.Length(min=6, message=_(u'Little short for an email address?')),
validators.Email(message=_(u'That\'s not a valid email address.'))
])
這通常更好地提供你自己的消息, 作為必要的默認(rèn)消息是通用的. 這也是提供本地化錯(cuò)誤消息的方法.
對(duì)于內(nèi)置的驗(yàn)證器的列表, 查閱 Validators.
渲染域
渲染域和強(qiáng)制它為字符串一樣簡(jiǎn)單:
>>> from wtforms import Form, StringField
>>> class SimpleForm(Form):
... content = StringField('content')
...
>>> form = SimpleForm(content='foobar')
>>> str(form.content)
'<input id="content" name="content" type="text" value="foobar" />'
>>> unicode(form.content)
u'<input id="content" name="content" type="text" value="foobar" />'
然而, 渲染域的真正力量來(lái)自于它的 __call__() 方法. 調(diào)用(calling)域, 你可以提供關(guān)鍵詞參數(shù), 它們會(huì)在輸出中作為HTML屬性注入.
>>> form.content(style="width: 200px;", class_="bar") u'<input class="bar" id="content" name="content" style="width: 200px;" type="text" value="foobar" />'
現(xiàn)在, 讓我們應(yīng)用這個(gè)力量在 Jinja 模板中渲染表單. 首先, 我們的表單:
class LoginForm(Form):
username = StringField('Username')
password = PasswordField('Password')
form = LoginForm()
然后是模板文件:
<form method="POST" action="/login">
<div>{{ form.username.label }}: {{ form.username(class="css_class") }}</div>
<div>{{ form.password.label }}: {{ form.password() }}</div>
</form>
相同的, 如果你使用 Django 模板, 當(dāng)你想要傳送關(guān)鍵詞參數(shù)時(shí), 你可以使用我們?cè)贒jango擴(kuò)展中提供的模板標(biāo)簽form_field:
{% load wtforms %}
<form method="POST" action="/login">
<div>
{{ form.username.label }}:
{% form_field form.username class="css_class" %}
</div>
<div>
{{ form.password.label }}:
{{ form.password }}
</div>
</form>
這兩個(gè)將會(huì)輸出:
<form method="POST" action="/login"> <div> <label for="username">Username</label>: <input class="css_class" id="username" name="username" type="text" value="" /> </div> <div> <label for="password">Password</label>: <input id="password" name="password" type="password" value="" /> </div> </form>
WTForms是模板引擎不可知的, 同時(shí)會(huì)和任何允許屬性存取、字符串強(qiáng)制(string coercion)、函數(shù)調(diào)用的引擎共事. 在 Django 模板中, 當(dāng)你不能傳送參數(shù)時(shí), 模板標(biāo)簽 form_field 提供便利.
顯示錯(cuò)誤消息
現(xiàn)在我們的表單擁有一個(gè)模板, 讓我們?cè)黾渝e(cuò)誤消息::
<form method="POST" action="/login">
<div>{{ form.username.label }}: {{ form.username(class="css_class") }}</div>
{% if form.username.errors %}
<ul class="errors">{% for error in form.username.errors %}<li>{{ error }}</li>{% endfor %}</ul>
{% endif %}
<div>{{ form.password.label }}: {{ form.password() }}</div>
{% if form.password.errors %}
<ul class="errors">{% for error in form.password.errors %}<li>{{ error }}</li>{% endfor %}</ul>
{% endif %}
</form>
如果你喜歡在頂部顯示大串的錯(cuò)誤消息, 也很簡(jiǎn)單:
{% if form.errors %}
<ul class="errors">
{% for field_name, field_errors in form.errors|dictsort if field_errors %}
{% for error in field_errors %}
<li>{{ form[field_name].label }}: {{ error }}</li>
{% endfor %}
{% endfor %}
</ul>
{% endif %}
由于錯(cuò)誤處理會(huì)變成相當(dāng)冗長(zhǎng)的事情, 在你的模板中使用 Jinja 宏(macros, 或者相同意義的) 來(lái)減少引用是更好的. (例子)
定制驗(yàn)證器
這有兩種方式定制的驗(yàn)證器. 通過定義一個(gè)定制的驗(yàn)證器并在域中使用它:
from wtforms.validators import ValidationError
def is_42(form, field):
if field.data != 42:
raise ValidationError('Must be 42')
class FourtyTwoForm(Form):
num = IntegerField('Number', [is_42])
或者通過提供一個(gè)在表單內(nèi)的特定域(in-form field-specific)的驗(yàn)證器:
class FourtyTwoForm(Form):
num = IntegerField('Number')
def validate_num(form, field):
if field.data != 42:
raise ValidationError(u'Must be 42')
編寫WTForm擴(kuò)展示例
class TagListField(Field):
widget = TextInput()
def _value(self):
if self.data:
return u', '.join(self.data)
else:
return u''
def process_formdata(self, valuelist):
if valuelist:
self.data = [x.strip() for x in valuelist[0].split(',')]
else:
self.data = []
根據(jù)上面的代碼,將TagListField中的字符串轉(zhuǎn)為models.py中定義的Tag對(duì)象即可:
class TagListField(Field):
widget = TextInput()
def __init__(self, label=None, validators=None,
**kwargs):
super(TagListField, self).__init__(label, validators, **kwargs)
def _value(self):
if self.data:
r = u''
for obj in self.data:
r += self.obj_to_str(obj)
return u''
else:
return u''
def process_formdata(self, valuelist):
print 'process_formdata..'
print valuelist
if valuelist:
tags = self._remove_duplicates([x.strip() for x in valuelist[0].split(',')])
self.data = [self.str_to_obj(tag) for tag in tags]
else:
self.data = None
def pre_validate(self, form):
pass
@classmethod
def _remove_duplicates(cls, seq):
"""去重"""
d = {}
for item in seq:
if item.lower() not in d:
d[item.lower()] = True
yield item
@classmethod
def str_to_obj(cls, tag):
"""將字符串轉(zhuǎn)換位obj對(duì)象"""
tag_obj = Tag.query.filter_by(name=tag).first()
if tag_obj is None:
tag_obj = Tag(name=tag)
return tag_obj
@classmethod
def obj_to_str(cls, obj):
"""將對(duì)象轉(zhuǎn)換為字符串"""
if obj:
return obj.name
else:
return u''
class TagListField(Field):
widget = TextInput()
def __init__(self, label=None, validators=None,
**kwargs):
super(TagListField, self).__init__(label, validators, **kwargs)
def _value(self):
if self.data:
r = u''
for obj in self.data:
r += self.obj_to_str(obj)
return u''
else:
return u''
def process_formdata(self, valuelist):
print 'process_formdata..'
print valuelist
if valuelist:
tags = self._remove_duplicates([x.strip() for x in valuelist[0].split(',')])
self.data = [self.str_to_obj(tag) for tag in tags]
else:
self.data = None
def pre_validate(self, form):
pass
@classmethod
def _remove_duplicates(cls, seq):
"""去重"""
d = {}
for item in seq:
if item.lower() not in d:
d[item.lower()] = True
yield item
@classmethod
def str_to_obj(cls, tag):
"""將字符串轉(zhuǎn)換位obj對(duì)象"""
tag_obj = Tag.query.filter_by(name=tag).first()
if tag_obj is None:
tag_obj = Tag(name=tag)
return tag_obj
@classmethod
def obj_to_str(cls, obj):
"""將對(duì)象轉(zhuǎn)換為字符串"""
if obj:
return obj.name
else:
return u''
主要就是在process_formdata這一步處理表單的數(shù)據(jù),將字符串轉(zhuǎn)換為需要的數(shù)據(jù)。最終就可以在forms.py中這樣定義表單了:
...
class ArticleForm(Form):
"""編輯文章表單"""
title = StringField(u'標(biāo)題', validators=[Required()])
category = QuerySelectField(u'分類', query_factory=get_category_factory(['id', 'name']), get_label='name')
tags = TagListField(u'標(biāo)簽', validators=[Required()])
content = PageDownField(u'正文', validators=[Required()])
submit = SubmitField(u'發(fā)布')
...
...
class ArticleForm(Form):
"""編輯文章表單"""
title = StringField(u'標(biāo)題', validators=[Required()])
category = QuerySelectField(u'分類', query_factory=get_category_factory(['id', 'name']), get_label='name')
tags = TagListField(u'標(biāo)簽', validators=[Required()])
content = PageDownField(u'正文', validators=[Required()])
submit = SubmitField(u'發(fā)布')
...
在views.py中處理表單就很方便了:
def edit_article():
"""編輯文章"""
form = ArticleForm()
if form.validate_on_submit():
article = Article(title=form.title.data, content=form.content.data)
article.tags = form.tags.data
article.category = form.category.data
try:
db.session.add(article)
db.session.commit()
except:
db.session.rollback()
return render_template('dashboard/edit.html', form=form)
def edit_article():
"""編輯文章"""
form = ArticleForm()
if form.validate_on_submit():
article = Article(title=form.title.data, content=form.content.data)
article.tags = form.tags.data
article.category = form.category.data
try:
db.session.add(article)
db.session.commit()
except:
db.session.rollback()
return render_template('dashboard/edit.html', form=form)
代碼是不是很簡(jiǎn)潔了?^_^。。。
當(dāng)然了寫一個(gè)完整的WTForms擴(kuò)展還是很麻煩的。這里只是剛剛?cè)腴T。可以看官方擴(kuò)展QuerySelectField的源碼。。。
效果:

相關(guān)文章
Python中字典和集合學(xué)習(xí)小結(jié)
本文通過實(shí)例給大家介紹了python中字典和集合的知識(shí)小結(jié),非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-07-07
python?sklearn?畫出決策樹并保存為PDF的實(shí)現(xiàn)過程
這篇文章主要介紹了python?sklearn?畫出決策樹并保存為PDF的實(shí)現(xiàn)過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
python實(shí)現(xiàn)的分析并統(tǒng)計(jì)nginx日志數(shù)據(jù)功能示例
這篇文章主要介紹了python實(shí)現(xiàn)的分析并統(tǒng)計(jì)nginx日志數(shù)據(jù)功能,結(jié)合實(shí)例形式分析了Python針對(duì)nginx日志ip、訪問url、狀態(tài)等數(shù)據(jù)的相關(guān)讀取、解析操作技巧,需要的朋友可以參考下2019-12-12

