サードパーティのコンポーネントを4.flask

1.flaskセッションの使用

在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共通バリ

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

  • メール:データがアップロードされているかどうかメールボックスを確認してください
  • EqualTo:データをアップロードし、追加のフィールドが2つのフィールドが等しい等しく、共通パスワードと確認しているかどうかを確認します
  • InputRequired:フィールドは記入しない限り、それはによって満たされるよう、必要であることを示しては失敗です
  • 長さ:制限の長さ、二つの値の最小値と最大値の制限があり、入力された文字列の長さを表している[最小、最大]
  • NumberRange:それは満足これら二つの数字の間にある場合にはデジタル部、およびMIX・MAXは、2つの限界値を持っていますが、数字でなければなりません
  • 正規表現:カスタムの正規表現
  • 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"1\d{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.ファイルアップロードの検証フラスコ-WTF

我々は、ユーザーがアップロードしたファイルを受信する前に、何の裁判官はありません、ユーザーがそれを行うにはどのようにTXT、またはPYファイルをアップロードすることができれば、例えば、ユーザーはアバターをアップロードし、絵すべきですか?我々は、ユーザーがアップロードしたファイルなければならないこの時間が判断されます。フラスコ-WTFファイルの検証に特化したサードパーティ製プラグインです。また、あなたはフラスコ-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)



CSRFに対する8.flask

CSRFとは何ですか

フラスコ-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_DEFAULT あります False,仅仅当你需要保护的时候选择调用csrf.protect()手动开启保护

おすすめ

転載: www.cnblogs.com/traditional/p/11222896.html