1. 程式人生 > 實用技巧 >蘑菇APP開發前置二

蘑菇APP開發前置二

1.請求鉤子

在客戶端和伺服器互動的過程中,我們有些工作總是需要在一開始或者結束的時候進行,例如:

1.請求開始的時候建立資料庫連線

2.請求開始時根據需求進行許可權校驗

3.請求結束的時指定資料的互動格式

為了避免寫重複功能,flask提供了四種請求鉤子供我們使用

before_first_request處理第一個請求前執行(初始化鉤子)

before_request每次請求前執行,如果返回了一個響應,將不再有效

after_request如果沒丟擲錯誤,每次請求後執行,接受檢視函式做出的相應作為引數,再次函式中可以對響應值返回之前做最後一步處理,必須返回響應值response

teardown_request每次請求後執行,接受錯誤資訊,需要設定flask的配置DEBUG=False,teardown_request才會接受到異常物件

...
@app.before_first_request
def before_first_request():
    """
    這個鉤子會在專案啟動後第一次被使用者訪問時執行
    可以編寫一些初始化專案的程式碼,例如,資料庫初始化,載入一些可以延後引入的全域性配置
    """
    print("----before_first_request----")
    print("系統初始化的時候,執行這個鉤子方法")
    print("會在接收到第一個客戶端請求時,執行這裡的程式碼")

@app.before_request
def before_request():
    
""" 這個鉤子會在每次客戶端訪問檢視的時候執行 # 可以在請求之前進行使用者的身份識別,以及對於本次訪問的使用者許可權等進行判斷。.. """ print("----before_request----") print("每一次接收到客戶端請求時,執行這個鉤子方法") print("一般可以用來判斷許可權,或者轉換路由引數或者預處理客戶端請求的資料") @app.after_request def after_request(response): print("----after_request----") print("在處理請求以後,執行這個鉤子方法
") print("一般可以用於記錄會員/管理員的操作歷史,瀏覽歷史,清理收尾的工作") response.headers["Content-Type"] = "application/json" response.headers["Company"] = "python oldboy..." # 必須返回response引數 return response @app.teardown_request def teardown_request(exc): print("----teardown_request----") print("在每一次請求以後,執行這個鉤子方法") print("如果有異常錯誤,則會傳遞錯誤異常物件到當前方法的引數中") # 在專案關閉了DEBUG模式以後,則異常資訊就會被傳遞到exc中,我們可以記錄異常資訊到日誌檔案中 print(exc) # 編寫路由檢視 @app.route(rule='/') def index(): print("-----------檢視函式執行了---------------") return "<h1>hello world!</h1>" if __name__ == '__main__': # 執行flask app.run(host="0.0.0.0")

2.異常捕獲

主動丟擲HTTP異常

通過abort來主動丟擲異常 abort(404)但是使用較少(其實壓根沒見過)主要還是介紹捕獲錯誤

捕獲錯誤

errorhandler裝飾器

@app.errorhandler(500)
def internal_server_error(e):
    return '想找也找不到了'
#捕獲指定異常型別
@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
    return '除數不能為0'

"""
flask中內建了app.errorhander提供給我們捕獲異常,實現一些在業務發生錯誤時的自定義處理。
1. 通過http狀態碼捕獲異常資訊
2. 通過異常類進行異常捕獲
"""

"""1. 捕獲http異常[在裝飾器中寫上要捕獲的異常狀態碼也可以是異常類]"""
@app.errorhandler(404)
def error_404(e):
    return "<h1>您訪問的頁面失聯了!</h1>"
    # return redirect("/")

"""2. 捕獲系統異常或者自定義異常"""
class APIError(Exception):
    pass

@app.route("/")
def index():
    raise APIError("api介面呼叫引數有誤!")
    return "個人中心,檢視執行了!!"

@app.errorhandler(APIError)
def error_apierror(e):
    return "錯誤: %s" % e

if __name__ == '__main__':
    app.run(host="localhost",port=8080)
context

執行上下文:即語境,語意,在程式中可以理解為在程式碼執行到某一行時,根據之前程式碼所做的操作以及下文即將要執行的邏輯,可以決定在當前時刻下可以使用到的變數,或者可以完成的事情。

Flask中上下文物件:相當於一個容器,儲存了 Flask 程式執行過程中的一些資訊[變數、函式、類與物件等資訊]。

Flask中有兩種上下文,請求上下文(request context)和應用上下文(application context)。

  1. application 指的就是當你呼叫app = Flask(__name__)建立的這個物件app

  2. request 指的是每次http請求發生時,WSGI server(比如gunicorn)呼叫Flask.__call__()之後,在Flask物件內部建立的Request物件;

  3. application 表示用於響應WSGI請求的應用本身,request 表示每次http請求;

  4. application的生命週期大於request,一個application存活期間,可能發生多次http請求,所以,也就會有多個request

請求上下文(request context)

我們在使用request.方法的時候是如何火的請求的相關資料的呢?例如請求方式,請求地址等等

request

  • 封裝了HTTP請求的內容,針對的是http請求。舉例:user = request.args.get('user'),獲取的是get請求的引數。

session

  • 用來記錄請求會話中的資訊,針對的是使用者資訊。舉例:session['name'] = user.id,可以記錄使用者資訊。還可以通過session.get('name')獲取使用者資訊。

請求上下文提供的變數/屬性/方法/函式/類與物件,只能在檢視中或者被檢視呼叫的地方使用

應用上下文(application context)

application並不是一直存在的,雖然廣義上來講application要相較於request存在的時間更久,但是實際上application context 是伴隨著 request context存在而存在 消亡而消亡的。

應用上下文的物件有:current_app,g

current_app:用於儲存應用中的變數

g變數作為flask程式全域性的一個臨時變數,充當媒介作用,來傳遞資料,不同的請求會有不同的全域性變數g

所以總而言之請求上下文:儲存了客戶端和伺服器互動的資料,一般來自於客戶端。應用上下文:flask 應用程式執行過程中,儲存的一些配置資訊,比如路由列表,程式名、資料庫連線、應用資訊等

4.flask_script擴充套件

安裝

pip install flask_script

整合flask_script到flask應用中可以是我們在執行檔案之外像是呼叫django中manage那樣通過命令來執行一些指令碼,在之後的資料庫遷移中我們將接觸到其使用,現在我們來介紹一下簡單的註冊

from flask_script import Manager
manage = Manager(app)   #通過這種方式來進行註冊 讓我們可以執行指令碼一樣去執行flask

5.jinja2模板引擎

flask內建的模板語言,設計思想來自於django的模板引擎,Flask提供的 render_template 函式封裝了該模板引擎,render_template 函式的第一個引數是模板的檔名,後面的引數都是鍵值對,表示模板中變數對應的真實值。

使用

app = Flask(__name__,template_folder='templates')

在檢視函式中渲染資料(你已經設定好了一個模板,這個還要我來教???)

from flask import Flask, render_template
# 初始化
app = Flask(import_name=__name__,template_folder='templates')

# 配置終端指令碼執行專案
from flask_script import Manager
manager = Manager(app)

# 宣告和載入配置
class Config():
    DEBUG = True
app.config.from_object(Config)

# 編寫路由檢視
@app.route(rule='/')
def index():
    data={}
    data["title"] = "我的flask專案"
    return render_template("index.html",**data)

if __name__ == '__main__':
    # 執行flask
    manager.run()

接下來我們來介紹一下模板語法

輸出變數

{{}} 來表示變數名,這種 {{}} 語法叫做 變數程式碼塊

Jinja2 模版中的變數程式碼塊可以是任意 Python 型別或者物件,只要它能夠被 Python 的 __str__ 方法或者str()轉換為一個字串就可以

模板中特有的變數和函式

config :可以從末班中直接訪問flask當前的config物件

{{config.SQLALCHEMY_DATABASE_URI}} #獲取flask中相關配置

request:代表著當前請求的request物件

{{request.url}}  #request 物件通過點方法呼叫屬性

session:flask的session物件,顯示session資料

{{session.new}}  #session物件 通過點屬性名呼叫相關屬性

g:flask中的g變數

{{g.name}}   #g變數

url_for():重定向會根據傳入的路由器函式名。返回該路由對應的url,在模板中始終使用url_for()就可以安全的修改路由繫結的url

{{url_for('home')}}   #無引數
{{url_for('home',user_id=1)}}   #有引數
流程控制

Jinja2 語法中的if語句跟 Python 中的 if 語句相似,後面的布林值或返回布林值的表示式將決定程式碼中的哪個流程會被執行.

用 {%%} 定義的控制程式碼塊,可以實現一些語言層次的功能,比如迴圈或者if語句

 if: {# 判斷一個引數是否是奇數 #}
    {% if name % 2 == 0 %}
        偶數<br>
    {% else %}
        奇數<br>
    {% endif %}

for: {# for迴圈 #}
{% for book in book_list %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.title }}</td>
<td>{{ book.price }}</td>
</tr>
{% endfor %}

過濾器:

過濾器本質上就是函式,有時候我們需要在一堆資料中挑選出一些特殊的資料,這是我們需要過濾器發揮它的作用

過濾器的使用:變數名|過濾器
{{variable | filter_name(args1,args2,....)}}
如果沒有引數傳遞()可以省略
{{variable | filter_name }}


例如
{{ "hello world" | reverse | upper }}  #轉換為反轉並大寫
常見過濾器
字串操作
safe:禁用轉義  <p>{{ '<em>hello</em>' | safe }}</p>
capitalize:把變數值的首字母轉成大寫,其餘字母轉小寫<p>{{ 'hello' | capitalize }}</p>
lower:把值轉成小寫  <p>{{ 'HELLO' | lower }}</p>
upper:把值轉成大寫  <p>{{ 'hello' | upper }}</p>
title:把值中的每個單詞的首字母都轉成大寫   <p>{{ 'hello' | title }}</p>
reverse:字串反轉   <p>{{ 'olleh' | reverse }}</p>
format:格式化輸出  format:格式化輸出   <p>{{ '%s is %d' | format('name',17) }}</p>
striptags:渲染之前把值中所有的HTML標籤都刪掉 
                <p>{{ '<em>hello</em>' | striptags }}</p> #注意如果存在大小於號容易誤刪
                <p>{{ "如果x<y,z>x,那麼x和z之間是否相等?" | striptags }}</p>
truncate: 字串截斷   <p>{{ 'hello every one' | truncate(9)}}</p>

列表操作
first:取第一個元素 <p>{{ [1,2,3,4,5,6] | first }}</p>
last:取最後一個元素 <p>{{ [1,2,3,4,5,6] | last }}</p>
length:獲取列表長度 <p>{{ [1,2,3,4,5,6] | length }}</p>
sum:列表求和 <p>{{ [1,2,3,4,5,6] | sum }}</p>
sort:列表排序 <p>{{ [6,2,3,1,5,4] | sort }}</p>

語句塊過濾
{% filter upper %}
#一大堆文字#
{% endfilter %}

自定義過濾器

當內建過濾器不能滿足我們的要求的時候我們可以自己設定,因為過濾器本質上就是函式

我們有兩種方式來自定義過濾器

1.通過flask中的add_template_filter方法

2.裝飾器

注意:如果我們自定義的過濾器和內建過濾器重名的話會覆蓋

方式一
def do_list_reverse(old_list):
    new_list = list(old_list)
    new_list.reverse()
    return new_list
app.add_template_filter(do_list_reverse,"rev")   #註冊過濾器
方式二
@app.template_filter('lrev')
def do_list_reverse(old_list):
    # 因為字典/列表是屬於複合型別的資料,所以改動資料的結構,也會應該能影響到原來的變數
    # 通過list新建一個列表進行操作,就不會影響到原來的資料
    new_list = list(old_list)
    new_list.reverse()
    return new_list
模板繼承

在專案中,可能會遇到以下情況:

  • 多個模板具有完全相同的頂部和底部內容

  • 多個模板中具有相同的模板程式碼內容,但是內容中部分值不一樣

  • 多個模板中具有完全相同的 html 程式碼塊內容

像遇到這種情況,可以使用 JinJa2 模板中的 繼承 來進行實現

模板繼承是為了重用模板中的公共內容。一般Web開發中,繼承主要使用在網站的頂部選單、底部選單版權資訊,或彈出視窗。這些內容可以定義在父模板中,子模板直接繼承,而不需要重複書寫。

標籤定義的內容{% block top %} {% endblock %}

{% block top %} {% endblock %}
  • 相當於在父模板中挖個坑,當子模板繼承父模板時,可以進行填充。

  • 子模板使用 extends 指令宣告這個模板繼承自哪個模板

  • 父模板中定義的塊在子模板中被重新定義,在子模板中呼叫父模板的內容可以使用super()

模板繼承使用時注意點:

  1. 不支援多繼承,一個子模板只能使用一次extends繼承父模板

  2. 為了便於閱讀,在子模板中使用extends時,儘量寫在模板的第一行。

  3. 不能在一個模板檔案中定義多個相同名字的block標籤。重複則報錯。

  4. 當在頁面中使用多個block標籤時,建議給結束標籤起個名字,當多個block巢狀時,閱讀性更好。

舉個例子

{% extends "base.html" %}
{% block style %}
<style>
body{
    background: #aabbcc;
}
</style>
{% endblock %}
{% block head %}
    <div>當前index2.html編寫的頭部內容</div>
{% endblock %}

{% block right %}
    <div>當前index2.html編寫的主體右邊內容</div>
{% endblock %}
繼承進階用法

模板繼承還提供了同一模板相互呼叫的操作和在繼承父級模板時繼承父模板內容的操作

父模板
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {% block header %}
        <div>頭部內容</div>
        {% block nav %}
        <div>導航選單內容</div>
        {% endblock %}
    {% endblock %}

    {% block main %}
        <div>base裡面編寫的公共主體內容</div>
    {% endblock %}

    {% block footer %}
    <div>腳部內容</div>
        {# 呼叫當前模板中的其他block板塊 #}
        {{ self.nav() }}
    {% endblock %}
</body>
</html>
            
            
子模板
{% extends "base2.html" %}

{% block main %}
    <div>index3.html編寫的主體內容</div>
    {# 重寫父模板內容時,如果希望繼承原來父模板中的block模組,可以使用supper() #}
    {{ super() }}
{% endblock %}   
include載入子模板
{% include "header.html" %}
<div>index4.html的主體內容</div>
{% include "footer.html" %}
模板巨集

所謂的巨集就是在模板中預先寫好的一段類似函式的程式碼,用於一些內容上可能相似度不高但是結構上是大體相似的

模板
{% macro func(text, name,type="text") %}
    <div>
    {{ text }}: <input type="{{ type }}" name="{{ name }}" />
    </div>
{% endmacro %}

模板巨集的呼叫需要使用import “模板巨集所在的檔名.html as 別名變數”進行匯入巨集,通過{{ 別名變數.函式名() }}進行呼叫。

{% import "macro.html" as macro %}
    {{ macro.func("使用者名稱","username") }}
    {{ macro.func("密碼","password","password") }}
在 Flask 專案中防範 CSRF 攻擊

在 Flask 中, Flask-wtf 擴充套件有一套完善的 csrf 防護體系,對於我們開發者來說,使用起來非常簡單

pip install flask_wtf   #安裝
# 1. session加密的時候已經配置過了.如果沒有在配置項中設定,則如下:
app.secret_key = "#此處可以寫隨機字串#"

# 2. 也可以寫在配置類中。
class Config(object):
    DEBUG = True
    SECRET_KEY = "dsad32DASSLD*13%^32"
 

from flask.ext.wtf import CSRFProtect  #導包
CSRFProtect(app)   #註冊


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