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"父模板檔名" %}
父模板
- 多個子類完全相同的部分可以直接寫死
- 各個子類直接不同的地方使用block模組定義出來
{%block 塊名%}可修改內容{%endblock%}
{% block 塊名 %} {# 可修改內容 #} {% endblock %}
子模版
- 子類繼承父類
子類繼承父類時,繼承的程式碼最好寫在所有程式碼的最上邊 - 子類根據自己的需求,重寫父類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>