Flask基礎

嗨,阿良發表於2020-08-06

配置檔案

配置檔案匯入原理

根據 "settings.DevelopmentConfig" 這個字串形式的路徑可以匯入 "settings" 這個模組,並且可以找到模組中的 "DevelopmentConfig" 這個類,還可以根據 "dir" 找到 "DevelopmentConfig" 類中的大寫的靜態欄位

import importlib


path = "settings.DevelopmentConfig"

p, c = path.rsplit('.', maxsplit=1)
m = importlib.import_module(p)
cls = getattr(m, c)


# 如何根據cls這個類獲取到類中的靜態欄位

for key in dir(cls):
    if key.isupper():
        print(key, getattr(cls, key))     

settings.py 配置檔案

import datetime


class Config(object):
    TESTING = False
    PROPAGATE_EXCEPTIONS = None
    PRESERVE_CONTEXT_ON_EXCEPTION = None
    SECRET_KEY = None
    PERMANENT_SESSION_LIFETIME = datetime.timedelta(days=31)
    USE_X_SENDFILE = False
    SERVER_NAME = None
    APPLICATION_ROOT = '/'
    SESSION_COOKIE_NAME = 'session'
    SESSION_COOKIE_DOMAIN = None
    SESSION_COOKIE_PATH = None
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SECURE = False
    SESSION_COOKIE_SAMESITE = None
    SESSION_REFRESH_EACH_REQUEST = True
    MAX_CONTENT_LENGTH = None
    SEND_FILE_MAX_AGE_DEFAULT = datetime.timedelta(seconds=43200)
    TRAP_BAD_REQUEST_ERRORS = None
    TRAP_HTTP_EXCEPTIONS = False
    EXPLAIN_TEMPLATE_LOADING = False
    PREFERRED_URL_SCHEME = 'http'
    JSON_AS_ASCII = True
    JSON_SORT_KEYS = True
    JSONIFY_PRETTYPRINT_REGULAR = False
    JSONIFY_MIMETYPE = 'application/json'
    TEMPLATES_AUTO_RELOAD = None
    MAX_COOKIE_SIZE = 4093


class ProductionConfig(Config):
    DEBUG = False


class DevelopmentConfig(Config):
    DEBUG = True

app.config.from_object 匯入配置資訊

app.py 中可以使用按需修改後settings.py 中的配置資訊

from flask import Flask

app = Flask(__name__)

app.config.from_object("settings.DevelopmentConfig")

路由系統

flask 中的路由url是依據帶引數的裝飾器寫的

  • endpoint="n1" 相當於 django 路由系統 url 中的 name 起別名進行反向解析
  • 如果不寫 endpoint="..." 則預設為:url_for("index") 檢視函式名稱 index
  • flask 路由反向解析用的是 url_for("n1")
  • 動態路由
    • /<int:nid> 動態接收 id 值(int型別),可以傳參
    • /<nid> 未定義型別則預設接收 string 字串型別
from flask import Flask
from flask import url_for


@app.route('/index/<int:nid>', methods=["GET", "POST"], endpoint="n1")
# endpoint="n1" 相當於 django 路由系統 url 中的 name 起別名進行反向解析
# 如果不寫 endpoint="..." 則預設為:url_for("index") 檢視函式名稱 index
# flask 路由反向解析用的是 url_for("n1")

# 動態路由
# '/<int:nid>' 動態接收 id 值(int型別),可以傳參
# '/<nid>' 未定義型別則預設接收 string 字串型別
def index(nid):
    print(url_for("n1", nid=1))
    # /index/1
    return "Index"

檢視

FBV 模式

import functools
from flask import Flask
from flask import render_template
from flask import request
from flask import redirect
from flask import session
from flask import url_for
from flask import Markup
from flask import flash
from flask import get_flashed_messages

app = Flask(__name__)
app.config.from_object("settings.DevelopmentConfig")

# 模擬資料庫中儲存的使用者
STUDENT_DICT = {
    1: {'name': '小明', 'age': 18, 'gender': '男'},
    2: {'name': '小李', 'age': 19, 'gender': '男'},
    3: {'name': '小花', 'age': 17, 'gender': '女'},
}


# def auth(func):
#     # '@functools.wraps(func)' 加上這個裝飾器,可以避免被裝飾的函式名變為'inner'
#     @functools.wraps(func)
#     def inner(*args, **kwargs):
#         if not session.get('user'):
#             return redirect(url_for('login'))
#         ret = func(*args, **kwargs)
#         return ret
#     return inner


# 被'@app.before_request' 此裝飾器裝飾的業務邏輯會在所有檢視函式執行之前執行
# 跟 'django' 的 'process_request' 極為相似
# 當被此裝飾器裝飾的業務邏輯有返回值時,整個flask框架中的所有檢視函式都不會被執行
# 直接顯示 "被此裝飾器裝飾的業務邏輯的返回值"
@app.before_request
def auth():
    if request.path == '/login':
        return None
    if session.get('user'):
        return None
    return redirect(url_for('login'))


# 被此全域性模板函式裝飾器所裝飾的業務邏輯,不需要藉助任何檢視函式傳遞此方法給模板,
# 此方法會自動傳遞到需要應用此方法的模板中!
# 模板只需要呼叫即可 {{global_template_func(1, 2)}} 即可在頁面相應的位置顯示出 3
@app.template_global()
def global_template_func(a, b):
    return a + b


# {{ 1|filter_template_func(2, 3) }}
# 頁面顯示 6
@app.template_filter()
def filter_template_func(a, b, c):
    return a + b + c


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        user = request.form.get('user')
        password = request.form.get('password')
        if user == 'aliang' and password == '007':
            session['user'] = user
            return redirect(url_for('index'))
        return render_template('login.html', error='使用者名稱或密碼錯誤!')
    return render_template('login.html')


@app.route('/index')
# @auth
def index():
    return render_template('index.html', stu_dic=STUDENT_DICT)


@app.route('/delete/<int:nid>')
# @auth
def delete(nid):
    del STUDENT_DICT[nid]
    return redirect(url_for('index'))


@app.route('/detail/<int:nid>')
# @auth
def detail(nid):
    info = STUDENT_DICT[nid]
    return render_template('detail.html', info=info)


def func(arg):
    return arg + 1


@app.route('/tpl')
def tpl():
    context = {
        'users': ['小明', '小李', '小花'],
        # 'txt': "<input type='text' />",
        'txt': Markup("<input type='text' />"),
        'func': func
    }
    return render_template('tpl.html', **context)


@app.route('/page1')
def page1():
    flash('儲存臨時資料')

    return "Session"


@app.route('/page2')
def page2():
    flash_msg = get_flashed_messages()

    return flash_msg


class Middleware(object):
    def __init__(self, origin_wsgi_app):
        self.origin_wsgi_app = origin_wsgi_app

    def __call__(self, *args, **kwargs):
        # 在請求之前做相應的業務邏輯

        ret = self.origin_wsgi_app(*args, **kwargs)

        # 在請求之後做相應的業務邏輯

        return ret


if __name__ == '__main__':
    app.wsgi_app = Middleware(app.wsgi_app)
    app.run()

請求相關

request

request.method

requestd.args

request.form

request.values

requesdt.cookies

request.headers

request.path

request.full_path

request.script_root

request.url

request.base_url

request.url_root

request.host_url

request.host

request.files

obj = request.files['the_file_name']
obj.save('/var/www/uploads/' + secure_filename(f.filename))

響應相關

return

from flask import render_template
from flask import redirect
from flask import jsonify


# 以下4種返回的資料都是請求體,能不能將請求頭也返回?可以封裝!

dic = {'k1': 'v1'}

# return json.dumps(dic)
# 相當於"django"中的JsonResponse
return jsonify(dic)

return "Index"

return render_template("index.html")

return redirect("/index")

封裝<響應頭>&<響應體>後再返回

定製返回的響應頭

from flask import make_response

# 將要返回的字串"Index"封裝到"make_response"中
# 設定了響應體
obj = make_response("Index")

# 設定了響應頭
obj.headers['xxxx'] = 'oooo'

# 設定cookie資訊
obj.set_cookie('key', 'value')

return obj

模板渲染

flask 模板渲染語法跟python語法極其相似

檢視傳參——>模板渲染

# app.py 檔案中
from flask import Markup


def func(arg):
    return arg + 1


@app.route('/tpl')
def tpl():
    context = {
        'users': ['小明', '小李', '小花'],
        # 'txt': "<input type='text' />",
        'txt': Markup("<input type='text' />"),
        'func': func
    }
    return render_template('tpl.html', **context)

tpl.html 模板檔案中

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    
{{users.0}}
{{users[0]}}
{{txt}}
{{txt|safe}}
{{func(6)}}
    
</body>
</html>

@app.template_global() 全域性模板函式裝飾器之一

被此全域性模板函式裝飾器所裝飾的業務邏輯,不需要藉助任何檢視函式傳遞此方法給模板,
此方法會自動傳遞到需要應用此方法的模板中!

模板只需要呼叫即可 {{global_template_func(1, 2)}} 即可在頁面相應的位置顯示出 3

# app.py 檔案中

@app.template_global()
def global_template_func(a, b):
    return (a + b)

tpl.html 模板檔案中

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

{{global_template_func(1, 2)}}
    
</body>
</html>

@app.template_filter() 全域性模板函式裝飾器之二

在模板中這樣呼叫:{{ 1|filter_template_func(2, 3) }}
則在頁面相應位置顯示 6

# app.py 檔案中

@app.template_filter()
def filter_template_func(a, b, c):
    return a + b + c

tpl.html 模板檔案中

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

{{1|filter_template_func(2, 3)}}
 
{% if 1|filter_template_func(2, 3) %}
    <div>返回值為6</div>
{% else %}
    <div>沒有返回值</div>
{% endif %}
    
</body>
</html>

模板繼承——> extends "layout.html"

layout.html 模板檔案中

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {% block content%}
    
    {% endblock %}
</body>
</html>

tpl.html 繼承 layout.html 模板檔案

{% extends "layout.html"%}

{% block content%}
    {{users.0}}
	{{users[0]}}
	{{txt}}
	{{txt|safe}}
	{{func(6)}}
{% endblock %}

模板外掛匯入——> include "nav.html"

nav.html 模板外掛——>導航欄等外掛

......

tpl.html 檔案中

{% extends "layout.html"%}

{% block content%}
	{% include "nav.html" %}
    {{users.0}}
	{{users[0]}}
	{{txt}}
	{{txt|safe}}
	{{func(6)}}
{% endblock %}

模板巨集定義——>{% macro ...%} ... {% endmacro %}

適用於某個標籤在整個頁面上多次使用,可以按照巨集定義的方式將此標籤定義為一個程式碼塊,在需要使用時直接呼叫即可!就不需要重寫很多遍了!

模板巨集定義的意思是可以在模板中定義一個程式碼塊,程式碼塊中可以按照python函式的定義方式去定義某個需要在特定時刻使用的標籤

在需要顯示此標籤時,直接呼叫即可

如下:

{% macro tag(name, type='text', value='') %}
	<h1>巨集定義input標籤</h1>
	<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
	<input type="submit" value="提交">
{% endmacro %}

{{tag("user")}}

Session

  • django 中的session資訊放在資料庫中

  • flask 中的session資訊經過加密之後儲存在使用者瀏覽器的cookie中

利用session儲存使用者的登入狀態

可以做許可權管理

  • 方法一:每個需要登入後才可以訪問的頁面所對應的檢視函式中都查詢一次是否有session資訊

  • 方法二:利用裝飾器,在每個需要登入後才可以訪問的頁面所對應的檢視函式上加裝飾器,在走相應的檢視函式之前先走裝飾器中定義好的判斷是否存在session資訊的業務邏輯

    • 裝飾器(應用於被裝飾的檢視函式較少時

      import functools
      
      
      def auth(func):
          @functools.wraps(func)  # 加上這個裝飾器,可以避免被裝飾的函式名變為'inner'
          def inner(*args, **kwargs):
              if not session.get('user'):
                  return redirect(url_for('login'))
              ret = func(*args, **kwargs)
              return ret
          return inner
          
      
  • 方法三:使用 '@app.before_request' 裝飾器
    被'@app.before_request' 此裝飾器裝飾的業務邏輯會在所有檢視函式執行之前執行,跟 'django' 的 'process_request' 極為相似

當被此裝飾器裝飾的業務邏輯有返回值時,整個flask框架中的所有檢視函式都不會被執行,但所有被 '@app.after_request' 裝飾器裝飾的業務邏輯都會按照定義好的順序倒序執行直接顯示 "被此裝飾器裝飾的業務邏輯的返回值"

當被此裝飾器裝飾的業務邏輯中返回 None 時,其他的檢視才能正常執行

@app.before_request
def auth():
    if request.path == '/login':
        return None
    if session.get('user'):
        return None
    return redirect(url_for('login'))

閃現——>flash

基於session資訊的設定原理封裝好方法:<臨時存>&<臨時取>

原理:在session中儲存一個資料,讀取一次之後,通過pop將資料移除,可以達到存一次取一次即失效的效果!

  • flask中的session 資訊是經過加密後儲存在cookie中的,可以一直取,只要不過期,不手動刪除,則session資訊一直存在!

  • 藉助 flash 儲存某一臨時資料 & get_flashed_messages 獲取臨時資料,取一次即失效

    • 臨時存,臨時取
    • 取出來的是列表型別的資料,第二次取得就是空列表
from flask import flash
from flask import get_flashed_messages


@app.route('/page1')
def page1():
    flash('儲存臨時資料')

    return "Session"


@app.route('/page2')
def page2():
    flash_msg = get_flashed_messages()

    return flash_msg

# ['儲存臨時資料']

分類臨時存,按分類臨時取

from flask import flash
from flask import get_flashed_messages


@app.route('/page1')
def page1():
    flash('儲存臨時資料1', '類別1')
    flash('儲存臨時資料2', '類別1')
    flash('儲存臨時資料3', '類別2')

    return "Session"


@app.route('/page2')
def page2():
    flash_msg = get_flashed_messages(category_filter=['類別1'])

    return flash_msg


# ['儲存臨時資料1', '儲存臨時資料2']

中介軟體

跟django的中介軟體的實現有較大區別

flask 框架開始執行流程

  • flask 專案啟動介面

    if __name__ == '__main__':
        app.run()
    
  • 執行 run 方法後使得服務端處於監聽狀態

  • 當有請求連線時,

    try:
        run_simple(host, port, self, **options)
    

    run_simple(host, port, self, **options) 中的第三個引數 self() 即 flask 的例項化物件 app() 會執行類的 __call__ 方法

    def __call__(self, environ, start_response):
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
        return self.wsgi_app(environ, start_response)
    

即在客戶端請求連線時,基於flask框架的專案服務端會執行 __call__ 方法

而我們知道中介軟體的作用就是在請求通過wsgi封裝後經過路由匹配執行相應的檢視函式再返回相應的模板頁面之前做相應的業務邏輯,比如:傳送某一請求訪問特定的頁面需要先登入之後才可以正常訪問。

所以,flask 框架中的中介軟體即在 __call__ 方法執行之前做相關定義

定義中介軟體

flask 框架中的中介軟體即在 Flask 類的 __call__ 方法執行之前做相關定義

class Middleware(object):
    def __init__(self, origin_wsgi_app):
        self.origin_wsgi_app = origin_wsgi_app

    def __call__(self, *args, **kwargs):
        # 在請求之前做相應的業務邏輯

        ret = self.origin_wsgi_app(*args, **kwargs)

        # 在請求之後做相應的業務邏輯

        return ret
        

if __name__ == '__main__':
    app.wsgi_app = Middleware(app.wsgi_app)
    app.run()

特殊的裝飾器

類比於django的中介軟體

@app.before_first_request

被'@app.before_first_request' 此裝飾器裝飾的業務邏輯會在 '@app.before_request' 裝飾的業務邏輯之前僅執行一次

@app.before_first_request
def excute_first():
    print("專案啟動之後第一次請求到來時,會在@app.before_request裝飾的業務邏輯之前僅僅執行一次")

@app.before_request

被'@app.before_request' 此裝飾器裝飾的業務邏輯會在所有檢視函式執行之前執行,


跟 'django' 的 'process_request' 極為相似


當被此裝飾器裝飾的業務邏輯有返回值時,整個flask框架中的所有檢視函式都不會被執行,但所有被 '@app.after_request' 裝飾器裝飾的業務邏輯都會按照定義順序倒序執行後直接顯示 "被此裝飾器裝飾的業務邏輯的返回值"


當被此裝飾器裝飾的業務邏輯沒有返回值即返回 None 時,請求按流程正常執行!

@app.before_request
def auth():
    print('請先登入!')

@app.after_request

被'@app.after_request' 此裝飾器裝飾的業務邏輯會在所有檢視函式執行之後執行跟 'django' 的 'process_response' 極為相似


被裝飾的業務邏輯必須要有返回值,若沒有返回值,程式會報錯

@app.after_request
def after(response):
    print('請求已經得到響應!')
    return response

@app.template_global() 全域性模板函式裝飾器之一

被此全域性模板函式裝飾器所裝飾的業務邏輯,不需要藉助任何檢視函式傳遞此方法給模板,此方法會自動傳遞到需要應用此方法的模板中!


模板只需要呼叫即可 {{global_template_func(1, 2)}} 即可在頁面相應的位置顯示出 3

# app.py 檔案中

@app.template_global()
def global_template_func(a, b):
    return (a + b)

tpl.html 模板檔案中

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

{{global_template_func(1, 2)}}
    
</body>
</html>

@app.template_filter() 全域性模板函式裝飾器之二

在模板中這樣呼叫:{{ 1|filter_template_func(2, 3) }}
則在頁面相應位置顯示 6

# app.py 檔案中

@app.template_filter()
def filter_template_func(a, b, c):
    return a + b + c

tpl.html 模板檔案中

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

{{1|filter_template_func(2, 3)}}
 
{% if 1|filter_template_func(2, 3) %}
    <div>返回值為6</div>
{% else %}
    <div>沒有返回值</div>
{% endif %}
    
</body>
</html>

@app.errorhandler

被'@app.errorhandler(404)' 此裝飾器裝飾的業務邏輯可以自定義訪問出錯後的錯誤資訊展示

@app.errorhandler(404)
def not_found(error_msg):
    print(error_msg)
    return "訪問的頁面不存在!"

相關文章