首頁 後端開發 Python教學 Python的Flask中使用WTForms表單框架

Python的Flask中使用WTForms表單框架

Mar 03, 2017 pm 02:17 PM

下載和安裝

安裝WTForms 最簡單的方式是使用easy_install 和pip:

easy_install WTForms
# or
pip 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, 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()])
登入後複製

當你建立一個表單(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.data
u&#39;test&#39;
>>> form.validate()
False
>>> form.errors
{&#39;username&#39;: [u&#39;Field must be at least 5 characters long.&#39;]}
登入後複製

我們看到的是當你實例化一個表單的時候, 表單包含所有域的實例, 存取域可以透過字典形式或屬性形式. 這些域擁有它們自己的屬性, 就和封閉的表單一樣.

當我們驗證表單, 它返回邏輯假, 意味著至少一個驗證規則不滿足. form.errors 會給你一個所有錯誤的概要.

>>> form2 = UsernameForm(username=u&#39;Robert&#39;)
>>> form2.data
{&#39;username&#39;: u&#39;Robert&#39;}
>>> 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=&#39;silly&#39;)
 if request.method == &#39;POST&#39; and form.validate():
  user.username = form.username.data
  user.save()
  return redirect(&#39;change_username&#39;)
 return render_response(&#39;change_username.html&#39;, form=form)
登入後複製

#雖然你在實踐中幾乎從未一起使用所有3種方式, 舉例說明WTForms是如何查找username 域:

如果表單被提交(request.POST非空), 則處理表單輸入. 實踐中,即使這個域沒有表單輸入, 而如果存在任何種類的表單輸入, 那麼我們會處理表單輸入.
如果沒有表單輸入, 則按下面的順序嘗試:

  • 檢查user 是否有一個名為username 的屬性.

  • ##檢查是否提供一個名為username 的關鍵字參數.

  • : 如果都失敗了, 使用域的預設值, 如果有的話.

#驗證器

WTForms中的验证器(Validators)为域(field)提供一套验证器, 当包含域的表单进行验证时运行. 你提供验证器可通过域构造函数的第二个参数validators:

class ChangeEmailForm(Form):
 email = StringField(&#39;Email&#39;, [validators.Length(min=6, max=120), validators.Email()])
登入後複製

你可以为一个域提供任意数量的验证器. 通常, 你会想要提供一个定制的错误消息:

class ChangeEmailForm(Form):
 email = StringField(&#39;Email&#39;, [
  validators.Length(min=6, message=_(u&#39;Little short for an email address?&#39;)),
  validators.Email(message=_(u&#39;That\&#39;s not a valid email address.&#39;))
 ])
登入後複製

这通常更好地提供你自己的消息, 作为必要的默认消息是通用的. 这也是提供本地化错误消息的方法.

对于内置的验证器的列表, 查阅 Validators.

渲染域
渲染域和强制它为字符串一样简单:

>>> from wtforms import Form, StringField
>>> class SimpleForm(Form):
... content = StringField(&#39;content&#39;)
...
>>> form = SimpleForm(content=&#39;foobar&#39;)
>>> str(form.content)
&#39;<input id="content" name="content" type="text" value="foobar" />&#39;
>>> unicode(form.content)
u&#39;<input id="content" name="content" type="text" value="foobar" />&#39;
登入後複製

然而, 渲染域的真正力量来自于它的 __call__() 方法. 调用(calling)域, 你可以提供关键词参数, 它们会在输出中作为HTML属性注入.

>>> form.content(style="width: 200px;", class_="bar")
u&#39;<input class="bar" id="content" name="content" style="width: 200px;" type="text" value="foobar" />&#39;
登入後複製

现在, 让我们应用这个力量在 Jinja 模板中渲染表单. 首先, 我们的表单:

class LoginForm(Form):
 username = StringField(&#39;Username&#39;)
 password = PasswordField(&#39;Password&#39;)

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 ValidationError

def is_42(form, field):
 if field.data != 42:
  raise ValidationError(&#39;Must be 42&#39;)

class FourtyTwoForm(Form):
 num = IntegerField(&#39;Number&#39;, [is_42])
登入後複製

或者通过提供一个在表单内的特定域(in-form field-specific)的验证器:

class FourtyTwoForm(Form):
 num = IntegerField(&#39;Number&#39;)

 def validate_num(form, field):
  if field.data != 42:
   raise ValidationError(u&#39;Must be 42&#39;)
登入後複製

编写WTForm扩展示例

class TagListField(Field):
 widget = TextInput()

 def _value(self):
  if self.data:
   return u&#39;, &#39;.join(self.data)
  else:
   return u&#39;&#39;

 def process_formdata(self, valuelist):
  if valuelist:
   self.data = [x.strip() for x in valuelist[0].split(&#39;,&#39;)]
  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&#39;&#39;
   for obj in self.data:
    r += self.obj_to_str(obj)
   return u&#39;&#39;
  else:
   return u&#39;&#39;

 def process_formdata(self, valuelist):
  print &#39;process_formdata..&#39;
  print valuelist
  if valuelist:
   tags = self._remove_duplicates([x.strip() for x in valuelist[0].split(&#39;,&#39;)])
   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&#39;&#39;

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&#39;&#39;
   for obj in self.data:
    r += self.obj_to_str(obj)
   return u&#39;&#39;
  else:
   return u&#39;&#39;
 
 def process_formdata(self, valuelist):
  print &#39;process_formdata..&#39;
  print valuelist
  if valuelist:
   tags = self._remove_duplicates([x.strip() for x in valuelist[0].split(&#39;,&#39;)])
   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&#39;&#39;
登入後複製

主要就是在process_formdata这一步处理表单的数据,将字符串转换为需要的数据。最终就可以在forms.py中这样定义表单了:

...
class ArticleForm(Form):
 """编辑文章表单"""

 title = StringField(u&#39;标题&#39;, validators=[Required()])
 category = QuerySelectField(u&#39;分类&#39;, query_factory=get_category_factory([&#39;id&#39;, &#39;name&#39;]), get_label=&#39;name&#39;)
 tags = TagListField(u&#39;标签&#39;, validators=[Required()])
 content = PageDownField(u&#39;正文&#39;, validators=[Required()])
 submit = SubmitField(u&#39;发布&#39;)
...

...
class ArticleForm(Form):
 """编辑文章表单"""
 
 title = StringField(u&#39;标题&#39;, validators=[Required()])
 category = QuerySelectField(u&#39;分类&#39;, query_factory=get_category_factory([&#39;id&#39;, &#39;name&#39;]), get_label=&#39;name&#39;)
 tags = TagListField(u&#39;标签&#39;, validators=[Required()])
 content = PageDownField(u&#39;正文&#39;, validators=[Required()])
 submit = SubmitField(u&#39;发布&#39;)
...
在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(&#39;dashboard/edit.html&#39;, 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(&#39;dashboard/edit.html&#39;, form=form)
登入後複製

代码是不是很简洁了?^_^。。。

当然了写一个完整的WTForms扩展还是很麻烦的。这里只是刚刚入门。可以看官方扩展QuerySelectField的源码。。。
效果:

Python的Flask中使用WTForms表單框架

更多Python的Flask中使用WTForms表單框架相关文章请关注PHP中文网!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何解決Linux終端中查看Python版本時遇到的權限問題? 如何解決Linux終端中查看Python版本時遇到的權限問題? Apr 01, 2025 pm 05:09 PM

Linux終端中查看Python版本時遇到權限問題的解決方法當你在Linux終端中嘗試查看Python的版本時,輸入python...

在Python中如何高效地將一個DataFrame的整列複製到另一個結構不同的DataFrame中? 在Python中如何高效地將一個DataFrame的整列複製到另一個結構不同的DataFrame中? Apr 01, 2025 pm 11:15 PM

在使用Python的pandas庫時,如何在兩個結構不同的DataFrame之間進行整列複製是一個常見的問題。假設我們有兩個Dat...

如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎? 如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎? Apr 02, 2025 am 07:18 AM

如何在10小時內教計算機小白編程基礎?如果你只有10個小時來教計算機小白一些編程知識,你會選擇教些什麼�...

如何在使用 Fiddler Everywhere 進行中間人讀取時避免被瀏覽器檢測到? 如何在使用 Fiddler Everywhere 進行中間人讀取時避免被瀏覽器檢測到? Apr 02, 2025 am 07:15 AM

使用FiddlerEverywhere進行中間人讀取時如何避免被檢測到當你使用FiddlerEverywhere...

Uvicorn是如何在沒有serve_forever()的情況下持續監聽HTTP請求的? Uvicorn是如何在沒有serve_forever()的情況下持續監聽HTTP請求的? Apr 01, 2025 pm 10:51 PM

Uvicorn是如何持續監聽HTTP請求的? Uvicorn是一個基於ASGI的輕量級Web服務器,其核心功能之一便是監聽HTTP請求並進�...

Python中如何通過字符串動態創建對象並調用其方法? Python中如何通過字符串動態創建對象並調用其方法? Apr 01, 2025 pm 11:18 PM

在Python中,如何通過字符串動態創建對象並調用其方法?這是一個常見的編程需求,尤其在需要根據配置或運行...

哪些流行的Python庫及其用途? 哪些流行的Python庫及其用途? Mar 21, 2025 pm 06:46 PM

本文討論了諸如Numpy,Pandas,Matplotlib,Scikit-Learn,Tensorflow,Tensorflow,Django,Blask和請求等流行的Python庫,並詳細介紹了它們在科學計算,數據分析,可視化,機器學習,網絡開發和H中的用途

什麼是正則表達式? 什麼是正則表達式? Mar 20, 2025 pm 06:25 PM

正則表達式是在編程中進行模式匹配和文本操作的強大工具,從而提高了各種應用程序的文本處理效率。

See all articles