下載和安裝
安裝 WTForms 最簡單的方式是使用 easy_install 和 pip:
easy_install WTForms# orpip install WTForms
你可以從 PyPI 手動 下載 WTForms 然後運行 python setup.py install .
如果你是那種喜歡這一切風險的人, 就運行來自 Git 的最新版本, 你能夠擷取最新變更集的 打包版本, 或者前往 項目首頁 複製代碼倉庫.
主要概念
Forms 類是 WTForms 的核心容器. 表單(Forms)表示域(Fields)的集合, 域能通過表單的字典形式或者屬性形式訪問.
Fields(域)做最繁重的工作. 每個域(field)代表一個資料類型, 並且網域作業強製表單輸入為那個資料類型. 例如, InputRequired 和 StringField 表示兩種不同的資料類型. 域除了包含的資料(data)之外, 還包含大量有用的屬性, 例如標籤、描述、驗證錯誤的列表.
每個域(field)擁有一個Widget(組件)執行個體. Widget 的工作是渲染域(field)的HTML表示. 每個域可以指定Widget執行個體, 但每個域預設擁有一個合理的widget. 有些域是簡單方便的, 比如 TextAreaField 就僅僅是預設組件(widget) 為 TextArea 的
StringField.
為了指定驗證規則, 域包含驗證器(Validators)列表.
開始
讓我們直接進入正題並定義我們的第一個表單::
from wtforms import Form, BooleanField, StringField, validatorsclass 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()])
當你建立一個表單(form), 你定義域(field)的方法類似於很多ORM定義它們的列(columns):通過定義類變數, 即域的執行個體.
因為表單是常規的 Python 類, 你可以很容易地把它們擴充成為你期望的::
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 類獲得了已經定義的 ProfileForm 類的所有域. 這允許你輕易地在不同表單之間共用域的共同子集, 例如上面的例子, 我們增加 admin-only 的域到 ProfileForm.
使用表單
使用表單和執行個體化它一樣簡單. 想想下面這個django風格的視圖函數, 它使用之前定義的 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)
首先, 我們執行個體化表單, 給它提供一些 request.POST 中可用的資料. 然後我們檢查請求(request)是不是使用 POST 方式, 如果它是, 我們就驗證表單, 並檢查使用者遵守這些規則. 如果成功了, 我們建立新的 User 模型, 並從已驗證的表單指派資料給它, 最後儲存它.
編輯現存對象
我們之前的註冊例子展示了如何為新條目接收輸入並驗證, 只是如果我們想要編輯現有對象怎麼辦?很簡單::
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)
這裡, 我們通過給表單同時提供 request.POST 和使用者(user)對象來執行個體化表單. 通過這樣做, 表單會從 user 對象得到在未在提交資料中出現的任何資料.
我們也使用表單的populate_obj方法來重新填充使用者物件, 用已驗證表單的內容. 這個方法提供便利, 用於當域(field)名稱和你提供資料的對象的名稱匹配時. 通常的, 你會想要手動分配值, 但對於這個簡單例子, 它是最好的. 它也可以用於CURD和管理(admin)表單.
在控制台中探索
WTForms 表單是非常簡單的容器物件, 也許找出表單中什麼對你有用的最簡單的方法就是在控制台中玩弄表單:
>>> 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.datau'test'>>> form.validate()False>>> form.errors{'username': [u'Field must be at least 5 characters long.']}
我們看到的是當你執行個體化一個表單的時候, 表單包含所有域的執行個體, 訪問域可以通過字典形式或者屬性形式. 這些域擁有它們自己的屬性, 就和封閉的表單一樣.
當我們驗證表單, 它返回邏輯假, 意味著至少一個驗證規則不滿足. form.errors 會給你一個所有錯誤的概要.
>>> form2 = UsernameForm(username=u'Robert')>>> form2.data{'username': u'Robert'}>>> form2.validate()True
這次, 我們執行個體化 UserForm 時給 username 傳送一個新值, 驗證表單是足夠了.
表單如何擷取資料
除了使用前兩個參數(formdata和obj)提供資料之外, 你可以傳送關鍵詞參數來填充表單. 請注意一些參數名是被保留的: formdata, obj, prefix.
formdata比obj優先順序高, obj比關鍵詞參數優先順序高. 例如:
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)
雖然你在實踐中幾乎從未一起使用所有3種方式, 舉例說明WTForms是如何尋找 username 域:
如果表單被提交(request.POST非空), 則處理表單輸入. 實踐中, 即使這個域沒有 表單輸入, 而如果存在任何種類的表單輸入, 那麼我們會處理表單輸入.
如果沒有表單輸入, 則按下面的順序嘗試:
檢查 user 是否有一個名為 username 的屬性.
檢查是否提供一個名為 username 的關鍵詞參數.
最後, 如果都失敗了, 使用域的預設值, 如果有的話.
驗證器
WTForms中的驗證器(Validators)為域(field)提供一套驗證器, 當包含域的表單進行驗證時運行. 你提供驗證器可通過域建構函式的第二個參數validators:
class ChangeEmailForm(Form): email = StringField('Email', [validators.Length(min=6, max=120), validators.Email()])
你可以為一個域提供任意數量的驗證器. 通常, 你會想要提供一個定製的錯誤訊息:
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.')) ])
這通常更好地提供你自己的訊息, 作為必要的預設訊息是通用的. 這也是提供本地化錯誤訊息的方法.
對於內建的驗證器的列表, 查閱 Validators.
渲染域
渲染域和強制它為字串一樣簡單:
>>> 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" />'
然而, 渲染域的真正力量來自於它的 __call__() 方法. 調用(calling)域, 你可以提供關鍵詞參數, 它們會在輸出中作為HTML 屬性注入.
>>> form.content(style="width: 200px;", class_="bar")u'<input class="bar" id="content" name="content" style="width: 200px;" type="text" value="foobar" />'
現在, 讓我們應用這個力量在 Jinja 模板中渲染表單. 首先, 我們的表單:
class LoginForm(Form): username = StringField('Username') password = PasswordField('Password')form = LoginForm()
然後是模板檔案:
<form method="POST" action="/login"> <p>{{ form.username.label }}: {{ form.username(class="css_class") }}</p> <p>{{ form.password.label }}: {{ form.password() }}</p></form>
相同的, 如果你使用 Django 模板, 當你想要傳送關鍵詞參數時, 你可以使用我們在Django擴充中提供的模板標籤form_field:
{% load wtforms %}<form method="POST" action="/login"> <p> {{ form.username.label }}: {% form_field form.username class="css_class" %} </p> <p> {{ form.password.label }}: {{ form.password }} </p></form>
這兩個將會輸出:
<form method="POST" action="/login"> <p> <label for="username">Username</label>: <input class="css_class" id="username" name="username" type="text" value="" /> </p> <p> <label for="password">Password</label>: <input id="password" name="password" type="password" value="" /> </p></form>
WTForms是模板引擎不可知的, 同時會和任何允許屬性存取、字串強制(string coercion)、函數調用的引擎共事. 在 Django 模板中, 當你不能傳送參數時, 模板標籤 form_field 提供便利.
顯示錯誤訊息
現在我們的表單擁有一個模板, 讓我們增加錯誤訊息::
<form method="POST" action="/login"> <p>{{ form.username.label }}: {{ form.username(class="css_class") }}</p> {% if form.username.errors %} <ul class="errors">{% for error in form.username.errors %}<li>{{ error }}</li>{% endfor %}</ul> {% endif %} <p>{{ form.password.label }}: {{ form.password() }}</p> {% if form.password.errors %} <ul class="errors">{% for error in form.password.errors %}<li>{{ error }}</li>{% endfor %}</ul> {% endif %}</form>
如果你喜歡在頂部顯示大串的錯誤訊息, 也很簡單:
{% 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 %}
由於錯誤處理會變成相當冗長的事情, 在你的模板中使用 Jinja 宏(macros, 或者相同意義的) 來減少引用是更好的. (例子)
定製驗證器
這有兩種方式定製的驗證器. 通過定義一個定製的驗證器並在域中使用它:
from wtforms.validators import ValidationErrordef is_42(form, field): if field.data != 42: raise ValidationError('Must be 42')class FourtyTwoForm(Form): num = IntegerField('Number', [is_42])
或者通過提供一個在表單內的特定域(in-form field-specific)的驗證器:
class FourtyTwoForm(Form): num = IntegerField('Number') def validate_num(form, field): if field.data != 42: raise ValidationError(u'Must be 42')
編寫WTForm擴充樣本
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 = []
根據上面的代碼,將TagListField中的字串轉為models.py中定義的Tag對象即可:
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): """將字串轉換位obj對象""" 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): """將對象轉換為字串""" 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): """將字串轉換位obj對象""" 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): """將對象轉換為字串""" if obj: return obj.name else: return u''
主要就是在process_formdata這一步處理表單的資料,將字串轉換為需要的資料。最終就可以在forms.py中這樣定義表單了:
...class ArticleForm(Form): """編輯文章表單""" title = StringField(u'標題', validators=[Required()]) category = QuerySelectField(u'分類', query_factory=get_category_factory(['id', 'name']), get_label='name') tags = TagListField(u'標籤', validators=[Required()]) content = PageDownField(u'本文', validators=[Required()]) submit = SubmitField(u'發布')......class ArticleForm(Form): """編輯文章表單""" title = StringField(u'標題', validators=[Required()]) category = QuerySelectField(u'分類', query_factory=get_category_factory(['id', 'name']), get_label='name') tags = TagListField(u'標籤', validators=[Required()]) content = PageDownField(u'本文', validators=[Required()]) submit = SubmitField(u'發布')...在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)
代碼是不是很簡潔了?^_^。。。
當然了寫一個完整的WTForms擴充還是很麻煩的。這裡只是剛剛入門。可以看官方擴充QuerySelectField的源碼。。。
效果: