1. 程式人生 > >Flask檢視、內容、模板

Flask檢視、內容、模板

文章目錄

Flask獲取請求相關資訊

from flask import Flask, request

app =
Flask(__name__) @app.route('/', methods=["GET", "POST"]) def hello_world(): # - 1. request.data: 獲取的是以post提交的, 非表單資料 print(request.data) # - 2. request.form: 獲取的是以post提交的, 表單資料 print(request.form) # - 3. request.args: 獲取的是查詢引數, 一般是GET請求, 獲取的是問號後面拼接的引數 print(request.args) # - 4. request.method: 獲取的是請求方式 print(request.method) # - 5. request.url: 獲取的是瀏覽器的請求地址 print(request.url) return "helloworld" if __name__ == '__main__': app.run(debug=True)

其中,request.args獲取到的是連結問號後的引數,此引數一般都是鍵值對形式存在的。要想獲取鍵值對的值,一般有兩種方式:

  • request.args[“key”]
  • request.args.get(“key”)

一般,我們採用第二種方式,因為第一種方式若key不存在,則會報錯,第二種方式若key不存在,則返回None


請求鉤子(request-hook)

在客戶端和伺服器互動的過程中,有些準備工作或掃尾工作需要處理,比如:

  • 在請求開始時,建立資料庫連線;
  • 在請求開始時,根據需求進行許可權校驗;
  • 在請求結束時,指定資料的互動格式;

為了讓每個檢視函式避免編寫重複功能的程式碼,Flask提供了通用設施的功能,即請求鉤子。

請求鉤子是通過裝飾器的形式實現,Flask支援如下四種請求鉤子:

  • before_first_request:在處理第一個請求前執行
  • before_request:在每次請求前執行,在該裝飾函式中,一旦return,檢視函式不再執行
  • after_request:如果沒有丟擲錯誤,在每次請求後執行
    • 接受一個引數:檢視函式作出的響應
    • 在此函式中可以對響應值,在返回之前做最後一步處理,再返回
  • teardown_request:在每次請求後執行
    • 接受一個引數:用來接收錯誤資訊
from flask import Flask

app = Flask(__name__)


# 只有第一次請求會呼叫此方法,因此可以在此方法內部做一些初始化操作
@app.before_first_request
def bfr():
    print('This is the first request')


# 每次請求都會呼叫此方法,可以在此方法裡面做類似驗證的語句
# 若請求不成功,可直接再次方法中進行響應,可以直接return
@app.before_request
def br():
    print('This is the normal request')


# 在每次執行完檢視函式後會呼叫,並會把檢視函式所生成的響應傳入-->[<Response 6 bytes [200 OK]>]
# 可在此方法中對響應做最後一步統一的處理
@app.after_request
def ar(a):
    print(a,'****************')
    print('This is the end of request')
    return a


# 每次請求之後都會呼叫,會接收到一個引數,引數是伺服器可能出現的錯誤資訊,若無錯誤資訊,返回None
@app.teardown_request
def tr(e):
    print(e,'****************')
    print('This is why I cry')


@app.route('/')
def func():
    return '123456'


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

保持狀態

狀態相關概念

  • http是一種無狀態協議,瀏覽器請求伺服器是無狀態的
  • 無狀態:指一次使用者請求時,瀏覽器、伺服器不知道之前這個使用者做過什麼,每次請求都是一次新的請求
  • 無狀態原因:瀏覽器與伺服器是使用 socket 套接字進行通訊的,伺服器將請求結果返回給瀏覽器之後,會關閉當前的 socket 連線,而且伺服器也會在處理頁面完畢之後銷燬頁面物件
  • 有時需要保持下來使用者瀏覽的狀態,比如使用者是否登入過,瀏覽過哪些商品等
  • 實現狀態保持主要有兩種方式:
    • 在客戶端儲存資訊使用Cookie
    • 在伺服器端儲存資訊使用Session

cookie

什麼是cookie

  • 指某些網站為了辨別使用者身份、進行會話跟蹤而儲存在使用者本地的資料(通常經過加密),複數形式Cookies
  • Cookie是由伺服器端生成,傳送給客戶端瀏覽器,瀏覽器會將Cookie的key/value儲存,下次請求同一網站時就傳送該Cookie給伺服器
  • Cookie中的key/value可以由伺服器端自己定義

設定,獲取cookie

from flask import Flask, make_response, request

app = Flask(__name__)


# 設定cookie值
@app.route('/set_cookie')
def set_cookie():
    response = make_response("wahaha")
    response.set_cookie("name", "zhangsan")
    response.set_cookie("age", "13", 60)  # 60秒有效期

    return response


# 獲取cookie
@app.route('/get_cookie')
def get_cookie():
    # 獲取cookie,可以根據cookie的內容來推薦商品資訊
    name1 = request.cookies
    print(name1)
    name = request.cookies.get('name')
    age = request.cookies.get('age')

    return "獲取cookie,name is %s, age is %s" % (name, age)


if __name__ == '__main__':
    app.run(debug=True)

session

什麼是session

cookie是儲存在客戶端瀏覽器中的資訊,因此會存在一定安全隱患。為此對於敏感、重要的資訊,建議要儲存在伺服器端,不能儲存在瀏覽器中,如使用者名稱、餘額、等級、驗證碼等資訊,所以可以使用session進行儲存。

設定,獲取session

from flask import Flask,session

app = Flask(__name__)

#設定任意一個字串當作secret_key
app.config["SECRET_KEY"] = "fhdk^fk#djefkj&*&*&"

#設定session
@app.route('/set_session/<path:name>')
def set_session(name):

    session["name"] = name
    session["age"] = "13"

    return "set session"

#獲取session內容
@app.route('/get_session')
def get_session():

    name = session.get('name')
    age = session.get('age')

    return "name is %s, age is %s"%(name,age)

if __name__ == '__main__':
    app.run(debug=True)

上下文

上下文:相當於一個容器,儲存了 Flask 程式執行過程中的一些資訊。
Flask中有兩種上下文,請求上下文和應用上下文

請求上下文

在 flask 中,可以直接在檢視函式中使用 request 這個物件進行獲取相關資料,而 request 就是請求上下文的物件,儲存了當前本次請求的相關資料,請求上下文物件有:request、session

request

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

session

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

應用上下文

它的字面意思是 應用上下文,但它不是一直存在的,它只是request context 中的一個對 app 的代理(人),所謂local proxy。它的作用主要是幫助 request 獲取當前的應用,它是伴 request 而生,隨 request 而滅的。
應用上下文物件有:current_app,g

current_app

應用程式上下文,用於儲存應用程式中的變數,可以通過current_app.name列印當前app的名稱,也可以在current_app中儲存一些變數,例如:

  • 應用的啟動指令碼是哪個檔案,啟動時指定了哪些引數
  • 載入了哪些配置檔案,匯入了哪些配置
  • 連了哪個資料庫
  • 有哪些public的工具類、常量
  • 應用跑再哪個機器上,IP多少,記憶體多大

g

g 作為 flask 程式全域性的一個臨時變數,充當者中間媒介的作用,我們可以通過它傳遞一些資料,g 儲存的是當前請求的全域性變數,不同的請求會有不同的全域性變數,通過不同的thread id區別
二者區別:
請求上下文:儲存了客戶端和伺服器互動的資料
應用上下文:flask 應用程式執行過程中,儲存的一些配置資訊,比如程式名、資料庫連線、應用資訊等


Flask-Script擴充套件

Flask-Script屬於flask的擴充套件包,通過使用Flask-Script擴充套件,我們可以在Flask伺服器啟動的時候,通過命令列的方式傳入引數,而不僅僅通過app.run()方法中傳參。
需要先安裝flask-script擴充套件
pip install flask-script

python hello.py runserver -host ip地址

用程式碼實現代理

from flask import Flask
#1.從flask_script中匯入Manager類
from flask_script import Manager

app = Flask(__name__)

# 2.使用Manager管理app物件
manager = Manager(app)

@app.route('/')
def hello_world():
    return "helloworld"

if __name__ == '__main__':
    manager.run()

該方法只能在終端啟動。若想要在程式碼頁面直接右鍵執行,需在Edit Configuration處新增引數runserver。


Jinja2

Jinja2模板概述

Jinja2是用來展示資料的html頁面,這個過程也通常稱為渲染,屬於Jinja2的功能使用模板的好處:

  • 檢視函式只負責業務邏輯和資料處理(業務邏輯方面)
  • 而模板則取到檢視函式的資料結果進行展示(檢視展示方面)
  • 程式碼結構清晰,耦合度低

Jinja2特點

  • Jinja2:是 Python 下一個被廣泛應用的模板引擎,是由Python實現的模板語言,他的設計思想來源於 Django 的模板引擎,並擴充套件了其語法和一系列強大的功能,其是Flask內建的模板語言。
  • 模板語言:是一種被設計來自動生成文件的簡單文字格式,在模板語言中,一般都會把一些變數傳給模板,替換模板的特定位置上預先定義好的佔位變數名。
  • 使用render_template函式封裝模板引擎

render_template函式模板語法

Jinja2模板語法

獲取變數
    <h1>整數:{ {number} }</h1>
    <h1>元祖:{ {tuple[0]} }</h1>
    <h1>列表:{ { list[0] } }</h1>
    <h1>字典:{ { dict['key'] } }</h1>
分支語句if
{ % if 條件 % }
    語句1
 { % else % }    
    語句2
{ % endif % }
for迴圈
{% for 變數  in 容器 %}
    語句
{% endfor%}

程式碼展示

-使用函式:render_template(‘模板檔名’,key=value)
-將資料攜帶到,檔案中進行展示
-建立檔案demo01.py,程式碼如下:

from flask import Flask,render_template
app = Flask(__name__) #預設省略了三個引數,static_url_path, static_folder, template_folders

@app.route('/')
def hello_world():
    #定義資料,整數,字串,元祖,列表,字典
    num = 10
    str = "hello"
    tuple = (1,2,3,4)
    list = [5,6,7,8]
    dict = {
        "name":"張三",
        "age":13
    }

    return render_template('file01.html',my_num=num,my_str=str,my_tuple=tuple,my_list=list,my_dict=dict)

if __name__ == '__main__':
    app.run(debug=True)

在templates資料夾下,建立檔案file01.html檔案,程式碼如下:

<h2>1.獲取各種變數的值</h2>
    <h3>整數: {{ my_num + 20}}</h3>
    <h3>字串: {{ my_str + " python" }}</h3>
    <h3>元組: {{ my_tuple }}, 分開獲取:{{ my_tuple[0] }}, {{ my_tuple[1] }}</h3>
    <h3>列表: {{ my_list }}, 分開獲取:{{ my_list[0] }}, {{ my_list[1] }}</h3>
    <h3>字典: {{ my_dict }},分開獲取:{{ my_dict.name }}, {{ my_dict[age] }}</h3>
    <h2>2.遍歷元祖中所有的元素</h2>
    {% for item in my_tuple %}
        <li>{{ item }}</li>
    {% endfor %}

    <h2>3.取出列表中所有偶數</h2>
    {% for item in my_list %}
        {% if item %2 == 0 %}
            {{ item }}
        {% endif %}
    {% endfor %}

    <h2>4.遍歷字典內容</h2>
    {% for key in my_dict %}
        {# 如果直接是mydict.key ,那麼這個key是一個字串, 如果是 mydict[key], 那麼key當成變數 #}
        <li>{{ key }} = {{ my_dict[key] }}</li>
    {% endfor %}

擴充套件

變數 描述
loop.index 當前迴圈迭代的次數(從 1 開始)
loop.index0 當前迴圈迭代的次數(從 0 開始)
loop.revindex 到迴圈結束需要迭代的次數(從 1 開始)
loop.revindex0 到迴圈結束需要迭代的次數(從 0 開始)
loop.first 如果是第一次迭代,為 True 。
loop.last 如果是最後一次迭代,為 True 。
loop.length 序列中的專案數。
loop.cycle 在一串序列間期取值的輔助函式。見下面示例程式。

Jinja2過濾器

過濾器概述

過濾器的本質就是函式。有時候我們不僅僅只是需要輸出變數的值,我們還需要修改變數的顯示,甚至格式化、運算等等,而在模板中是不能直接呼叫 Python 中的某些方法,那麼這就用到了過濾器。

Jinja2自帶過濾器

字串

使用格式:{{字串 | 字串過濾器 }}

  • 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:格式化輸出
    <p>{{ '%s is %d' | format('name',17) }}</p>
  • striptags:渲染之前把值中所有的HTML標籤都刪掉
    <p>{{ '<em>hello</em>' | striptags }}</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 %}

鏈式呼叫

{{ "hello world" | reverse | upper }}

自定義過濾器

方法一

先定義函式,後新增到過濾器列表
app.add_template_filter(‘函式名’,‘過濾器名稱’)

def do_listreverse(li):
    # 通過原列表建立一個新列表
    temp_li = list(li)
    # 將新列表進行返轉
    temp_li.reverse()
    return temp_li

app.add_template_filter(do_listreverse,'lireverse')

方法二

定義函式,直接使用@app.template_filter(‘過濾器名稱’)裝飾

@app.template_filter('lireverse')
def do_listreverse(li):
    # 通過原列表建立一個新列表
    temp_li = list(li)
    # 將新列表進行返轉
    temp_li.reverse()
    return temp_li

在html中使用過濾器

在 html 中使用該自定義過濾器

<h2>my_array 原內容:{{ my_array }}</h2>
<h2> my_array 反轉:{{ my_array | lireverse }}</h2>

模板複用

當有部分程式碼會重複利用的時候,我們就可以利用程式碼複用的方式。程式碼複用一般有三種方式:巨集、繼承和包含。

巨集

解釋

巨集和python中的函式類似,在需要的時候呼叫即可

定義格式

{%macro 巨集名(引數)%}內容{%endmacro%}

{% macro 巨集名(引數) %}
{# 內容 #}
{% endmacro %}

使用

在當前檔案中
直接{{ 巨集名(引數) }}
在其他檔案中
先匯入
{{ import ‘擁有巨集程式碼的檔名’ as ‘別名’}}
再引用
{{ 巨集名(引數) }}

繼承

目的

共性抽取,程式碼複用

格式

{% extends"父模板檔名" %}

父模板

  1. 多個子類完全相同的部分可以直接寫死
  2. 各個子類直接不同的地方使用block模組定義出來
    {%block 塊名%}可修改內容{%endblock%}
    {% block 塊名 %}
    {# 可修改內容 #}
    {% endblock %}
    

子模版

  1. 子類繼承父類
    子類繼承父類時,繼承的程式碼最好寫在所有程式碼的最上邊
  2. 子類根據自己的需求,重寫父類block模組中的程式碼
    • 完全重寫
      {%block 塊名%}新內容{%endblock%}
    {% block 塊名%}
    {# 新內容 #}
    {% endblock%}
    
    • 繼承並增加
      {%block 塊名%}{{super()}}新內容{%endblock%}
    {% block 塊名%}
    {{super()}}
    {# 新內容 #}
    {% endblock%}
    

包含

包含指的是在一個檔案中完全拷貝另一個檔案的所有程式碼.這種方式無法對程式碼進行擴充套件,因此不夠靈活.

格式

{%include "檔名" ignore missing%}
其中,ignore missing是可選引數,選此引數,當包含的檔案找不到時,系統不會報錯.因此,推薦選擇該引數.

模板中的特有變數

特有變數指的是不需要從python中傳遞到模板,就可以直接使用的變數

常見特有變數

request
就是python中的請求上下文物件
g
是一個全域性的應用上下文物件
url_for(函式名)
是一個反解析方法,根據函式名找到對應的路徑
config
config指的就是app.config的配置物件
get_flashed_messages()
是消耗訊息,消耗的是在python中使用flash(“訊息”)方法儲存的訊息.
flash 儲存訊息時需要依賴 session ,所以用 flash 應該先設定SECRET_KEY

CSRF攻擊

CSRF全拼為Cross Site Request Forgery,譯為跨站請求偽造。指攻擊者盜用了你的身份,以你的名義傳送惡意請求。
CSRF攻擊示意圖:
在這裡插入圖片描述

如何防止CSRF攻擊

防止CSRF攻擊的思想:

  • 在客戶端向後端請求介面資料的時候,後端會往響應中的 cookie 中設定 csrf_token 的值
  • 在 Form 表單中新增一個隱藏的的欄位,值也是 csrf_token
  • 在使用者點選提交的時候,會帶上這兩個值向後臺發起請求
  • 後端接受到請求,以會以下幾件事件:
    • 從 cookie中取出 csrf_token
    • 從 表單資料中取出來隱藏的 csrf_token 的值
    • 進行對比
  • 如果比較之後兩值一樣,那麼代表是正常的請求,如果沒取到或者比較不一樣,代表不是正常的請求,不執行下一步操作
  • 提示:程式碼展示:見<< webA >>, << webB >>檔案

程式碼構思

  • flask_wtf模組提供了csrf攻擊的保護
  • 使用流程:
    • from flask_wtf.csrf import CSRFProtect
    • CSRFProtect(app)
  • CSRFProtect(app)保護原理:
    • 對應用程式app中的post,put,dispatch,delete, 4種類型的請求做保護,因為這些型別的請求是用於更改伺服器的資源
    • 當以上面4種類型的請求,操作伺服器資源的時候,會校驗cookie中的csrf_token, 表單中的csrf_token資訊
    • 只有上面二者的值相等的時候,那麼校驗則通過,可以操作伺服器資源

提示 : csrf_token值的生成需要加密,所以要設定SECRET_KEY

程式碼如下

  • 後端程式碼
from flask import Flask,render_template
from flask_wtf import CSRFProtect

app = Flask(__name__)

#設定SECRET_KEY
app.config["SECRET_KEY"] = "fjkdjfkdfjdk"

#保護應用程式
CSRFProtect(app)

@app.route('/')
def show_page():

    return render_template('file01csrf.html')

@app.route('/add_data',methods=["POST"])
def add_data():

    return "登陸成功"

if __name__ == '__main__':
    app.run(debug=True)
  • 前端程式碼
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/add_data" method="post">
    {#設定隱藏的csrf_token,使用了CSRFProtect保護app之後,即可使用csrf_token()方法#}
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">

    <label>使用者名稱:</label> <input type="text" name="username"><br>
    <label>密碼:</label> <input type="text" name="username"><br>
    <input type="submit" value="登陸">
</form>
</body>
</html>