zoukankan      html  css  js  c++  java
  • 4.flask第三方组件

    1.flask-session的使用

    在flask中,有一个app.session_interface = SecureCookieSessionInterface(),也就是存session,调用open_session方法,取session调用save_session方法
    

    因此如果我们想要自己定制session的存储位置,那么直接修改app.session_interface即可。这里我们介绍一个第三方的组件,叫做flask-session,直接pip install flask-session即可

    from flask import Flask
    from flask_session import Session
    
    app = Flask(__name__)
    
    Session(app)
    
    
    @app.route("/login")
    def login():
        return "login"
    
    
    if __name__ == "__main__":
        app.run(port=8888, debug=True)
    
    

    我们看看Session(app)这一步都做了些什么

    class Session(object):
        def __init__(self, app=None):
            # 传入app,这里肯定会部位None
            self.app = app
            if app is not None:
                # 调用init_app
                self.init_app(app)
    
        def init_app(self, app):
            # 可以看到,帮我们把app.session_interface给换掉了
            # 因为默认的是SecureCookieSessionInterface(),然后调用了_get_interface(app)
            app.session_interface = self._get_interface(app)
    
        def _get_interface(self, app):
            # 这里的配置文件先不用管
            config = app.config.copy()
            config.setdefault('SESSION_TYPE', 'null')
            config.setdefault('SESSION_PERMANENT', True)
            config.setdefault('SESSION_USE_SIGNER', False)
            config.setdefault('SESSION_KEY_PREFIX', 'session:')
            config.setdefault('SESSION_REDIS', None)
            config.setdefault('SESSION_MEMCACHED', None)
            config.setdefault('SESSION_FILE_DIR',
                              os.path.join(os.getcwd(), 'flask_session'))
            config.setdefault('SESSION_FILE_THRESHOLD', 500)
            config.setdefault('SESSION_FILE_MODE', 384)
            config.setdefault('SESSION_MONGODB', None)
            config.setdefault('SESSION_MONGODB_DB', 'flask_session')
            config.setdefault('SESSION_MONGODB_COLLECT', 'sessions')
            config.setdefault('SESSION_SQLALCHEMY', None)
            config.setdefault('SESSION_SQLALCHEMY_TABLE', 'sessions')
    		
            # 这里通过app.config,指定SESSION_TYPE,创建一个新的session_interface
            # 可以使redis,memcached,文件系统,数据库等等
            if config['SESSION_TYPE'] == 'redis':
                session_interface = RedisSessionInterface(
                    # 如果是redis,则必须指定'SESSION_REDIS'
                    # 后面的配置不指定,可以使用默认的
                    config['SESSION_REDIS'], config['SESSION_KEY_PREFIX'],
                    config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])
            elif config['SESSION_TYPE'] == 'memcached':
                session_interface = MemcachedSessionInterface(
                    config['SESSION_MEMCACHED'], config['SESSION_KEY_PREFIX'],
                    config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])
            elif config['SESSION_TYPE'] == 'filesystem':
                session_interface = FileSystemSessionInterface(
                    # 如果是文件,则需要指定文件路径
                    config['SESSION_FILE_DIR'], config['SESSION_FILE_THRESHOLD'],
                    config['SESSION_FILE_MODE'], config['SESSION_KEY_PREFIX'],
                    config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])
            elif config['SESSION_TYPE'] == 'mongodb':
                session_interface = MongoDBSessionInterface(
                    config['SESSION_MONGODB'], config['SESSION_MONGODB_DB'],
                    config['SESSION_MONGODB_COLLECT'],
                    config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'],
                    config['SESSION_PERMANENT'])
            elif config['SESSION_TYPE'] == 'sqlalchemy':
                session_interface = SqlAlchemySessionInterface(
                    app, config['SESSION_SQLALCHEMY'],
                    config['SESSION_SQLALCHEMY_TABLE'],
                    config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'],
                    config['SESSION_PERMANENT'])
            else:
                # 如果在配置中不指定,那么不好意思,open_session返回None
                """
    		   class NullSessionInterface(SessionInterface):
        	   	   def open_session(self, app, request):
            	   return None
    		   """
                session_interface = NullSessionInterface()
    		
            # 最后返回session_interface
            return session_interface
    

    使用flask-session

    from flask import Flask
    from flask_session import Session
    
    app = Flask(__name__)
    
    # 我的阿里云没有安装redis,所以这里就使用文件了
    """
    如果是redis的话,也是一样的
    import redis
    app.config["SESSION_TYPE"] = "redis"
    app.config["SESSION_REDIS"] = redis.Redis() 
    
    """
    app.config["SESSION_TYPE"] = "filesystem"
    app.config["SESSION_FILE_DIR"] = "session_dir"
    Session(app)
    # 可以看到,在使用层面上,加上三行代码就可以了
    
    
    @app.route("/login")
    def login():
        return "login"
    
    
    if __name__ == "__main__":
        app.run(port=8888, debug=True)
    
    

    可以看到这里多了一个session_dir目录,里面多了两个文件

    当然我这里没有设置session,我们来设置一下

    from flask import Flask
    from flask_session import Session
    from flask import session
    
    app = Flask(__name__)
    
    # 我的阿里云没有安装redis,所以这里就使用文件了
    """
    如果是redis的话,也是一样的
    import redis
    app.config["SESSION_TYPE"] = "redis"
    app.config["SESSION_REDIS"] = redis.Redis() 
    
    """
    app.config["SESSION_TYPE"] = "filesystem"
    app.config["SESSION_FILE_DIR"] = "session_dir"
    Session(app)
    # 可以看到,在使用层面上,加上三行代码就可以了
    
    
    @app.route("/login")
    def login():
        session["username"] = "satori"
        return "login"
    
    
    @app.route("/index")
    def index():
        print(session["username"])
        return "index"
    
    
    if __name__ == "__main__":
        app.run(port=8888, debug=True)
    

    访问/login,再访问/index

    可以看到是成功的。此时的session就不再通过序列化、加密写到用户的浏览器的cookie里面去了,而是写到我们指定的地方。然后返回给用户浏览器的则是一个随机字符串,用户再来请求的时候会拿随机字符串来进行匹配。
    那么这是怎么做到的呢?
    我们之前在看session源码的时候,知道要通过open_session获取session,通过save_session存储session,既然我们把SecureCookieSessionInterface()给换掉了,那么是不是就以为着,我们用来替代的方法中也必须要有open_session和save_session呢?答案是肯定的。
    

    每个SESSION_TYPE都会对应一个类,里面的open_session和save_session会从对应的位置获取和存储session



    2.WTForms表单验证基本使用

    WTForms表单的两个主要的功能就是就是验证用户提交数据的合法性以及渲染模板。当然还包括其他的功能:CSRF保护,文件上传等等

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form action="/register" method="post">
            <table>
                <tbody>
                    <tr>
                        <td>姓名:</td>
                        <td><input name="username" type="text"></td>
                    </tr>
                    <tr>
                        <td>密码:</td>
                        <td><input name="password" type="password"></td>
                    </tr>
                    <tr>
                        <td>重复密码:</td>
                        <td><input name="repeat_passwd" type="password"></td>
                    </tr>
                    <tr>
                        <td></td>
                        <td><input type="submit" value="立即注册"></td>
                    </tr>
                </tbody>
            </table>
        </form>
    </body>
    </html>
    
    from flask import Flask, request, render_template
    from wtforms import Form, StringField
    from wtforms.validators import Length, EqualTo
    
    app = Flask(__name__)
    
    
    # 定义一个类,继承自Form
    class RegisterForm(Form):
        # 我们这里的左值,就是html里面的name,这里一定要保持一直,否则不会验证
        # 这里传入一个列表,因为验证条件会有多个,所以以列表形式传值,这里表示长度为6到10位
        username = StringField(validators=[Length(min=6, max=10)])
        password = StringField(validators=[Length(min=6, max=10)])
        # 这里的repeat_passwd必须要和password保持一致,所以这里的Length也可以不要,まぁいいや
        repeat_passwd = StringField(validators=[Length(min=6, max=10), EqualTo("password")])
    
    
    @app.route("/register", methods=["GET", "POST"])
    def register():
        if request.method == 'GET':
            return render_template("register.html")
        else:
            # 直接将request.form丢进去就可以了
            form = RegisterForm(request.form)
            # 如果满足条件,那么form.validate()会返回True
            if form.validate():
                # 然后调用form.username.data和form.password.data即可获取值了
                # 而form.username和form.password则为StringField,加上()调用的话则会生成html标签
                print(f"username={form.username}, password={form.password}")
                return "登陆成功"
            else:
                # 没通过的话,那么错误信息会存储在form.errors里面
                return f"登录失败:{form.errors}"
    
    
    if __name__ == "__main__":
        app.run(port=8888, debug=True)
    

    但是这种报错信息似乎显得不友好,因此我们也可以自己指定

    class RegisterForm(Form):
        # 我们这里的左值,就是html里面的name="username",这里一定要保持一直,否则不会验证
        # 这里传入一个列表,因为验证条件会有多个,所以以列表形式传值,这里表示长度为6到10位
        username = StringField(validators=[Length(min=6, max=10, message="用户名必须在6到10位")])
        password = StringField(validators=[Length(min=6, max=10, message="密码必须在6到10位")])
        # 这里的repeat_passwd必须要和password保持一致,所以这里的Length也可以不要,まぁいいや
        repeat_passwd = StringField(validators=[Length(min=6, max=10, message="密码必须在6到10位"),
                                                EqualTo("password", message="两次输入的密码不一致")])
    



    3.WTForms常用验证器

    常用的验证器: 数据发送过来,经过表单验证,因此需要验证器来进行验证,以下是一些常用的验证器

    • Email:验证上传的数据是否为邮箱
    • EqualTo:验证上传的数据是否和另外一个字段相等,常用的就是密码和确认密码两个字段是否相等
    • InputRequired:表示该字段为必填项,只要填了就通过,不填就是失败
    • Length:长度限制,有min和max两个值进行限制 ,表示输入的字符串的长度为[min, max]
    • NumberRange:数字的区间,有mix和max两个值进行限制,而且必须是数字,如果处在这两个数字之间则满足
    • Regexp:自定义正则表达式
    • URL:必须要是url的形式
    • UUID:必须是UUID的形式
    class RegisterForm(Form):
        # 必须为邮箱
        email = StringField(validators=[Email()])
        # 必须输入
        username = StringField(validators=[InputRequired()])
        # 是一个数字,要在18到60之间
        age = StringField(validators=[NumberRange(min=18, max=60)])
        # 以1开头的11位数字
        phone = StringField(validators=[Regexp(r"1d{10}")])
        # url格式
        url = StringField(validators=[URL()])
        # uuid类型
        uuid = StringField(validators=[UUID()])
    

    没有什么技术难度,具体不再演示了



    3.自定义表单验证器

    from flask import Flask, request, render_template
    from wtforms import Form, StringField
    from wtforms.validators import Length, EqualTo, Email, InputRequired, NumberRange, Regexp, URL, UUID, ValidationError
    
    app = Flask(__name__)
    
    
    class RegisterForm(Form):
        # 如何自定义表单,比如我们在html里面有一个name="username"
        # 那么就需要定义一个函数叫做,validate_username,当验证username的时候,会自动执行相应的函数
        username = StringField(validators=[InputRequired(), Length(min=8, max=15, message="用户名要在8到15位")])
    	
        # 必须得有username = 
        # 否则即使定义了validate_username也是无效的
        def validate_username(self, field):
            # field.data,就是我们在html中获取的值
            print(type(field))  # <class 'wtforms.fields.core.StringField'>
            # 如果全部username全部是数字,那么不允许
            if set(field.data) <= set("123456789"):
                raise ValidationError("用户名不能全为数字")
    
    
    @app.route("/register", methods=["GET", "POST"])
    def register():
        if request.method == 'GET':
            return render_template("register.html")
        else:
            # 直接将request.form丢进去就可以了
            form = RegisterForm(request.form)
            if form.validate():
                return "登陆成功"
            else:
                # 没通过的话,那么错误信息会存储在form.errors里面
                return f"登录失败:{form.errors}"
    
    
    if __name__ == "__main__":
        app.run(port=8888, debug=True)
    



    4.使用wtforms渲染模板

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .text_color {
                background-color: yellow;
            }
        </style>
    </head>
    <body>
        <form action="/index" method="post">
            <table>
                <tbody>
                <tr>
                    <td>{{ form.username.label }}</td>
                    <td>{{ form.username(class='text_color') }}</td>
                </tr>
                <tr>
                    <td>{{ form.age.label }}</td>
                    <td>{{ form.age() }}</td>
                </tr>
                <tr>
                    <td>{{ form.remember.label }}</td>
                    <td>{{ form.remember() }}</td>
                </tr>
                <tr>
                    <td>{{ form.tags.label }}</td>
                    <td>{{ form.tags() }}</td>
                </tr>
                <tr>
                    <td></td>
                    <td>
                        <input type="submit" value="提交">
                    </td>
                </tr>
                </tbody>
            </table>
        </form>
    </body>
    </html>
    
    from flask import Flask, request, render_template
    from wtforms import Form, StringField, BooleanField, SelectField
    from wtforms.validators import Length, EqualTo, Email, InputRequired, NumberRange, Regexp, URL, UUID, ValidationError
    
    app = Flask(__name__)
    
    
    class IndexForm(Form):
        # 第一个参数指定之后,会自动传到html中的form.username.label,如果不指定,那么自动为左值并且首字母大写
        # 而form.username()则是一个input标签,这里的左值username到html中就会变成,name="username"
        username = StringField("用户名:", validators=[InputRequired()])
        # 同理age也是一样,我这里指定了"年龄:", 那么form.age.label就等于"年龄:"
        # form.age()则是一个input标签,等价于<input name="age" type="text"/>
        # 当然form.age()里面还可以指定属性,比如class,id等等
        age = StringField("年龄:", validators=[InputRequired()])
        # 还有BooleanField,在页面中就是一个小框,点击是否确定
        remember = BooleanField("记住我:")
        # SelectField,则是菜单模式,可以选择
        tags = SelectField("选项:", choices=[("1", "古明地觉"), ("2", "椎名真白"), ("3", "四方茉莉")])
    
    
    @app.route("/index", methods=["GET", "POST"])
    def index():
        if request.method == "GET":
            # 如果我们在html中自己指定表单信息的话,这里是不需要IndexForm的
            # 但是现在html中的信息是需要我们生成的,创建将form丢进去
            form = IndexForm()
            return render_template("index.html", form=form)
        else:
            # 当用户把信息填完之后,将request.form再丢进IndexForm中,进行验证
            form = IndexForm(request.form)
            if form.validate():
                # 此时form.username获取的是一个StringField
                # 如果获取值,需要调用form.username.data
                # form.username()的话,则会打印对应标签
                print(type(form.username))  # <class 'wtforms.fields.core.StringField'>
                print(form.username.name)  # username
                print(form.username.data)  # satori
                print(form.username.type)  # StringField
                print(form.username.label)  # <label for="username">用户名:</label>
                print(form.username())  # <input id="username" name="username" required type="text" value="satori">
    
                print(type(form.age))  # <class 'wtforms.fields.core.StringField'>
                print(form.age.name)  # age
                print(form.age.data)  # 16
                print(form.age.type)  # StringField
                print(form.age.label)  # <label for="age">年龄:</label>
                print(form.age())  # <input id="age" name="age" required type="text" value="16">
    
                print(type(form.remember))  # <class 'wtforms.fields.core.BooleanField'>
                print(form.remember.name)  # remember
                print(form.remember.data)  # True
                print(form.remember.type)  # BooleanField
                print(form.remember.label)  # <label for="remember">记住我:</label>
                print(form.remember())  # <input checked id="remember" name="remember" type="checkbox" value="y">
    
                print(type(form.tags))  # <class 'wtforms.fields.core.SelectField'>
                print(form.tags.name)  # tags
                print(form.tags.data)  # 1
                print(form.tags.type)  # SelectField
                print(form.tags.label)  # <label for="tags">选项:</label>
                print(form.tags())  # <select id="tags" name="tags"><option selected value="1">古明地觉</option><option value="2">椎名真白</option><option value="3">四方茉莉</option></select>
                return f"xxxxx"
            else:
                return f"error occurred:{form.errors}"
    
    
    if __name__ == "__main__":
        app.run(port=8888, debug=True)
    



    5.上传文件以及访问上传的文件

    flask中如何接收用户上传的文件呢?

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form action="/upload" method="post" enctype="multipart/form-data">
            <table>
                <tr>
                    <td>头像:</td>
                    <td><input type="file" name="avatar"></td>
                </tr>
                <tr>
                    <td>描述:</td>
                    <td><input type="text" name="describe"></td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </table>
        </form>
    </body>
    </html>
    
    from flask import Flask, request, render_template
    import os
    
    app = Flask(__name__)
    
    
    @app.route("/upload", methods=["GET", "POST"])
    def upload():
        if request.method == 'GET':
            return render_template("upload.html")
        else:
            describe = request.form.get("describe")
            avatar = request.files.get("avatar")
            # avatar就是我们所获取的文件
            # avatar.filename则是文件名,那么如何将文件保存起来呢?可以直接使用save
            # 由于直接使用用户获取的文件名比较危险,那么可以from werkzeug.utils import secure_filename,然后进行转化
            avatar.save(os.path.join(os.path.dirname(__file__), "upload_file", avatar.filename))
            return f"{avatar.filename}上传成功, 描述信息为{describe}"
    
    
    if __name__ == "__main__":
        app.run(port=8888, debug=True)
    



    6.让用户直接访问图片

    就拿我们刚才上传的图片为例

    from flask import Flask, request, render_template
    import os
    
    app = Flask(__name__)
    
    
    @app.route("/images/<filename>")
    def get_image(filename):
        from flask import send_from_directory
        if os.path.exists(os.path.join(os.path.dirname(__file__), "upload_file", filename)):
            # send_from_directory表示将图片直接返回
            # 接收两个参数,一个是图片的目录,一个是图片的名字
            return send_from_directory(os.path.join(os.path.dirname(__file__), "upload_file"), filename)
    
        else:
            return "没有该图片。。。。。。"
    
    
    if __name__ == "__main__":
        app.run(port=8888, debug=True)
    



    7.使用flask-wtf验证上传的文件

    之前我们接收用户上传的文件,也没有进行判断,比如说用户上传头像,应该是一张图片,可如果用户上传的是txt,或者py文件怎么办呢?这时候我们应该对用户上传的文件进行一个判断。flask-wtf是一个专门用于对文件进行验证的第三方插件。同样需要pip install flask-wtf

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form action="/upload" method="post" enctype="multipart/form-data">
            <table>
                <tr>
                    <td>头像:</td>
                    <td><input type="file" name="avatar"></td>
                </tr>
                <tr>
                    <td>描述:</td>
                    <td><input type="text" name="describe"></td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </table>
        </form>
    </body>
    </html>
    
    from flask import Flask, request, render_template
    from wtforms import Form, StringField, FileField
    from wtforms.validators import InputRequired
    # FileRequired表示文件必须上传,FileAllowed可以输入允许的文件格式
    from flask_wtf.file import FileRequired, FileAllowed
    from werkzeug.datastructures import CombinedMultiDict
    
    app = Flask(__name__)
    
    
    class UpLoadForm(Form):
        avatar = FileField(validators=[FileRequired(), FileAllowed(["jpg", "png", "gif"], message="文件不符合格式呢?")])
        describe = StringField(validators=[InputRequired()])
    
    
    @app.route("/upload", methods=["GET", "POST"])
    def get_img():
        if request.method == "GET":
            return render_template("upload.html")
        else:
            # 因为我们既有文件类型,还有一般的表单类型。因此需要把两者组合在一块
            form = UpLoadForm(CombinedMultiDict([request.form, request.files]))
            if form.validate():
                avatar = form.avatar.data  # 或者avatar = request.files.get("avatar")
                describe = form.describe.data  # 或者describe= request.form.get("describe")
                return f"上传成功,上传的文件为:{avatar.filename}, 描述信息为:{describe}"
            else:
                return f"error occurred:{form.errors}"
    
    
    if __name__ == "__main__":
        app.run(port=8888, debug=True)
    



    8.flask抵御csrf

    什么是csrf

    使用flask-wtf开启csrf保护

    from flask_wtf.csrf import CsrfProtect
    # 开启csrf保护 
    CsrfProtect(app)
    

    注意,需要为CSRF保护设置一个密钥,但通常情况下,和Flask应用的SECRET_KEY是一样的。如果你设置的模板中存在表单,你只需要在表单中添加如下

    <form method="post" action="/">
        {{ form.csrf_token() }}
    </form>
    

    如果没有模板中没有表单,你仍然需要一个 CSRF 令牌:

    <form method="post" action="/">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
    </form>
    

    如果网站没有通过CSRF验证,都会返回400响应,我们可以自定义这个错误响应:

    from flask_wtf.csrf import CsrfProtect
     
    csrf = CsrfProtect()
     
     
    @csrf.error_handler
    def csrf_error(reason):
        return render_template('csrf_error.html', reason=reason)
    

    这里官方强烈建议对所有视图启用CSRF保护,也提供了给某些视图函数不需要保护的装饰器

    @csrf.exempt  # 不对index进行保护
    @app.route('/foo', methods=('GET', 'POST'))
    def index():
        return "index"
    

    你也可以在所有的视图中禁用CSRF保护,app.config中通过设置 WTF_CSRF_CHECK_DEFAULTFalse,仅仅当你需要保护的时候选择调用csrf.protect()手动开启保护

  • 相关阅读:
    python测试开发django-1.开始hello world!
    python基础--杂项
    Python基础----函数
    python介绍
    公共Webservice
    divmod(a,b)函数
    模块知识
    第三周作业 修改配置文件
    rsync在windows和linux同步数据的配置过程
    docker学习笔记
  • 原文地址:https://www.cnblogs.com/traditional/p/11222896.html
Copyright © 2011-2022 走看看