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便可html

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)這一步都作了些什麼python

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目錄,裏面多了兩個文件redis

固然我這裏沒有設置session,咱們來設置一下sql

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,再訪問/indexmongodb

能夠看到是成功的。此時的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保護,文件上傳等等flask

<!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經常使用驗證器

經常使用的驗證器: 數據發送過來,通過表單驗證,所以須要驗證器來進行驗證,如下是一些經常使用的驗證器cookie

  • 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"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.使用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()手動開啓保護

相關文章
相關標籤/搜索