Django
利用HTTP協議向伺服器傳參的幾種途徑、響應、Cookie、Session、類檢視、中介軟體
注意:
1>Django中的QueryDict物件
django 中有一個 QueryDict 物件,這個物件一般用來儲存瀏覽器傳遞過來的引數,我們可以預設把他看成是一個字典
但是它和一般的字典的不同之處在於:
■ QueryDict 這個字典可以用來處理一個鍵帶有多個值得情況,即: 一鍵 ===> 多值
QueryDict 這個字典支援 get( ) 方法的使用:
■ 這個位置需要區分是一鍵一值還是一鍵多值的不同情況:
如果是一鍵一值:
QueryDict.get( key ) 獲取的就是當前對應的值(value)
如果是一鍵多值:
QueryDict.get( key ) 獲取的就是最後一個值
QueryDict 還有一個所有值得方法:
QueryDict.getlist( key ) 獲取這個鍵對應的所有值.
例如:
get( ):
根據鍵獲取值
如果一個鍵同時擁有多個值將獲取最後一個值
如果鍵不存在則返回None值,可以設定預設值進行後續處理
# 預設值一般是default,可以省略
QueryDict.get('鍵',預設值)
# 或可寫為:
QueryDict['鍵']
getlist( ):
根據鍵獲取值,值以列表返回,可以獲取指定鍵的所有值
如果鍵不存在則返回空列表[],可以設定預設值進行後續處理
dict.getlist('鍵',預設值)
2>Django中CSRF保護的開關
Django預設開啟了CSRF防護,會對POST、PUT、PATCH、DELETE請求方式進行CSRF防護驗證
在使用postman測試時可以關閉CSRF防護機制
關閉CSRF防護方法為在settings.py檔案中註釋掉CSRF中介軟體,如:
3>Django中的request.GET和request.POST
查詢字串不區分請求方式,即假使客戶端進行POST方式的請求,依然可以通過request.GET獲取請求中的查詢字串資料。
request.GET 指的不是傳送ajax用的get方法, 而是指從url的查詢字串中獲取引數
request.POST 指的也不是ajax用的post方法,而是我們從請求體中獲取的引數
通過上面大家就能明白: GET 和 POST 這裡指的都是獲取引數的位置, 而不是我們以前說的get請求和post請求
如果是從url中(通過查詢字串)傳遞引數,使用GET(代表的是傳遞引數的位置),和請求方法無關
如果是從請求體中傳遞引數使用POST
4>postman請求體傳遞引數的4個選項
form-data多元素表單
x-www-form-urlencoded 正常表單
raw (非表單)可以選擇傳輸資料的資料型別
binary二進位制
1.利用HTTP協議向伺服器傳參的幾種途徑
<1>flask框架
1>通過URL的特定部分傳參
提取URL的特定部分,如/weather/2018,可以在伺服器端的路由中用正則轉換器擷取
# flask後端接收資料:
# 使用正則轉換器
# /user/weather/2018
@app.route('/user/weather/<re: userid>')
def index(userid):
print(userid)# 2018
return '%s' % userid
2>通過查詢字串(query string)傳遞引數,url中?後形如key1=value1&key2=value2
獲取引數的方式
request.args.get('key1')
3>請求體(body)中傳送的資料,比如表單資料、json、xml
獲取引數的方式
request.json.get('key')
4>通過http請求報文的請求頭(header)傳遞引數。
獲取引數的方式
request.headers.get('content-type')
<2>Django
1>通過URL的特定部分傳遞引數
獲取引數的方式:檢視函式傳參
■ 在定義路由URL時,可以使用正則表示式提取引數的方法從URL中獲取請求引數,Django會將提取的引數直接傳遞到檢視的傳入引數中。
■ 未命名引數按定義順序傳遞(相當於位置引數), 如
url(r'^weather/([a-z]+)/(\d{4})/$', views.weather),
def weather(request, city, year):
print('city=%s' % city) # beijing
print('year=%s' % year) # 2008
return HttpResponse('OK')
命名引數按名字傳遞(相當於關鍵字引數),如
url(r'^weather/(?P<city>[a-z]+)/(?P<year>\d{4})/$', views.weather),
def weather(request, year, city):
print('city=%s' % city)
print('year=%s' % year)
return HttpResponse('OK')
2>通過請求路徑url中的查詢字串傳遞引數(形如?k1=v1&k2=v2)
獲取引數的方式:request.GET.get('key')
■ 可以通過request.GET屬性獲取,這個方法返回QueryDict物件。
■ QueryDict物件類似於一個字典, 所以我們可以通過get() 來獲取 key 值所對應的 value 值
a = request.GET.get('a')
b = request.GET.get('b')
alist = request.GET.getlist('a')
3>通過請求體傳遞引數
請求體資料格式不固定,可以是表單型別字串,可以是JSON字串,可以是XML字串,應區別對待。
可以傳送請求體資料的請求方式有 POST、PUT、PATCH、DELETE。
請求體—表單型別 (Form Data)
■ 獲取引數的方式:request.POST.get('key')
■ 前端傳送的表單型別的請求體資料,可以通過request.POST屬性獲取,返回QueryDict物件。
■ QueryDict物件類似於一個字典, 所以我們可以通過get() 來獲取 key 值所對應的 value 值
a = request.POST.get('a')
b = request.POST.get('b')
alist = request.POST.getlist('a')
注意:
如果是表單資料, 傳送的請求不但要求body中是表單的鍵值對
也要求請求頭中content-type的型別是application/x-www-form-urlencoded
如果請求中的不是這樣寫的型別, 上面的request.POST.get( )方法也不可以使用.
如圖所示: (這兩部分都有才可以使用上面的request.POST.get( )方法進行請求)
請求體——非表單型別 (Non-Form Data)
■ 非表單型別的請求體資料,Django無法自動解析
■ 可以通過request.body屬性獲取最原始的請求體資料,自己按照請求體格式(JSON、XML等)進行解析。
■ 其中: request.body返回bytes型別。
json_bytes = request.body
json_str = json_bytes.decode()
#python3.6及以上版本中, json.loads()方法可以接收str和bytes型別
#但是python3.5以及以下版本中, json.loads()方法只能接收str,
#所以我們的版本如果是3.5以及以下 需要將bytes型別解碼為str型別
req_data = json.loads(json_str)
print(req_data['a'])
print(req_data['b'])
4>通過請求頭傳遞引數
我們可以通過request.META屬性獲取請求頭headers中的資料
request.META為字典型別。
注意請求頭的用法區別,key的表現形式不同
■ 我們通過request.META獲取的時候,需要使用如下所示的用法:
■ CONTENT_LENGTH – The length of the request body (as a string).
■ CONTENT_TYPE – The MIME type of the request body.
■ HTTP_ACCEPT – Acceptable content types for the response.
■ HTTP_ACCEPT_ENCODING – Acceptable encodings for the response.
■ HTTP_ACCEPT_LANGUAGE – Acceptable languages for the response.
■ HTTP_HOST – The HTTP Host header sent by the client.
■ HTTP_REFERER – The referring page, if any.
■ HTTP_USER_AGENT – The client’s user-agent string.
■ QUERY_STRING – The query string, as a single (unparsed) string.
■ REMOTE_ADDR – The IP address of the client.
■ REMOTE_HOST – The hostname of the client.
■ REMOTE_USER – The user authenticated by the Web server, if any.
■ REQUEST_METHOD – A string such as "GET" or "POST".
■ SERVER_NAME – The hostname of the server.
■ SERVER_PORT – The port of the server (as a string).
request.META['CONTENT_TYPE']
<3>其他常用的HttpRequest物件屬性
1>method:一個字串,表示請求使用的HTTP方法,常用值包括:'GET'、'POST'。
2>user:請求的使用者物件。
3>path:一個字串,表示請求的頁面的完整路徑,不包含域名和引數部分。
4>encoding:一個字串,表示提交的資料的編碼方式。
如果為None則表示使用瀏覽器的預設設定,一般為utf-8。
這個屬性是可寫的,可以通過修改它來修改訪問表單資料使用的編碼,接下來對屬性的任何訪問將使用新的encoding值。
5>FILES:一個類似於字典的物件,包含所有的上傳檔案。
2.響應
檢視在接收請求並處理後,必須返回HttpResponse物件或子物件。
HttpRequest物件由Django建立,HttpResponse物件由開發人員建立。
<1>HttpResponse類
1>HttpResponse的使用方式
可以從 django.http裡面匯入HttpResponse
from django.http import HttpResponse
HttpResponse(
content=響應體,
content_type=響應體資料型別,
status=狀態碼
)
通過上式我們知道HttpResponse裡面有對應的一些引數可以修改:
■ content:表示返回的內容。
■ status_code:返回的HTTP響應狀態碼。
■ content_type:指定返回資料的的MIME型別。
例如:
# 定義一個新的檢視函式
def demo_response(request):
# 定義一個json字串
s = '{"name": "python"}'
# 返回一個HttpResponse響應物件
return HttpResponse(s, content-type="application/json", status=400)
2>HttpResponse特別的使用方式:
我們如果需要在響應頭新增自定義的鍵值對內容,可以把HttpResponse物件當做字典進行響應頭鍵值對的設定:
response = HttpResponse()
# 自定義響應頭Itcast, 值為Python
response['Itcast'] = 'Python'
示例:
from django.http import HttpResponse
def demo_view(request):
return HttpResponse('itcast python', status=400)
或者
response = HttpResponse('itcast python')
response.status_code = 400
response['Itcast'] = 'Python'
return response
<2>HttpResponse子類
Django提供了一系列HttpResponse的子類,可以快速設定狀態碼
這個狀態碼可以從 Django.http 裡面匯入,例如:
from django.http import HttpResponseNotFound
HttpResponseRedirect 301
HttpResponsePermanentRedirect 302
HttpResponseNotModified 304
HttpResponseBadRequest 400
HttpResponseNotFound 404
HttpResponseForbidden 403
HttpResponseNotAllowed 405
HttpResponseGone 410
HttpResponseServerError 500
使用的演示:
<3>JsonResponse
如果我們要返回json字串, 那麼我們可以使用 JsonResponse 來幫助我們快速的構建json字串,進行返回.
JsonResponse 能夠幫助我們自動把字典轉成json字串型別, 並且還不用自己設定響應頭中contentType欄位
1>JsonResponse的作用
幫助我們將資料轉換為json字串
設定響應頭Content-Type為 application/json
2>JsonResponse的使用
# 匯入JsonResponse
from django.http import JsonResponse
def demo_view(request):
# 直接返回JsonResponse這個物件,並且裡面可以直接傳入引數
return JsonResponse({'city': 'beijing', 'subject': 'python'})
<4>redirect重定向
建議redirect(重定向)和我們前面學習的reverse(反解析)搭配使用.
return redirect(reverse('reqrespspace:getresponse_name'))
■ reverse('spacename:name')
儘量不要把路由寫死. 有利於我們更改開發程式碼.
from django.shortcuts import redirect
def demo_view(request):
return redirect('/index.html')
3.Cookie
Cookie是儲存在瀏覽器中的一段純文字資訊,建議不要儲存敏感資訊如密碼,因為電腦上的瀏覽器可能被其它人使用。
Cookie,有時也用其複數形式Cookies,指某些網站為了辨別使用者身份、進行session跟蹤而儲存在使用者本地終端上的資料(通常經過加密)。
Cookie最早是網景公司的前僱員Lou Montulli在1993年3月的發明。
Cookie是由伺服器端生成,傳送給User-Agent(一般是瀏覽器),瀏覽器會將Cookie的key/value儲存到某個目錄下的文字檔案內,下次請求同一網站時就傳送該Cookie給伺服器(前提是瀏覽器設定為啟用cookie)。
Cookie名稱和值可以由伺服器端開發自己定義,這樣伺服器可以知道該使用者是否是合法使用者以及是否需要重新登入等。伺服器可以利用Cookies包含資訊的任意性來篩選並經常性維護這些資訊,以判斷在HTTP傳輸中的狀態。
Cookies最典型記住使用者名稱。
<1>Cookie的特點
Cookie以鍵值對的格式進行資訊的儲存。
Cookie基於域名安全,不同域名的Cookie是不能互相訪問的
如訪問taobao.com時向瀏覽器中寫了Cookie資訊,使用同一瀏覽器訪問baidu.com時,無法訪問到taobao.com寫的Cookie資訊。
當瀏覽器請求某網站時,會將瀏覽器儲存的跟網站相關的所有Cookie資訊提交給網站伺服器。
<2>設定Cookie
可以通過HttpResponse物件中的set_cookie方法來設定cookie。
HttpResponse.set_cookie(cookie名, value=cookie值, max_age=cookie有效期)
max_age 單位為秒,預設為None。
如果是臨時cookie,可將max_age設定為None。
示例:
def demo_view(request):
response = HttpResponse('ok')
response.set_cookie('0001', 'python1') # 臨時cookie
response.set_cookie('0002', value='python2', max_age=3600) # 有效期一小時
return response
<3>獲取Cookie
可以通過HttpRequest物件的COOKIES屬性來讀取本次請求攜帶的cookie值。request.COOKIES為字典型別。
request.COOKIES.get('key')
def demo_view(request):
cookie1 = request.COOKIES.get('0001')
print(cookie1)
return HttpResponse('OK')
4.Session
<1>啟用Session
1>Session簡介:
在計算機中,尤其是在網路應用中,稱為“會話控制”。Session 物件儲存特定使用者會話所需的屬性及配置資訊。這樣,當用戶在應用程式的 Web 頁之間跳轉時,儲存在 Session 物件中的變數將不會丟失,而是在整個使用者會話中一直存在下去。當用戶請求來自應用程式的 Web 頁時,如果該使用者還沒有會話,則 Web 伺服器將自動建立一個 Session 物件。當會話過期或被放棄後,伺服器將終止該會話。Session 物件最常見的一個用法就是儲存使用者的首選項。例如,如果使用者指明不喜歡檢視圖形,就可以將該資訊儲存在 Session 物件中。注意 會話狀態僅在支援 cookie 的瀏覽器中保留。
2>Session的理解:
廣義來講: session是一種會話機制, 用於記錄多次http請求之間的關係,關係就是狀態資料,比如登入狀態.
狹義來講: session是一種會話資料, 記錄的狀態資料, 比如登入之後記錄的user_id等
3>Django專案預設啟用Session
可以在settings.py檔案的中介軟體選項中檢視,如圖所示
如需禁用session,將上圖中的session中介軟體註釋掉即可。
<2>儲存方式
在settings.py檔案中,可以設定session資料的儲存方式.
另外session可以儲存在資料庫、本地快取( 程式的執行記憶體中, 全域性變數)、檔案、redis等 。
1>資料庫
儲存在資料庫中,如下設定可以寫到settings中,也可以不寫,這是預設儲存方式。
# 如果是存放資料庫, 一般以db結尾
SESSION_ENGINE='django.contrib.sessions.backends.db'
如果儲存在資料庫中,需要在項INSTALLED_APPS中安裝Session應用。
資料庫中的表如圖所示
表結構如下
由表結構可知,操作Session包括三個資料:鍵,值,過期時間。
2>本地快取
儲存在本機記憶體中,如果丟失則不能找回,比資料庫的方式讀寫更快。
# 如果是存放在本地快取, 一般以cache結尾
SESSION_ENGINE='django.contrib.sessions.backends.cache'
其中,本地快取會出現問題: 因為是存放在本地的記憶體中,所以會出現在離線情況下出現的跨機訪問問題:
■ 我們這裡可以看到: 有兩臺伺服器儲存的有session資料, 前面由nginx負責管理訪問機制,有可能現在的訪問方式是輪詢形式, 那麼意味著第一次使用者進入的是上面的伺服器,進行了登入操作,我們把他的登入狀態儲存到了伺服器1裡面, 隨後使用者有進行了其他操作, 然後有登陸進入這個伺服器,這次輪詢到了伺服器2裡面,但是這裡面沒有儲存登入狀態,這樣就會造成使用者第二次登入.所以會造成使用者跨機的問題.
■ 但是如果我們使用redis就不會出現這樣的情況,因為無論是哪一個伺服器,最終儲存的資料都儲存到了redis中 :
3>混合儲存
優先從本機記憶體中存取,如果沒有則從資料庫中存取。
# 如果是存放資料庫,一般以cached_db結尾
SESSION_ENGINE='django.contrib.sessions.backends.cached_db'
4>Redis
django-redis 中文文件: http://django-redis-chs.readthedocs.io/zh_CN/latest/#cache-backend
在redis中儲存session,需要引入第三方擴充套件,我們可以使用django-redis來解決。
安裝擴充套件
pip install django-redis
在settings.py檔案中做如下設定
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
# 定義django中redis的位置
"LOCATION": "redis://127.0.0.1:6379/0",
"OPTIONS": {
# django使用redis的預設客戶端來進行操作.
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# 我們定義一個cache(本地快取來儲存資訊,cache指定的是redis)
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
# 本地的session使用的本地快取名稱是'default', 這個名稱就是上面我們配置的caches的名
# 稱"default"
SESSION_CACHE_ALIAS = "default"
新增程式碼,檢視是否能夠儲存到redis中去:
urls.py:
# url的配置:
from django.conf.urls import url
from . import views
urlpatterns = [
# 儲存session資料
url(r'^set_session', views.set_session),
# 獲取session資料
url(r'^get_session', views.get_session),
]
Views.py:
# 定義設定session的檢視函式
def set_session(request):
request.session['one'] = '1'
request.session['two'] = '2'
request.session['three'] = '3'
return HttpResponse('儲存session資料成功')
# 定義獲取session的檢視函式
def get_session(request):
one = request.session.get('one')
two = request.session.get('two')
three = request.session.get('three')
text = 'one=%s, two=%s, three=%s' % (one,two,three)
return HttpResponse(text)
開啟redis來檢視儲存的資訊:
開啟redis:
redis-server
檢視資訊: (在新視窗中)
redis-cli
select 1
keys *
<3>Session操作
通過HttpRequest物件的session屬性進行會話的讀寫操作。
1>設定session
以鍵值對的格式寫session
request.session['鍵']=值
例如: request.session['one'] = '1'
2>獲取session
根據鍵讀取值
request.session.get('鍵')
例如: one = request.session.get('one')
3>清除所有session,在儲存中刪除值部分
request.session.clear()
4>清除session資料,在儲存中刪除session的整條資料。
request.session.flush()
5>刪除session中的指定鍵及值,在儲存中只刪除某個鍵及對應的值。
del request.session['鍵']
6>設定session的有效期
request.session.set_expiry(value)
如果value是一個整數,session將在value秒沒有活動後過期。
如果value為0,那麼使用者session的Cookie將在使用者的瀏覽器關閉時過期。
如果value為None,那麼session有效期將採用系統預設值,預設為兩週,可以通過在settings.py中設定SESSION_COOKIE_AGE來設定全域性預設值。其中 SESSION_COOKIE_AGE的單位是以秒為單位的.
5.類檢視
<1>類檢視
1>檢視函式
以函式的方式定義的檢視稱為函式檢視,即我們常說的檢視函式
2>檢視函式的缺點
檢視函式遭遇不同方式分別請求( 例如 get 和 post ),並且需要做不同處理時,我們如果在一個函式中編寫不同的業務邏輯,程式碼可讀性和複用性都不好
3>類檢視
使用類來定義的檢視,稱為類檢視
類檢視中的方法通常以請求方式命名
在Django中給我們提供了類檢視的概念,即可以使用類來定義一個檢視,解決了檢視函式的缺點
<2>類檢視使用
1>如果想要使用類檢視需要如下幾步:
定義類檢視, 且類檢視繼承自View
■ 使用: from django.views.generic import View
■ 或者是: from django.views.generic.base import View
定義路由,路由的第二個引數需要是一個函式,所以我們會呼叫系統的 as_view() 方法
urlpatterns = [
# 類檢視:註冊
# url(路徑, 執行的函式)
# url的第二個引數需要是一個函式
# 我們這裡如果傳入: views.RegisterView 會發現這個是一個類, 不是一個函式,
# 所以我們需要呼叫系統給我們提供的 as_view() 方法
url(r'^register/$', views.RegisterView.as_view()),
]
使用瀏覽器訪問我們定義的路由, 檢視結果
2>使用類檢視可以將檢視對應的不同請求方式以類中的不同方法來區別定義
如下所示:
# 匯入類檢視的父類View
from django.views.generic import View
class RegisterView(View):
"""類檢視:處理註冊"""
def get(self, request):
"""處理GET請求,返回註冊頁面"""
return render(request, 'register.html')
def post(self, request):
"""處理POST請求,實現註冊邏輯"""
return HttpResponse('這裡實現註冊邏輯')
3>類檢視的好處:
程式碼可讀性好
類檢視相對於函式檢視有更高的複用性
如果其他地方需要用到某個類檢視的某個特定邏輯,直接繼承該類檢視即可
4>注意:
如果我們在類檢視函式中沒有定義方法, 但是我們請求了. 會報405找不到請求方法的錯誤.
■ 例如: 類檢視中沒有定義get方法, 但是我們使用get方法進行了請求, 那麼會報405的錯誤: 找不到對應的請求方法.
在類檢視中定義的get或者是post都是物件方法, 第一個引數都是self.
第二個引數一般情況下都是 request物件. 其他的引數依次往後面寫就可以.
我們在使用類檢視的時候, 需要在路由位置進行設定, 設定的第二個引數需要是一個函式, 所以我們這裡呼叫了類以後, 後面需要呼叫 as_view( ) 函式.
<3>類檢視原理
為什麼我們定義url的時候, 呼叫 as_view() 函式,就可以達到結果, 如果不呼叫就會報錯
as_view()的原理
由原始碼可看出as_view()的作用是返回真正的檢視函式
@classonlymethod
def as_view(cls, **initkwargs):
...省略程式碼...
def view(request, *args, **kwargs):
# 這裡的cls是as_view這個函式接收的第一個引數,也就是呼叫當前函式的類
# 得到呼叫的類了之後, 建立類的物件: self
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
# 給當前這個類,新增對應的屬性, 如下所示:
self.request = request
self.args = args
self.kwargs = kwargs
# 呼叫dispatch方法,按照不同請求方式呼叫不同請求方法
return self.dispatch(request, *args, **kwargs)
...省略程式碼...
# 返回真正的函式檢視
return view
# dispatch本身是分發的意思,這裡指的是函式的名字.
def dispatch(self, request, *args, **kwargs):
# self.http_method_names指的是我們的類檢視中方法的名字
# 這裡把所有方法的名字都存放在了http_methods_names中
# 我們會把當前請求的方式轉為小寫,然後判斷是否在列表中存在.
if request.method.lower() in self.http_method_names:
"""
如果在裡面, 則進入這裡
這裡的getattr作用是獲取當前物件的屬性.
下面的引數為:
self : 類檢視物件
request.method.lower() : 請求方法的小寫. 例如: 'get' 或 'post'
http_method_not_allowed : 後續處理方式(不允許請求)
下面程式碼整體的意思: 根據類檢視物件, 獲取當前類檢視中對應名稱的方法
如果獲取到, 則把方法返回給handle, 否則不允許訪問.
"""
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
# 如果類檢視中如果沒有的話, 則進入這裡, 表明不允許進行請求.
# 我們會把不允許請求這個欄位返回給handle.
handler = self.http_method_not_allowed
# 最終返回handle(handle裡面要麼包含可以訪問的方法, 要麼就是不允許訪問的欄位)
return handler(request, *args, **kwargs)
<4>類檢視使用裝飾器
先定義一個為函式檢視準備的裝飾器(在設計裝飾器時基本都以函式檢視作為考慮的被裝飾物件),以及一個要被裝飾的類檢視
# 定義一個裝飾器
def my_decorator(func):
# wrapper函式必然會接收一個request物件,因為傳入進來的func這個函式肯定會帶這個引數
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求路徑%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 我們定義的類檢視
class DemoView(View):
# 我們給get方法新增上裝飾器, 然後執行.
@my_decorator
def get(self, request):
print('get方法')
return HttpResponse('ok')
# 類視圖裡面的物件方法: post方法
def post(self, request):
print('post方法')
return HttpResponse('ok')
把一個修飾函式的裝飾器作用在類上的幾種方法
1>在URL配置中裝飾
url配置中使用 as_view( ) 函式返回一個view( ) 函式
換句話說在呼叫類檢視之前,必然會呼叫 view( ) 這個函式,所以把裝飾器作用到 view( ) 函式上
# 匯入views.py檢視檔案
from . import views
urlpatterns = [
# 我們在路由部分, 把定義好的裝飾器新增到當前的函式上
# 這裡需要注意: as_view() 會返回一個 view() 函式
# 所以我們把裝飾器新增到view()函式上
url(r'^demo/$', views.my_decorate(views.DemoView.as_view()))
]
這種方式的弊端
■ 此種方式最簡單,但因裝飾行為被放置到了url配置中,單看檢視的時候無法知道此檢視還被添加了裝飾器,不利於程式碼的完整性,不建議使用。
■ 此種方式會為類檢視中的所有請求方法都加上裝飾器行為(因為是在檢視入口處,分發請求方式前)。
2>在類檢視中使用裝飾器
因為裝飾器的內函式第一個引數是request,而類視圖裡面的函式第一個引數是self,第二個引數才是request。所以我們不能夠直接給類檢視中的函式新增裝飾器,否則傳入的引數會出現錯誤
解決方法:
由上面的案例我們可知: 我們定義的裝飾器不能夠直接使用到類檢視的方法上.
所以我們需要把我們定義的裝飾器進行轉化, 轉化為能夠被類檢視中函式使用的裝飾器
轉化的方法需要匯入:
from django.utils.decorators import method_decorator
匯入進來之後, 我們需要把自定義的裝飾器用這個方法包裹住轉化, 例如:
@method_decorator(自定義裝飾器)
第一種解決方式:
在類檢視中使用為函式檢視準備的裝飾器時,不能直接新增裝飾器
需要使用method_decorator將其轉換為適用於類檢視方法的裝飾器。
from django.views.generic import View
# 匯入轉換的裝飾器方法:
from django.utils.decorators import method_decorator
# 為特定請求方法新增裝飾器
class DemoView(View):
# 使用轉換的方法將裝飾器轉化:
@method_decorator(my_decorator)
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
問題:
雖然上面的方式可以解決類檢視新增裝飾器問題, 但是我們這種是給單個函式新增的, 而不是類檢視中的所有函式
第二種解決方式:
重寫dispatch方法,給dispatch方法加裝飾器
使用method_decorator將其轉換為適用於類檢視方法的裝飾器
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
# 自定義的裝飾器方法
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求的路徑:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 類檢視
class DemoView(View):
# 重寫父類的dispatch方法, 因為這個方法被 as_view() 中的 view() 呼叫
# 所以我們對這個方法新增裝飾器, 也就相當於對整個類檢視的方法新增裝飾器.
@method_decorator(my_decorator)
def dispatch(self, request, *args, **kwargs):
# 重寫父類的這個方法我們不會修改它的任何引數, 所以我們直接呼叫父類的這個方法即可
# 它裡面的引數我們也不動它, 直接還傳遞過去.
return super().dispatch(request, *args, **kwargs)
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
第三種解決方式:
■ method_decorator( )方法直接裝飾到類上去,使當前的檢視類中的某一個函式新增裝飾器方法
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求的路徑:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
"""
類檢視
給類檢視增加上@method_decorator方法
增加上之後,並不能夠給其中的某一個函式增加上裝飾器
所以我們需要給method_decator配置第二個引數
第二個引數就是類中某一個函式的名稱,意味著給當前這個函式增加上裝飾器
"""
@method_decorator(my_decorator, name='get')
class DemoView(View):
@method_decorator(my_decorator)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
第四種解決方式:
■ 使用@method_decorator在類檢視的位置給'dispatch'方法新增裝飾器
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求的路徑:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 類檢視
# 因為我們可以直接給dispatch方法新增裝飾器,意味著, 我們內部不用重寫dispatch方法了.
@method_decorator(my_decorator, name='dispatch')
class DemoView(View):
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
3>method_decorator裝飾器的作用
為函式檢視準備的裝飾器,其被呼叫時,第一個引數用於接收request物件
def my_decorate(func):
def wrapper(request, *args, **kwargs): # 第一個引數request物件
...程式碼省略...
return func(request, *args, **kwargs)
return wrapper
而類檢視中請求方法被呼叫時,傳入的第一個引數不是request物件,而是self 檢視物件本身,第二個位置引數才是request物件
class DemoView(View):
def dispatch(self, request, *args, **kwargs):
...程式碼省略...
def get(self, request):
...程式碼省略...
所以如果直接將用於函式檢視的裝飾器裝飾類檢視方法,會導致引數傳遞出現問題。
method_decorator裝飾器的作用是為函式檢視裝飾器補充第一個self引數,以適配類檢視方法。
4>類檢視使用裝飾器的最終方法
將裝飾器本身改為可以適配類檢視方法
def my_decorator(func):
def wrapper(self, request, *args, **kwargs): # 此處增加了self
print('自定義裝飾器被呼叫了')
print('請求路徑%s' % request.path)
return func(self, request, *args, **kwargs) # 此處增加了self
return wrapper
<5>構造Mixin擴充套件類
1>給類檢視所有方法新增裝飾器有兩種
修改裝飾器接收到的引數
■ 即把裝飾器的第一個引數修改為self,使類中所有的方法都可以直接新增上裝飾器
修改類檢視中所有函式都會呼叫的 as_view() 方法
■ 或者是 dispatch方法, 因為dispatch方法在 as_view() 方法內部,故我們這裡不討論dispatch
2>使用類擴充套件的形式給當前的類檢視新增裝飾器
也就是說,把裝飾器加在類擴充套件中,在類檢視的父類中重寫as_view()方法,並繼承自View類的as_view()方法
View類的as_view()方法返回了view(),給view()新增裝飾器
那麼我們可以繼續思考
類檢視 —————> 繼承自View類 (包含有as_view函式)
如果我們在整個繼承的過程中新增一步, 例如:
類檢視 ———> 繼承自額外擴充套件的類 ———> 繼承自View類(包含有as_view函式)
在額外擴充套件的類中重寫 as_view( ) 方法,並且對 as_view( ) 方法新增裝飾器,類檢視中的所有方法都會被裝飾器裝飾
■ 第一步:建立一個擴充套件類,在擴充套件類中重寫 as_view 方法,並且給父類傳過來的view方法新增裝飾器
■ 第二步:讓類檢視繼承自擴充套件類
第一步程式碼:
# 定義一個新的擴充套件類,讓該類繼承自View類
class BaseView(View):
# 在擴充套件類中,重寫View類的 as_view 方法, 並且對該方法新增裝飾器
@classmethod
def as_view(cls, *args, **kwargs):
# 重寫之後, 不對該方法做其他額外操作,所以我們重新呼叫父類的該方法
view = super().as_view(*args, **kwargs)
# 對父類傳過來的view方法新增裝飾器
view = my_decorator(view)
return view
第二步程式碼:
# 讓我們的類檢視繼承自擴充套件類
class DemoView(BaseView):
def get(self, request):
print('get')
return HttpResponse('get func')
def post(self, request):
print('post')
return HttpResponse('post func')
結論:
經過中間一層額外擴充套件類的裝飾過濾,我們原來的DemoView中的所有檢視方法是能夠經過裝飾器的
3>使用多個類擴充套件的形式給當前的類檢視新增多個裝飾器
那麼我們可以繼續思考:
類檢視 —————> 繼承自View類 (包含有as_view函式)
類檢視 ———> 繼承自額外擴充套件的類1 ---> 繼承自額外擴充套件的類2 ———> 繼承自View類(包含有as_view函式)
延伸:
我們定義兩個擴充套件類, 並且重寫兩次 as_view 方法, 來看看會發生什麼 :
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
# 定義的第一個裝飾器:
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求的路徑:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 定義的第二個裝飾器:
def my_decorator2(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了22222')
print('請求的路徑:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 額外增加的第一個擴充套件類
class BaseView(View):
# 第一次重寫父類中的as_view方法
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
# 對獲取的view第一次新增裝飾器
view = my_decorator(view)
return view
# 額外增加的第二個擴充套件類
class Base2View(View):
# 第二次重寫父類中的as_view方法
@classmethod
def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs)
# 對獲取的view進行第二次新增裝飾器
view = my_decorator2(view)
return view
# 我們定義的類檢視, 繼承自兩個額外增加的類
class DemoView(BaseView, Base2View):
# 類檢視中的get方法
def get(self, request):
print('get')
return HttpResponse('get func')
# 類檢視中的post方法
def post(self, request):
print('post')
return HttpResponse('post func')
那麼我們來看一下同時呼叫兩個裝飾器是怎樣實現的:
說明:
如果兩個擴充套件類的父類相同:則兩個父類都會呼叫
如果兩個擴充套件類的父類不同:則只會呼叫第一個父類
綜上,我們可以把程式碼變成這個樣子:
為什麼BaseView類和Base2View類都繼承自object還能重寫as_view()?
■ 當程式執行到BaseView類的view = super().as_view(*args, **kwargs)時,會在父類中查詢as_view(),如果沒找到會去Base2View類中以及父類中查詢,如果還沒有會去View類中查詢,再沒有就會報錯了。
# 第一個擴充套件類, 讓他繼承自object
class BaseView(object):
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
view = my_decorator(view)
return view
# 第二個擴充套件類,讓他繼承自object
class Base2View(object):
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
view = my_decorator2(view)
return view
# 類檢視, 讓他除了繼承自這兩個父類外, 最後繼承View類.
class DemoView(BaseView, Base2View, View):
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
說明:
因為都是繼承自object,所以擴充套件類中的super.as_view都會去找其他的父類依次執行,最終都會執行到View這個類這裡,所以肯定會執行View中的as_view方法
使用Mixin擴充套件類,也會為類檢視的所有請求方法都新增裝飾行為。
6.中介軟體
中介軟體類似請求鉤子
Django中的中介軟體是一個輕量級、底層的外掛系統,可以介入Django的請求和響應處理過程,修改Django的輸入或輸出。中介軟體的設計為開發者提供了一種無侵入式的開發方式,增強了Django框架的健壯性。
我們可以使用中介軟體,在Django處理檢視的不同階段對輸入或輸出進行干預。
<1>中介軟體的定義方法
1>定義中介軟體的步驟
在一個子應用中建立一箇中間件檔案,例如: middleware(別的名字也可以)
■ 在中介軟體檔案中定義一箇中間件工廠函式,然後返回一個可以呼叫的中介軟體。
■ 中介軟體工廠函式需要接收一個可以呼叫的get_response物件。這個函式接收的引數只能是get_response
■ 返回的中介軟體也是一個可以被呼叫的物件,並且像檢視一樣需要接收一個request物件引數,返回一個response物件。
在setting.py檔案的MIDDLEWARE部分註冊新增
在呼叫檢視時,便會呼叫中介軟體了
2>中介軟體模板
def simple_middleware(get_response):
# 此處編寫的程式碼僅在Django第一次配置和初始化的時候執行一次。
def middleware(request):
# 此處編寫的程式碼會在每個請求處理檢視前被呼叫。
response = get_response(request)
# 此處編寫的程式碼會在每個請求處理檢視之後被呼叫。
return response
return middleware
3>定義中介軟體的案例
在子應用users中新建一箇中間件檔案middleware.py
■ 在中介軟體檔案中定義一箇中間件工廠函式
def my_middleware(get_response):
print('init 被呼叫')
def middleware(request):
print('before request 被呼叫')
response = get_response(request)
print('after response 被呼叫')
return response
return middleware
定義好中介軟體後,需要在工程同名應用下的settings.py檔案中添加註冊中介軟體
■ 子應用名.中介軟體檔名.中介軟體工廠函式名
■ 'users.middleware.my_middleware'
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'users.middleware.my_middleware', # 新增中介軟體
]
定義一個檢視進行測試
def demo_view(request):
print('view 檢視被呼叫')
return HttpResponse('OK')
注意:Django執行在除錯模式下,中介軟體init部分有可能被呼叫兩次,關閉除錯模式只會執行一次
<2>多箇中間件的執行順序
在請求檢視被處理前,中介軟體由上至下依次執行
在請求檢視被處理後,中介軟體由下至上依次執行
1>定義兩個中介軟體
def my_middleware(get_response):
print('init 被呼叫')
def middleware(request):
print('before request 被呼叫')
response = get_response(request)
print('after response 被呼叫')
return response
return middleware
def my_middleware2(get_response):
print('init2 被呼叫')
def middleware(request):
print('before request 2 被呼叫')
response = get_response(request)
print('after response 2 被呼叫')
return response
return middleware
2>註冊新增兩個中介軟體
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'users.middleware.my_middleware', # 新增
'users.middleware.my_middleware2', # 新增
]
3>執行結果
init2 被呼叫
init 被呼叫
before request 被呼叫
before request 2 被呼叫
view 檢視被呼叫
after response 2 被呼叫
after response 被呼叫