web.py開發web 第九章 Formalchemy 客戶端驗證

    上一章講了使用formalchemy進行服務端驗證,但要開發一個用戶體驗良好的網站,客戶端驗證是必不可少的,用戶能夠在填寫的時候就獲取到錯誤信息,沒必要等待提交的過程,大大節省了用戶的時間。 javascript

    Formalchemy進行客戶端驗證,其實就是使用前端的js插件定義驗證方法,而後在formalchemy中爲字段賦予特殊的屬性,讓驗證插件從屬性瞭解某個字段須要進行什麼驗證,例如,咱們在User表中定義email字段必須爲email形式數據,那麼formalchemy會在輸出的表單中設置email字段的type爲email,前臺使用的插件能識別type="email"的input,而後進行email格式驗證。 css

    這裏介紹一個我經常使用的jquery驗證插件validation,這個插件自己已內置了不少經常使用的驗證方法,若是有特殊的驗證需求,還能夠進行自定義,有興趣的同窗能夠谷歌下。下面咱們就以jquery的validation爲驗證插件,結合formalchemy來介紹下如何進行客戶端驗證。 html

    首先咱們下載validation,放入static中,並在模板中引用,form.html模板修改以下
form.html 前端

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <link type="text/css" href="/static/css/bootstrap.min.css" rel="stylesheet">
    <script type="text/javascript" src="/static/js/jquery.min.js"></script>
    <script type="text/javascript" src="/static/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="/static/js/jquery.metadata.js"></script><!--引入該插件能夠在input中用metadata的形式設置驗證規則-->
    <script type="text/javascript" src="/static/js/jquery.validate.min.js"></script>
    <script type="text/javascript" src="/static/js/additional-methods.min.js"></script><!--validation的內置驗證規則-->
    <script type="text/javascript" src="/static/js/messages_zh.js"></script><!--validation的中文多語言包-->
    <script type="text/javascript" src="/static/js/base.js"></script>
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="span6 offset3">
                <form class="form-horizontal validate" action="" method="post">
                    {{ form.render() }}
                    <div style="text-align: center;">
                        <input type="submit" class="btn btn-large" value="Submit" />
                    </div>
                </form>
            </div>
        </div>
    </div>
</body>
</html>
其中base.js是咱們將要進行驗證設置的js文件,代碼以下
base.js
$(function(){
    //重定義validation的remote驗證方法,讓其傳遞的值字段名所有爲val,並使用post方法
    $.validator.methods.remote = function(value, element, param) {
        if ( this.optional(element) ) {
            return "dependency-mismatch";
        }

        var previous = this.previousValue(element);
        if (!this.settings.messages[element.name] ) {
            this.settings.messages[element.name] = {};
        }
        previous.originalMessage = this.settings.messages[element.name].remote;
        this.settings.messages[element.name].remote = previous.message;

        param = typeof param === "string" && {url:param} || param;

        if ( this.pending[element.name] ) {
            return "pending";
        }
        if ( previous.old === value ) {
            return previous.valid;
        }

        previous.old = value;
        var validator = this;
        this.startRequest(element);
        var data = {};
        data["val"] = value;
        $.ajax($.extend(true, {
            url: param,
            mode: "abort",
            port: "validate" + element.name,
            dataType: "json",
            data: data,
            type: "post",
            success: function(response) {
                validator.settings.messages[element.name].remote = previous.originalMessage;
                var valid = response === true || response === "true";
                if ( valid ) {
                    var submitted = validator.formSubmitted;
                    validator.prepareElement(element);
                    validator.formSubmitted = submitted;
                    validator.successList.push(element);
                    delete validator.invalid[element.name];
                    validator.showErrors();
                } else {
                    var errors = {};
                    var message = response || validator.defaultMessage( element, "remote" );
                    errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
                    validator.invalid[element.name] = true;
                    validator.showErrors(errors);
                }
                previous.valid = valid;
                validator.stopRequest(element, valid);
            }
        }, param));
        return "pending";
    };
    
    var validate_form = $("form.validate");//定義class爲validate的表單須要進行驗證
    validate_form.each(function(){
        $(this).validate({
            errorClass: "help-inline",//設置錯誤信息使用的class,能夠自定義該class的樣式
            errorElement: "span",//設置錯誤信息包括在什麼元素中
            //設置用戶輸入錯誤時事件回調
            errorPlacement: function(error, element){
                element.after(error);
            }
        });
    });
});

    接下來咱們重溫下User表單的驗證需求: java


  1. name字段必填且數據庫內惟一,長度不小於6,不大於20。
  2. email字段必填且數據庫內惟一,且爲email格式,長度不大於32。
  3. password必填,長度不小於6,不大於32。
  4. confirm password必填且必須與password相等,長度不小於6,不大於32。
    根據需求,咱們對name和email的惟一驗證須要使用到遠程驗證,即須要中建立驗證惟一的視圖,因此咱們在main.py建立一個unique的類來實現這個需求,將如下unique類添加進main.py中:


class unique python

class unique(BaseView):
    def __init__(self):
        super(unique, self).__init__()
        #設置白名單,讓須要進行驗證的表和字段才能夠在該視圖中查詢
        self.allow = [
            ("User", "name"),
            ("User", "email"),
        ]

    def POST(self, model, field, action):
        if (model, field) not in self.allow:
            raise web.notfound()
        data = web.input()
        m = globals()[model]
        try:
            id = int(action)
        except:
            query = self.db.query(m).filter(getattr(m, field)==data.val).first()
        else:
            query = self.db.query(m).filter(
                (getattr(m, field)==data.val) &
                (m.id!=id)
            ).first()
        if not query:
            return "true"
        else:
            return "false"
    並在urls改成如下:



urls = (
    "/unique/(.*)/(.*)/(.*)/", "unique",
    "/", "index",
    "/form/", "showform",
)

     最後咱們在forms.py中進行with_html的定義,就能在輸出的表單中設置須要告訴驗證插件的信息了,forms.py修改以下: jquery

forms.py
web

#-*- coding:utf-8 -*-
from formalchemy import config, validators, Field, FieldSet
from customEngine import Jinja2Engine
from models import *
from customValidators import *

class UserForm:
    def __init__(self):
        #這裏的directories是指表單模板存放的地方,咱們在第二章提到的templates下建立一個文件夾,命名爲form
        config.engine = Jinja2Engine(directories=["templates/form"])

    #爲表單設置label
    def setLabel(self):
        self.name = self.fs.name.label("User Name")
        self.email = self.fs.email.label("Email Address")
        self.password = self.fs.password.label("Password")
        self.superuser = self.fs.superuser.label("Admin?")

    #定義編輯模式下通用的設置,編輯模式包括:新增,修改
    def wmode(self, password=None):
        self.setLabel()

        #由於新增和修改中都須要用戶從新確認密碼,因此要爲表單加入Confirm Password
        #若是有指定password的值,說明用戶是在修改記錄,那麼Confirm Password也必須有值
        if not password:
            self.fs.insert_after(self.fs.password, Field("confirm_password"))
        else:
            self.fs.insert_after(self.fs.password, Field("confirm_password", value=password))
        self.confirm_password = self.fs.confirm_password.label("Re-enter Password")

        self.name = self.name.required().validate(
            validators.length(min=6, max=20)
        ).with_html(
            minlength_=6,#定義客戶端驗證限制該字段最小長度爲6
            class_="{messages:{remote:'該用戶名已存在'}}"#定義客戶端惟一驗證錯誤的提示語
        )
        self.email = self.email.required().email().validate(
            validators.email
        ).validate(
            validators.maxlength(32)
        ).with_html(
            class_="{messages:{remote:'該郵箱已存在'}}"#定義客戶端惟一驗證錯誤的提示語
        )
        self.password = self.password.required().password().validate(
            validators.length(min=6, max=32)
        ).with_html(
            minlength_=6,#定義客戶端驗證限制該字段最小長度爲6
        )
        self.confirm_password = self.confirm_password.required().password().validate(
            validators.length(min=6, max=32)
        ).validate(
            customEqual("password", "密碼先後不一致")
        ).with_html(
            minlength_=6,#定義客戶端驗證限制該字段最小長度爲6
        )

    #定義新增用戶時調用的方法
    def write_render(self, cls):
        #設置Fieldset對象,指定要綁定的sqlalchemy中的表類,並賦予sqlalchemy的session
        self.fs = FieldSet(User, session=cls.db)
        self.wmode()

        #配置表單信息
        self.fs.configure(
            #表單包含的字段
            include=[
                self.name.validate(customUnique(cls.db)).with_html(
                    remote_="/unique/User/name/create/",#定義客戶端遠程驗證的地址
                ),
                self.email.validate(customUnique(cls.db)).with_html(
                    remote_="/unique/User/email/create/",#定義客戶端遠程驗證的地址
                ),
                self.password,
                self.confirm_password.with_html(
                    class_="{equalTo:'#User--password'}"#設置客戶端驗證該字段值與另外一個字段值是否相等
                )
            ]
        )
        return self.fs
     修改完成後運行main.py,進入/form/,輸入已經註冊過的name和email,看看客戶端遠程驗證是否實現了:)
相關文章
相關標籤/搜索