看用Tornado如何自定义实现表单验证
我们知道,平时在登陆某个网站或软件时,网站对于你输入的内容是有要求的,并且会对你输入的错误内容有提示,对于Django这种大而全的web框架,是提供了form表单验证功能,但是对于Tornado而言,就没有这功能,所以就需要我们来自己自定义form表单验证,而且这种方法正是Django里的form表单验证的实质内容,也帮我们在后面学习Django理解相关的源码。
写之前,我们必须知道form表单验证的实质是什么?
实质就是正则匹配
我们知道用户提交数据是通过post方式提交,所以我们重写post方法,并在post方法进行业务逻辑处理
- 获取用户提交的数据
- 将用户提交的数据和正则表达式匹配
第一阶段
- 场景:我们知道后台是一个url对应一个类来处理客户请求的
- 问题:那么在不同页面里会相同需求,比如登陆时用邮箱登陆,修改密码时又要用邮箱,那对于这样同样验证需求,在我们的后台是不是代码就重复了呢?
- 解决:独立一个模块专门用于用户信息验证
我们先看一下下面这段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 = request.get_argument(key) #获取用户输入的值 # 让提交的数据 和 定义的正则表达式进行匹配 ret = re.match(regular, post_value) print key,ret,post_value if not ret: flag = False #一旦有匹配不成功的,设置为False return flag #post方法里根据这个返回值来决定给客户返回什么内容 |
从上面我们可以知道,这个模块只是简简单单的给了true or false返回值的问题,我们还希望返回客户输入的值,看下怎么优化吧
- 在check_vaild方法里定义一个局部变量-字典,然后接收客户的信息,并把这个字典一并返回到post方法里
第二阶段
在上面,我们已经独立了一个模块来验证信息,但问题又来了,上面这个模块我定义的时候以index页面定制的,不同的页面处理的需要是不一样的,那如果解决这个问题,好像可以每个页面都独立一个模块,但这样,代码未免重复的太多了。
仔细的人会发现,其实就是init里需要匹配的内容不一样,下面的check_vaild方法都是一样的
-
类的继承,写一个BaseForm父类,让其他验证类继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | class BaseForm: def check_valid( self , handle): flag = True value_dict = {} for key, regular in self .__dict__.items(): # host,ip port phone input_value = handle.get_argument(key) val = re.match(regular, input_value) print (key, input_value, val, regular) if not val: flag = False value_dict[key] = input_value return flag, value_dict class IndexForm(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}$" 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时可为空呢??并且我们让这个类处理时返回错误提示信息??
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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 = None #用户发来进行匹配的值 def validate( self , name, input_value): ''' :param name: 字段名:IP 方便在错误信息里提示 :param input_value:用户输入的值 :return: ''' if not self .required: #可以为空--》通过 self .is_valid = True self .value = input_value else : if not input_value.strip(): #输入为空 if self .error_dict.get( 'required' , None ): #使用自定义错误信息 self .error = self .error_dict[ 'required' ] else : #使用默认 self .error = "%s is required" % name else : ret = re.match(IPFiled.REGULAR, input_value) if ret: #匹配成功--》通过 self .id_valid = True self .value = ret.group() else : if self .error_dict.get( 'valid' , None ): #获取自定义错误信息,并使用 self .error = self .error_dict[ 'valid' ] else : #使用默认 self .error = "%s is invalid" % name class BaseForm: def check_valid( self , handle): flag = True success_value_dict = {} error_message_dict = {} for key, regular in self .__dict__.items(): #key-->ip handle-->homeHandler对象 regular--》IPFiled对象 input_value = handle.get_argument(key) #用户输入的值 #将验证放在了IPFiled对象里 regular.validate(key,input_value) if regular.is_valid: #匹配成功就添加用户输入值 success_value_dict[key] = regular.value else : #添加错误提示信息 error_message_dict[key] = regular.error flag = False return flag, error_message_dict,success_value_dict class HomeForm(BaseForm): def __init__( self ): self .ip = IPFiled(required = True ,error_dict = { "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进行判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 = handle.get_arguments(key) #get_arguments是没有默认参数的 else : input_value = handle.get_argument(key) # input_value = 用户输入的值 # 将具体的验证,放在IPFiled对象中 regular.validate(key, input_value) if regular.is_valid: success_value_dict[key] = regular.value else : error_message_dict[key] = regular.error flag = False |
另外对checkbox返回结果处理方式也稍微有些区别,不用对其合法性检测,只要判断是否为空即可,所以在CheckBoxFiled类的validate方法也要改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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 = None self .is_valid = False def validate( self , name, input_value): """ :param name: 字段名 favor :param input_value: 用户表单中输入的内容,列表None or [1,2] :return: """ if not self .required: # 用户输入可以为空 self .is_valid = True self .value = input_value else : if not input_value: #不应为空,而空了,提示错误信息 if self .error_dict.get( 'required' , None ): #获取自定制信息 self .error = self .error_dict[ 'required' ] else : #取默认的 self .error = "%s is required" % name else : self .is_valid = True self .value = input_value |
第四阶段
除了checkbox这个特殊外,还有file----文件上传,在后台获取方式也不一样,不是self.get_argument,而是self.request.files.get(),得到的内容是一个列表,格式如 [{'body':'xx','filename':'xx'},{'body':'xx','filename':'xx'}],想要获得文件名,还要循环这个列表,通过filename的key取到,并且我们如果要把文件上传到服务端,就在FileFiled写入save方法,“body”里面就是文件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | import tornado.ioloop import tornado.web import re import os 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 .value = None self .is_valid = False def validate( self , name, input_value): """ :param name: 字段名 :param input_value: 用户表单中输入的内容 :return: """ if not self .required: # 用户输入可以为空 self .is_valid = True self .value = input_value else : if not input_value.strip(): if self .error_dict.get( 'required' , None ): self .error = self .error_dict[ 'required' ] else : self .error = "%s is required" % name else : ret = re.match(IPFiled.REGULAR, input_value) if ret: self .is_valid = True self .value = input_value else : if self .error_dict.get( 'valid' , None ): self .error = self .error_dict[ 'valid' ] else : self .error = "%s is invalid" % name class 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 = None self .is_valid = False def validate( self , name, input_value): """ :param name: 字段名 :param input_value: 用户表单中输入的内容 :return: """ if not self .required: # 用户输入可以为空 self .is_valid = True self .value = input_value else : if not input_value.strip(): if self .error_dict.get( 'required' , None ): self .error = self .error_dict[ 'required' ] else : self .error = "%s is required" % name else : ret = re.match(IPFiled.REGULAR, input_value) if ret: self .is_valid = True self .value = input_value else : if self .error_dict.get( 'valid' , None ): self .error = self .error_dict[ 'valid' ] else : self .error = "%s is invalid" % name class 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 = None self .is_valid = False def validate( self , name, input_value): """ :param name: 字段名 favor :param input_value: 用户表单中输入的内容,列表None or [1,2] :return: """ if not self .required: # 用户输入可以为空 self .is_valid = True self .value = input_value else : if not input_value: if self .error_dict.get( 'required' , None ): self .error = self .error_dict[ 'required' ] else : self .error = "%s is required" % name else : self .is_valid = True self .value = input_value class FileFiled: REGULAR = "^(\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 = [] self .is_valid = True self .name = None self .success_file_name_list = [] def validate( self , name, all_file_name_list): """ :param name: 字段名 :param all_file_name_list: 所有文件文件名 :return: """ self .name = name if not self .required: # 用户输入可以为空 self .is_valid = True self .value = all_file_name_list else : #用户输入不能为空 if not all_file_name_list: #输入为空 self .is_valid = False if self .error_dict.get( 'required' , None ): self .error = self .error_dict[ 'required' ] else : self .error = "%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 = temp_list 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 = handle.get_arguments(key) elif type (regular) = = FileFiled: # 获取文件名 file_list = handle.request.files.get(key) # [{'body':'xx','filename':'xx'},{'body':'xx','filename':'xx'}] input_value = [] #file_name_list for item in file_list: input_value.append(item[ 'filename' ]) # 所有文件名进行验证 else : input_value = handle.get_argument(key) # input_value = 用户输入的值 # 将具体的验证,放在IPFiled对象中 regular.validate(key, input_value) if regular.is_valid: success_value_dict[key] = regular.value else : error_message_dict[key] = regular.error flag = False return flag, success_value_dict, error_message_dict class HomeForm(BaseForm): def __init__( self ): self .ip = IPFiled(required = True , error_dict = { 'required' : "别闹,别整空的.." , "valid" : "骚年,格式错误了" }) self .host = StringFiled(required = False ) self .favor = ChechBoxFiled(required = True ) self .fafafa = FileFiled(required = True ) #前端name同名fafafa class 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() |
是不是有点乱,这么多类,好!这里画个小图,助于大家理解.....
是不是还是不好理解,好把,我画图有限...
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步