• Tornado-Form表单验证


    基本思路

    用户提交表单后,验证开始。页面中会有多个域要求验证,如text input,files, checkbox。同时,根据验证字段的不同,验证方式会有很多种,例如对邮箱、IP地址、电话的验证标准就各不相同。那么就要设计不同的验证标准,并且在将所有待验证域验证完毕后,返回验证是否成功的结果。这就是基本的实现了。

    我们还可以将验证机制做得更细致、更友好些。第一点,并不是所有域名都是必须输入,也会有一些空着也无妨的域。那就可以对不同域做出区分。第二点,用户输入出错和空着没填,我们也可以区分开来,并且在前端动态地提示用户。第三点,对于每个用户输入域,我们都是获取用户输入,并拿自己定义的标准比对,这个过程其实是可以复用的,那是不是可以写一个基类将这些过程抽象并封装,让每个域去继承这一基本流程呢?

    文件结构

    两个启动文件,对应的分别的是基本表单验证实现和完整的验证实现。

    HTML文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Form验证</title>
        <style>
            .inputs_1{margin-left:36px;}
            .inputs_2{margin-left:50px;}
            .inputs_3{margin-left:18px;}
        </style>
    </head>
    <body>
        <form action="/index" method="post" enctype="multipart/form-data">
            IP地址<input type="text" name="ip" class="inputs_1">
            <span style="color:red;">
                {{ display_error_info(error_message_dict, 'ip') }}
            </span><br>
            <p>
                <input type="checkbox" name="game" value="1">马里奥
                <input type="checkbox" name="game" value="2">塞尔达
                <input type="checkbox" name="game" value="3">星之卡比
            </p>
            <span style="color:red;">
                {{ display_error_info(error_message_dict, 'game') }}
            </span><br>
            <input type="file" name="files"/>
            <input type="file" name="files"/>
            <input type="submit" value="提交">
            <span style="color:red;">
                {{ display_error_info(error_message_dict, 'files') }}
            </span><br>
        </form>
    
    </body>
    </html>
    View Code

    验证的基本实现

    import tornado.ioloop
    import tornado.web
    
    
    class MainForm(object):
        # MainForm类的字段就是要匹配的输入域,值是匹配模式
        # 其中对checkbox和files两个域,暂时简单地用判断列表有无元素进行检测
        def __init__(self):
            # 匹配IP地址这里折腾了很久,一致尝试使用命名分组,却一直无法成功,
            # 不懂怎么回事,不想再看正则这一部分了,先放一放
            # 注意尖角号和美元符的作用:匹配字符串开头和结尾的位置
            # match从头匹配,没有则None,search则是匹配到
            self.ip = "^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])(.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])){3}$"
            self.email = "^w+@w+.w+"
            self.phone = "1[3|7|5|8]d{8}"
            self.game = []
            self.files = []
    
        def check_valid(self, handler):
            import re
            # 获取了对象的所有字段,形式是一个字典,键值分别是字段和字段的值
            form_dict = self.__dict__
            flag = True
            # 用户输入的值也储存在一个字典中
            user_dict = {}
            # 这里我们迭代form_dict的所有元素
            for key, pattern in form_dict.items():
                if key == 'game':
                    user_value = handler.get_arguments(key)
                    is_valid = False if not user_value else True
                elif key == 'files':
                    user_value = handler.request.files.get(key)
                    is_valid = False if not user_value else True
                else:
                    user_value = handler.get_argument(key)
                    is_valid = re.match(pattern, user_value)
                if not is_valid:
                    flag = False
                user_dict[key] = user_value
            return flag, user_dict
    
    
    class IndexHandler(tornado.web.RequestHandler):
        def get(self):
            self.render('index.html')
    
        def post(self):
            # 用户提交数据后,会生成一个MainForm()对象
            _formVali = MainForm()
            flag, user_inputs = _formVali.check_valid(self)
            print(flag, user_inputs)
    
    
    settings = {
            "template_path": "views",  # 配置html文件路径
            "static_path": "statics",  # 配置静态文件路径
        }
    
    # 路由映射
    application = tornado.web.Application([
        (r"/index", IndexHandler),
    ], **settings)
    
    # 启动
    if __name__ == "__main__":
        application.listen(8001)
        tornado.ioloop.IOLoop.instance().start()
    View Code

    类封装后的验证实现

    import tornado.ioloop
    import tornado.web
    import re
    import myuimethod
    
    class IPField:
        REGULAR = "^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])(.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])){3}$"
    
        def __init__(self, error_dict=None, required=True):
            # 封装自定义错误信息,格式为错误类型-错误信息
            self.error_dict = {}
            if error_dict:
                self.error_dict.update(error_dict)
            self.required = required
            # 用于返回给check_valid函数,让其判断输入是否全部合法
            self.is_valid = False
            # 封装了用户输入的值
            self.value = None
            # 封装了具体的错误信息
            self.error = None
    
        def validate(self, name, user_value):
            # 传入参数:域名、用户输入值
            # 是否要求必须输入
            if not self.required:
                self.is_valid = True
                self.value = user_value
            else:
                # 输入是否为空
                if not user_value.strip():
                    # 是否自定义了域不可为空的错误信息
                    if self.error_dict.get("required", None):
                        self.error = self.error_dict['required']
                    else:
                        self.error = "%s is required." % name
                else:
                    match_obj = re.match(IPField.REGULAR, user_value)
                    # 是否匹配成功
                    if match_obj:
                        self.is_valid = True
                        self.value = match_obj.group()
                    else:
                        # 是否定义了非法输入的错误信息
                        if self.error_dict.get('valid', None):
                            self.error = self.error_dict['valid']
                        else:
                            self.error = "%s is invalid." % name
    
    
    class CheckboxField:
        def __init__(self, error_dict=None, required=True):
            self.error_dict = {}
            if error_dict:
                self.error_dict.update(error_dict)
            self.required = required
            self.is_valid = False
            self.value = None
            self.error = None
    
        def validate(self, name, user_value):
            """
            :param name: 域名 game
            :param user_value: 用户勾选的内容 形如[1,2,3]
            :return:
            """
            if not self.required:
                self.is_valid = True
                self.value = user_value
            else:
                if not user_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 = user_value
    
    
    class FileField:
        REGULAR = r'(w+.jpg)|(w+.jpeg)|(w+.gif)|(*+.png)'
    
        def __init__(self, error_dict=None, required=True):
            self.name = None
            self.error = None
            # 封装了符合规范的文件名
            self.value = []
            # 上传文件一项是否符合规范,默认为True,验证中一旦有不合法的地方就将其改为False
            self.is_valid = True
            self.required = required
            # 存储最终成功上传的文件路径
            self.success_file_list = []
            # 将错误信息封装在字典中
            self.error_dict = {}
            # 如果自定义了错误信息,则更新默认字典
            if error_dict:
                self.error_dict.update(error_dict)
    
        def validate(self, name, file_name_list):
            self.name = name
            # 根据文件上传是否必须,区分为两大场景
            if not self.required:
                self.is_valid = True
                # 所有合法的文件列表,文件可能有1个没有上传,也有可能文件后缀或大小不符合要求
                self.success_file_list = file_name_list
            else:
                # 要求上传文件却没有上传
                if not file_name_list:
                    self.is_valid = False
                    if self.error_dict.get('required', None):
                        self.error = self.error_dict['required']
                    else:
                        self.error = 'Files not selected and uploaded.'
                else:
                    for file_name in file_name_list:
                        # 文件是否符合规范
                        if not re.match(FileField.REGULAR, file_name):
                            self.is_valid = False
                            if self.error_dict.get('error', None):
                                self.error = self.error_dict['error']
                            else:
                                self.error = '%s is invalid' % file_name
                        else:
                            self.value.append(file_name)
    
        def save(self, files_obj, path=''):
            import os
            for file_obj in files_obj:
                file_name = file_obj['filename']
                full_file_name = os.path.join(path, file_name)
                print(full_file_name)
                # 文件名不为空你且文件名在合法文件列表中
                if file_name and file_name in self.value:
                    self.success_file_list.append(full_file_name)
                    with open(full_file_name, 'wb') as f:
                        f.write(file_obj['body'])
    
    
    class BaseForm:
        def check_valid(self, handler):
            flag = True
            error_message_dict = {}
            success_dict = {}
            # Form类继承自BaseForm类,self.__dict__内封装了派生类的所有字段
            # key是字段名,regOBj则是对应的正则对象
            for key, regObj in self.__dict__.items():
                # key: 输入域
                if type(regObj) == CheckboxField:
                    user_value = handler.get_arguments(key)
                elif type(regObj) == FileField:
                    # form表单内有文件上传时,一定要将enctype属性设置为multipart/form-data,否则服务端收不到文件数据
                    # files_obj是一个列表,元素是字典,有键filename和body,封装了文件名和文件内容
                    files_obj = handler.request.files.get(key)
                    user_value = []
                    # 如果没有上传任何文件,files_obj将是NoneType无法迭代,迭代前需判断
                    if files_obj:
                        for file in files_obj:
                            user_value.append(file['filename'])
                else:
                    user_value = handler.get_argument(key)
                # 将验证过程放入对应域的正则对象中
                # 这样可以将不同的验证规则封装到不同的类中
                regObj.validate(key, user_value)
                # 验证信息封装在regObj中
                if regObj.is_valid:
                    success_dict[key] = regObj.value
                    regObj.save(files_obj)
                else:
                    flag = False
                    error_message_dict[key] = regObj.error
            return flag, success_dict, error_message_dict
    
    
    class IndexForm(BaseForm):
        def __init__(self):
            self.ip = IPField(required=True)
            self.game = CheckboxField(required=True)
            self.files = FileField(required=True)
    
    
    class IndexHandler(tornado.web.RequestHandler):
        def get(self):
            self.render('index.html', error_message_dict={})
    
        def post(self):
            _formVali = IndexForm()
            flag, success_dict, error_message_dict = _formVali.check_valid(self)
            if flag:
                print('success', success_dict)
            else:
                print('error', error_message_dict)
                self.render('index.html', error_message_dict=error_message_dict)
    
    
    settings = {
            "template_path": "views",  # 配置html文件路径
            "static_path": "statics",  # 配置静态文件路径
            "ui_methods": myuimethod   # 配置模板方法文件
        }
    
    
    # 路由映射
    application = tornado.web.Application([
        (r"/index", IndexHandler),
    ], **settings)
    
    # 启动
    if __name__ == "__main__":
        application.listen(8001)
        tornado.ioloop.IOLoop.instance().start()
    View Code
    def display_error_info(self, error_dict, key):
        if error_dict.get(key):
            return error_dict[key]
        else:
            return ''
    myuimethod
  • 相关阅读:
    mass Framework draggable插件
    将一段数字从右到左每隔三位插入一个逗号
    Firefox 12正式发布
    各大瀑布流简析与建议
    判定是否为非负整数
    mass Framework droppable插件
    HTML 5 <video> 标签
    SQL DELETE 语句
    SQL CREATE TABLE 语句(转)
    HTML <fieldset> 标签
  • 原文地址:https://www.cnblogs.com/yifeixu/p/8413735.html
Copyright © 2020-2023  润新知