2012/05/21

Form validation in python web development

表單驗證

User 要傳資料到 server 不外乎就是透過 HTML 表單搭配 HTTP method GET/POST,為了避免 user 輸入奇怪的資料造成 server 異常,通常我們都會去限定各欄位值域、資料形態、格式...這些東西,如果自己用一堆 if 去寫一定會寫到瘋掉,好在網路上已經有現成的 library 可以用。

表單驗證有可以分別在 client 端或跟 server 端去做:
  • Client 端驗證
    好處是可以減少 server loading,少了傳輸的 latency,反應速度也比較快,但這只能防君子不能防小人,client 端驗證是用 javascript,有心人只要把 script 修改一下就可以避過。
    Library 可以參考:10 Useful jQuery Form Validation Techniques and Tutorials
  • Server 端驗證
    無論 client 端有沒有做,server 端做檢查是必然的事情啦!這也是我今天想介紹的,在 python 上我只用過一套:WTForms,一用就上癮,不只可以做到驗證,表單也可以自動產生。
    如果你 web framework 是用 flask,已經有現成的 extension: Flask-WTF 可以用,我自己是用 tornado,必須要另外疊一個 wrapper,幸好已經有人幫忙寫好了: flying with tornado on appengine



下面以"個人資料更新"為例:

  • custom_form.py - 定義自己的表單
class ProfileUpdateForm(form_wrapper.Form):
    email    = forms.StringField('email', [validators.Email()])
    nickname = forms.StringField('nickname', [validators.Length(min=5, max=20)])
    sex      = forms.RadioField('sex', [validators.Required()], choices=[("0", "male"), ("1", "female")], default=0)
    birthday = forms.DateField ('birthday')
    country  = forms.SelectField('country', choices=[("0", "TW"), ("1", "CN"), ("2", "US")])
    height   = forms.FloatField ('height', [validators.NumberRange(min=90.0, max=200.0)])
    photo    = forms.FileField('photo')

  • general_form.html - 自動產生表單的 template
<form action="{{ request.uri }}" method="post" accept-charset="utf-8" enctype="multipart/form-data">
  {% autoescape None %}
  <table border="0">
    {% for field in form %}
    <tr>
      {% import wtforms as forms %}
      {% if type(field) == forms.RadioField %}
        <td align="right">{{ field.label }}</td>
        <td>
          {% for subfield in field %}
            {{ subfield }}{{ subfield.label }}
          {% end for %}
      {% elif type(field) == forms.SelectField %}
        <td align="right">{{ field.label }}</td>
        <td>
          <select name="{{ field.id }}">
          {% for subfield in field %}
            {{ subfield }}
          {% end for %}
          </select>
      {% else %}
        <td align="right">{{ field.label }}</td><td>{{ field }}</td>
      {% end if %}
    </tr>
 {% end %}
  </table>
  <input type="submit" value="Submit"/>
</form>

  • profile.py - GET request handler
class ProfileUpdateHandler(tornado.web.RequestHandler):
    def get(self):
        form = ProfileUpdateForm()
        self.render("general_form.html", form=form)
自動產生的表單如下:


  • profile.py - POST request handler
class ProfileUpdateHandler(tornado.web.RequestHandler):
    def post(self):
        form = ProfileUpdateForm(self)
        if not form.validate():
            return self.write("form validation fail: %s", form.errors)
在 form.errors 裡面會詳細說明哪個欄位出錯 good!










沒有留言:

張貼留言