首页 > 代码库 > 看用Tornado如何自定义实现表单验证

看用Tornado如何自定义实现表单验证

  我们知道,平时在登陆某个网站或软件时,网站对于你输入的内容是有要求的,并且会对你输入的错误内容有提示,对于Django这种大而全的web框架,是提供了form表单验证功能,但是对于Tornado而言,就没有这功能,所以就需要我们来自己自定义form表单验证,而且这种方法正是Django里的form表单验证的实质内容,也帮我们在后面学习Django理解相关的源码。

 

  写之前,我们必须知道form表单验证的实质是什么?

   实质就是正则匹配

  我们知道用户提交数据是通过post方式提交,所以我们重写post方法,并在post方法进行业务逻辑处理

  • 获取用户提交的数据
  • 将用户提交的数据和正则表达式匹配

 

第一阶段

  • 场景:我们知道后台是一个url对应一个类来处理客户请求的
  • 问题:那么在不同页面里会相同需求,比如登陆时用邮箱登陆,修改密码时又要用邮箱,那对于这样同样验证需求,在我们的后台是不是代码就重复了呢?
  • 解决:独立一个模块专门用于用户信息验证

我们先看一下下面这段代码

class MainForm(object):    def __init__(self):        # 各种信息正则匹配规则        # 并且要求这里字段名和前端传来的name一致        self.host = "(.*)"        self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"        self.port = ‘(\d+)‘        self.phone = ‘^1[3|4|5|8][0-9]\d{8}$‘    def check_valid(self, request):        flag = True        form_dict = self.__dict__  #获取类的普通字段和值        for key, regular in form_dict.items():            post_value = http://www.mamicode.com/request.get_argument(key)  #获取用户输入的值>

 

技术分享

 从上面我们可以知道,这个模块只是简简单单的给了true  or false返回值的问题,我们还希望返回客户输入的值,看下怎么优化吧

  • 在check_vaild方法里定义一个局部变量-字典,然后接收客户的信息,并把这个字典一并返回到post方法里

 

第二阶段

  在上面,我们已经独立了一个模块来验证信息,但问题又来了,上面这个模块我定义的时候以index页面定制的,不同的页面处理的需要是不一样的,那如果解决这个问题,好像可以每个页面都独立一个模块,但这样,代码未免重复的太多了。

  仔细的人会发现,其实就是init里需要匹配的内容不一样,下面的check_vaild方法都是一样的

  • 类的继承,写一个BaseForm父类,让其他验证类继承

class BaseForm:    def check_valid(self, handle):        flag = True        value_dict = {}        for key, regular in self.__dict__.items():            # host,ip port phone            input_value = http://www.mamicode.com/handle.get_argument(key)"(.*)"        self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"        self.port = ‘(\d+)‘        self.phone = ‘^1[3|4|5|8][0-9]\d{8}$‘class HomeForm(BaseForm):    def __init__(self):        self.host = "(.*)"        self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"

   在实际场景中,客户填写的信息有些是必填,有些是可不填,这样又增加了匹配的复杂度,我们可不可以把每种匹配规则独立成一个类,并达到后台传入True时,不为空,传入了False时可为空呢??并且我们让这个类处理时返回错误提示信息??

class IPFiled:    REGULAR = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"    def __init__(self, error_dict=None, required=True):        self.error_dict = {}  #用于自定制错误提醒信息        if error_dict:            self.error_dict.update(error_dict)        self.required = required  #可空否        self.error = None      #记录错误提醒信息        self.is_valid = False  #匹配成功与否        self.value = http://www.mamicode.com/None       #用户发来进行匹配的值"%s is required" % name            else:                ret = re.match(IPFiled.REGULAR, input_value)                if ret:                    #匹配成功--》通过                    self.id_valid = True                    self.value = http://www.mamicode.com/ret.group()"%s is invalid" % nameclass BaseForm:    def check_valid(self, handle):        flag = True        success_value_dict = {}        error_message_dict = {}        for key, regular in self.__dict__.items():            #key-->ip  handle-->homeindex对象  regular--》IPFiled对象            input_value = http://www.mamicode.com/handle.get_argument(key)  #用户输入的值"required":"别闹","valid":"妹子,格式错了"})class HomeHandler(tornado.web.RequestHandler):    def get(self):        self.render(‘home.html‘)    def post(self, *args, **kwargs):        obj = HomeForm()        # 获取用户输入的内容        # 和正则表达式匹配        is_valid,error_dict,success_dict = obj.check_valid(self)        print(is_valid)        if is_valid:            print(success_dict)        else:            print(error_dict)

 技术分享

 

第三阶段

  在input类型有个叫checkbox的,在后台获取值不再是get_argument,而是get_arguments,不同于其他的input类型,所以在BaseForm的check_valid方法里获取值的方式就要改了,利用type进行判断

class BaseForm:    def check_valid(self, handle):        flag = True        error_message_dict = {}        success_value_dict = {}        for key, regular in self.__dict__.items():            # key: ip .....            # handle: HomeIndex对象,self.get_... self.            # regular: IPFiled(required=True)            if type(regular) == ChechBoxFiled:                input_value = http://www.mamicode.com/handle.get_arguments(key)  #get_arguments是没有默认参数的>

   另外对checkbox返回结果处理方式也稍微有些区别,不用对其合法性检测,只要判断是否为空即可,所以在CheckBoxFiled类的validate方法也要改

class CheckBoxFiled:    def __init__(self, error_dict=None, required=True):        # 封装了错误信息        self.error_dict = {}        if error_dict:            self.error_dict.update(error_dict)        self.required = required        self.error = None # 错误信息        self.value = http://www.mamicode.com/None"""        :param name: 字段名 favor        :param input_value: 用户表单中输入的内容,列表None or [1,2]        :return:        """        if not self.required:            # 用户输入可以为空            self.is_valid = True            self.value = http://www.mamicode.com/input_value"%s is required" % name            else:                self.is_valid = True                self.value = http://www.mamicode.com/input_value>

 

第四阶段

  除了checkbox这个特殊外,还有file----文件上传,在后台获取方式也不一样,不是self.get_argument,而是self.request.files.get(),得到的内容是一个列表,格式如 [{‘body‘:‘xx‘,‘filename‘:‘xx‘},{‘body‘:‘xx‘,‘filename‘:‘xx‘}],想要获得文件名,还要循环这个列表,通过filename的key取到,并且我们如果要把文件上传到服务端,就在FileFiled写入save方法,“body”里面就是文件内容

import tornado.ioloopimport tornado.webimport reimport osclass IPFiled:    REGULAR = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"    def __init__(self, error_dict=None, required=True):        # 封装了错误信息        self.error_dict = {}        if error_dict:            self.error_dict.update(error_dict)        self.required = required        self.error = None # 错误信息        self.value = http://www.mamicode.com/None"""        :param name: 字段名        :param input_value: 用户表单中输入的内容        :return:        """        if not self.required:            # 用户输入可以为空            self.is_valid = True            self.value = http://www.mamicode.com/input_value"%s is required" % name            else:                ret = re.match(IPFiled.REGULAR, input_value)                if ret:                    self.is_valid = True                    self.value = http://www.mamicode.com/input_value"%s is invalid" % nameclass StringFiled:    REGULAR = "^(.*)$"    def __init__(self, error_dict=None, required=True):        # 封装了错误信息        self.error_dict = {}        if error_dict:            self.error_dict.update(error_dict)        self.required = required        self.error = None # 错误信息        self.value = http://www.mamicode.com/None"""        :param name: 字段名        :param input_value: 用户表单中输入的内容        :return:        """        if not self.required:            # 用户输入可以为空            self.is_valid = True            self.value = http://www.mamicode.com/input_value"%s is required" % name            else:                ret = re.match(IPFiled.REGULAR, input_value)                if ret:                    self.is_valid = True                    self.value = http://www.mamicode.com/input_value"%s is invalid" % nameclass ChechBoxFiled:    def __init__(self, error_dict=None, required=True):        # 封装了错误信息        self.error_dict = {}        if error_dict:            self.error_dict.update(error_dict)        self.required = required        self.error = None # 错误信息        self.value = http://www.mamicode.com/None"""        :param name: 字段名 favor        :param input_value: 用户表单中输入的内容,列表None or [1,2]        :return:        """        if not self.required:            # 用户输入可以为空            self.is_valid = True            self.value = http://www.mamicode.com/input_value"%s is required" % name            else:                self.is_valid = True                self.value = http://www.mamicode.com/input_value"^(\w+\.pdf)|(\w+\.mp3)|(\w+\.py)$"    def __init__(self, error_dict=None, required=True):        # 封装了错误信息        self.error_dict = {}        if error_dict:            self.error_dict.update(error_dict)        self.required = required        self.error = None  # 错误信息        self.value = http://www.mamicode.com/[]"""        :param name: 字段名        :param all_file_name_list: 所有文件文件名        :return:        """        self.name = name        if not self.required:            # 用户输入可以为空            self.is_valid = True            self.value = http://www.mamicode.com/all_file_name_list"%s is required" % name            else:                # 循环所有的文件名                for file_name in all_file_name_list:                    ret = re.match(FileFiled.REGULAR, file_name)                    if not ret:                        #有文件名匹配不成功                        self.is_valid = False                        if self.error_dict.get(‘valid‘, None):                            self.error = self.error_dict[‘valid‘]                        else:                            self.error = "%s is invalid" % name                        break                    else:                        self.value.append(file_name)  #都匹配成功的文件名列表    def save(self, request, path=‘statics‘):        # 所有文件列表        # request = HomeHandler.request  self.name = fafafa(前端名字)        file_metas = request.files.get(self.name)        # 循环文件列表        temp_list = []        for meta in file_metas:            # 每一个文件的文件名            file_name = meta[‘filename‘]            # self.value:[1.py, 2.py]  【statics/1.py  statics/2.py】            new_file_name = os.path.join(path, file_name)            #self.value--->success_file_name_list            if file_name and file_name in self.value:                temp_list.append(new_file_name)                with open(new_file_name, ‘wb‘) as up:                    up.write(meta[‘body‘])        self.value = http://www.mamicode.com/temp_list"别闹,别整空的..", "valid": "骚年,格式错误了"})        self.host = StringFiled(required=False)        self.favor = ChechBoxFiled(required=True)        self.fafafa = FileFiled(required=True)  #前端name同名fafafaclass HomeHandler(tornado.web.RequestHandler):    def get(self):        self.render(‘home.html‘, error_dict=None)    def post(self, *args, **kwargs):        # self.get_argument()        # self.get_arguments()        # files = self.request.files.get(‘fafafa‘,[])        # # files = [ 文件一、文件二]        # print(type(files),files)        obj = HomeForm()        is_valid, success_dict, error_dict = obj.check_valid(self)        if is_valid:            print(‘success‘,success_dict)            #只有全部通过,就执行上传方法            obj.fafafa.save(self.request)  #HomeForm().fafafa=FileFiled()        else:            print(‘error‘, error_dict)            self.render(‘home.html‘, error_dict=error_dict)settings = {    ‘template_path‘: ‘views‘,    ‘static_path‘: ‘statics‘,    ‘static_url_prefix‘: ‘/statics/‘,}application = tornado.web.Application([    (r"/home", HomeHandler),], **settings)if __name__ == "__main__":    application.listen(8001)    tornado.ioloop.IOLoop.instance().start()

 

  是不是有点乱,这么多类,好!这里画个小图,助于大家理解.....

技术分享

   是不是还是不好理解,好把,我画图有限...

看用Tornado如何自定义实现表单验证