09 Django 檢視函式
Django 中的檢視是 MTV 架構模式中的 V 層,主要處理客戶端的請求並生成響應資料返回。它其實類似於 MVC 架構模式中的 C 層,用於處理專案的業務邏輯部分。Django 的檢視函式就是 Django 專案中專門處理對應 path 的 python 函式,這也是整個專案開發中最核心的業務處理部分。當然,在 Django 中處理 path 路徑的不只有檢視函式(FBV),還有檢視類(CBV)。
1. Django 檢視中的 FBV 和 CBV
FBV 全稱是 function base views, 即在檢視中是使用函式來對應處理請求。如下示例:
# 在django中會對跨域請求做攔截處理,這裡使用@csrf_exempt註解表示不作處理,主要是針對POST請求
@csrf_exempt
def hello_world(request, *args, **kwargs):
if request.method == 'GET':
return HttpResponse('Hello, get request', content_type="text/plain")
elif request.method == 'POST':
return HttpResponse('Hello, post request', content_type="text/plain")
return HttpResponse( "Hello, world.", content_type="text/plain")
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/', hello_world),
]
注意: 檢視函式可以接受 request 引數,用於存放瀏覽器傳遞過來的所有資料。它是 WSGIRequest 類的一個例項,而 WSGIRequest 類又繼承自 HttpRequest 。我們通過 request 這個例項可以拿到客戶端 http 請求的方法,請求路徑、傳遞的引數以及上傳的檔案等等。
CBV 全稱是 class base views 就是在檢視中使用類處理請求。在 Django 中加入了 CBV 的模式,讓我們用類去處理響應,這樣做有以下幾個好處:
-
可以使用面嚮物件的技術,比如 Mixin(多繼承),這樣可以有效複用增刪改查資料庫的程式碼;
-
在檢視類中我們定義如 get 方法就能處理對應的 GET 請求,這樣無需使用 if 再進行判斷,一方面提高了程式碼的可讀性,另一方面也規範了 web api 的介面設計。
示例程式碼:
from django.http import HttpResponse
from django.views import View
class HelloView(View):
def get(self, request, *args, **kwargs):
return HttpResponse('get\n')
def post(self, request, *args, **kwargs):
return HttpResponse('post\n')
def put(self, request, *args, **kwargs):
return HttpResponse('put\n')
def delete(self, request, *args, **kwargs):
return HttpResponse('delete\n')
# 注意,給CBV加上@csrf_exempt註解,需要加到dispatch方法上,後續會詳解介紹這個函式的作用
@csrf_exempt
def dispatch(self, request, *args, **kwargs):
return super(HelloView, self).dispatch(request, *args, **kwargs)
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/', HelloView.as_view()),
]
將包含這個檢視層的 django demo 工程啟動後,請求 /hello/
路徑:
[root@server ~]# curl http://127.0.0.1:8881/hello/
get
[root@server ~]# curl -XPOST http://127.0.0.1:8881/hello/
post
[root@server ~]# curl -XPUT http://127.0.0.1:8881/hello/
put
[root@server ~]# curl -XDELETE http://127.0.0.1:8881/hello/
delete
可以看到,這樣封裝的檢視類對應的 web api 介面具有良好的 restful 風格,而且程式碼可讀性也非常好。 後面我們會深入學習這種檢視模式以及 Django 內部封裝的各種 view 類。
2. Django 中的 HttpRequest 類
上面我們初步接觸到了 HttpRequest 類,現在來詳細介紹下這個類及其相關屬性和方法。當 URLconf 檔案匹配到客戶端的請求路徑後,會呼叫對應的 FBV 或者 CBV,並將 HttpRequest 類的例項作為第一個引數傳入對應的處理函式中。那麼這個 HttpRequest 類有哪些常用的屬性和方法呢?
常用屬性:
-
HttpRequest.scheme:請求的協議,一般為 http 或者 https;
-
HttpRequest.body:請求主體;
-
HttpRequest.path: 所請求 URL 的完整路徑,即去掉協議,主機地址和埠後的路徑;
-
HttpRequest.method:客戶端 HTTP 請求方法,如 GET、POST、PUT、DELETE等;
-
HttpRequest.GET: 返回一個 querydict 物件,該物件包含了所有的 HTTP 請求中 GET 請求的引數;
-
HttpRequest.POST: 返回一個 querydict 物件,該物件包含了所有的 HTTP 請求中 POST 請求的引數;
-
HttpRequest.COOKIES:返回一個包含了所有 cookies 的字典;
-
HttpRequest.FILES:返回一個包含所有檔案物件的字典。
常用方法:
-
HttpRequest.get_host():返回客戶端發起請求的 IP + 埠;
-
HttpRequest.get_port():返回客戶端請求埠;
-
HttpRequest.get_full_path():返回請求的完整路徑,包括 “?” 後面所帶引數;
-
HttpRequest.get_raw_uri():返回完整的 uri 地址,包括了協議、主機和埠以及完整請求路徑;
-
HttpRequest.build_absolute_uri():通過 request 例項中的地址和變數生成絕對的 uri 地址。
示例程式碼:
# 省略了import內容
def hello_world(request, *args, **kwargs):
request_info = ""
request_info += "request.scheme={}\n".format(request.scheme)
request_info += "request.body={}\n".format(request.body)
request_info += "request.path={}\n".format(request.path)
request_info += "request.method={}\n".format(request.method)
request_info += "request.GET={}\n".format(request.GET)
request_info += "request.FILES={}\n".format(request.FILES)
request_info += "request.get_host={}\n".format(request.get_host())
request_info += "request.get_port={}\n".format(request.get_port())
request_info += "request.get_full_path={}\n".format(request.get_full_path())
request_info += "request.get_raw_uri={}\n".format(request.get_raw_uri())
request_info += "request.build_absolute_uri={}\n".format(request.build_absolute_uri())
return HttpResponse(request_info, content_type="text/plain")
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/', hello_world),
]
我們啟動 Django 服務後,我們使用 curl
命令傳送 HTTP 請求如下:
# 準備一個新的檔案
[root@server ~]# cat upload_file.txt
upload file test
[root@server ~]# curl -XPOST "http://127.0.0.1:8881/hello/?a=xxx&a=yyy&b=zzz" -F 'data={"name": "join", "age": 28}' -F "file=@/root/upload_file.txt"
request.scheme=http
request.body=b'------------------------------c28860e155fe\r\nContent-Disposition: form-data; name="data"\r\n\r\n{"name": "join", "age": 28}\r\n------------------------------c28860e155fe\r\nContent-Disposition: form-data; name="file"; filename="upload_file.txt"\r\nContent-Type: text/plain\r\n\r\nupload file test\n\r\n------------------------------c28860e155fe--\r\n'
request.path=/hello/
request.method=POST
request.GET=<QueryDict: {'a': ['xxx', 'yyy'], 'b': ['zzz']}>
request.FILES=<MultiValueDict: {'file': [<InMemoryUploadedFile: upload_file.txt (text/plain)>]}>
request.get_host=127.0.0.1:8881
request.get_port=8881
request.get_full_path=/hello/?a=xxx&a=yyy&b=zzz
request.get_raw_uri=http://127.0.0.1:8881/hello/?a=xxx&a=yyy&b=zzz
request.build_absolute_uri=http://127.0.0.1:8881/hello/?a=xxx&a=yyy&b=zzz
通過測試結果可以更容易理解 HttpRequest 類屬性和方法的含義。其中,上述 curl 請求中 -F 表示帶表單資料。
3. Django 檢視函式的返回值
對於檢視函式的返回值,往往有如下幾種方式:
3.1 直接返回字串
直接返回字串是非常常見的一種方式,不過我們需要將字串作為引數傳到 HttpResponse 類中例項化後返回:
def hello_world(request, *args, **kwargs):
return HttpResponse('要返回的字串')
Tips:HttpResponse:是 Django 中封裝的用於返回響應的類 。
3.2 返回 json 型別
檢視函式直接返回 json 資料是在微服務架構中常見的套路。這裡 Django 程式只提供後端資料並不提供靜態資源。針對返回 json 資料,在 Django 中專門定義了一個 JsonResponse 類用來生成 json 資料。它實際上是繼承自 HttpResponse 類:
# django/http/response.py
# 忽略其他內容
class JsonResponse(HttpResponse):
"""
忽略註釋部分內容
"""
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
json_dumps_params=None, **kwargs):
if safe and not isinstance(data, dict):
raise TypeError(
'In order to allow non-dict objects to be serialized set the '
'safe parameter to False.'
)
if json_dumps_params is None:
json_dumps_params = {}
kwargs.setdefault('content_type', 'application/json')
data = json.dumps(data, cls=encoder, **json_dumps_params)
super().__init__(content=data, **kwargs)
JsonResponse 類的使用和 HttpResponse 類一樣簡單,我們只需要把字典資料傳給 JsonResponse 類進行例項化即可。但是字典資料中存在中文時候,會出現亂碼,我們只需要在例項化 JsonResponse 時,多傳入一個引數即可:
# 在頁面上會出現亂碼
def hello_world(request, *args, **kwargs):
data = {'code': 0, "content": "返回中文字串", "err_msg": ""}
return JsonResponse(data)
# 經過處理後的JsonResponse
def hello_world(request, *args, **kwargs):
data = {'code': 0, "content": "返回中文字串", "err_msg": ""}
return JsonResponse(data, json_dumps_params={'ensure_ascii': False})
請求結果:
# 第一個不處理的 JsonResponse 返回
[root@server ~]# curl "http://127.0.0.1:8881/hello/"
{"code": 0, "content": "\u8fd4\u56de\u4e2d\u6587\u5b57\u7b26\u4e32", "err_msg": ""}
# 使用第二個資料處理後的HttpResponse返回
[root@server ~]# curl "http://127.0.0.1:8881/hello/"
{"code": 0, "content": "返回中文字串", "err_msg": ""}
另外一種比較好的方式是,仿照 JsonResponse 類,定義一個支援返回包含中文 json 資料的 Response 類:
# 忽略匯入模組
# 將原來支援的json_dumps_params引數固定寫死成{'ensure_ascii':False}
class JsonResponseCn(HttpResponse):
"""
忽略註釋部分內容
"""
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, **kwargs):
if safe and not isinstance(data, dict):
raise TypeError(
'In order to allow non-dict objects to be serialized set the '
'safe parameter to False.'
)
kwargs.setdefault('content_type', 'application/json')
data = json.dumps(data, cls=encoder, **{'ensure_ascii':False})
super().__init__(content=data, **kwargs)
這樣處理後,我們在和原來一樣使用 JsonResponseCn 類來返回 json 資料即可,不用考慮返回的字典資料中是否包含中文字元:
def hello_world(request, *args, **kwargs):
data = {'code': 0, "content": "返回中文字串", "err_msg": ""}
return JsonResponseCn(data)
3.3 呼叫 render 函式返回
HTML 文字或者模板檔案。其實,通過檢視 Django 的原始碼,可以看到 render 函式會呼叫 loader.render_to_string()
方法將 html 檔案轉成 string,然後作為 content 引數傳遞給 HttpResponse 類進行例項化:
# django/shortcuts.py
def render(request, template_name, context=None, content_type=None, status=None, using=None):
"""
Return a HttpResponse whose content is filled with the result of calling
django.template.loader.render_to_string() with the passed arguments.
"""
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
它的用法如下:
# 第一步,在django工程中準備一個靜態檔案,放到templates目錄下
(django-manual) [root@server first_django_app]# cat templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>這是首頁</h1>
</body>
</html>
# 第二步,在first_django_app/setting.py檔案,指定靜態資源的目錄
(django-manual) [root@server first_django_app]# cat first_django_app/settings.py
...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 指定專案目錄下的templates目錄
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
...
# 第三步,新增檢視函式以及URLconf,位置first_django_app/urls.py
def index(request, *args, **kwargs):
return render(request, "index.html")
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', index),
]
就這樣一個簡單的配置,我們請求 /index/ 路徑時,會返回 index.html 檔案內容:
[root@server ~]# curl "http://127.0.0.1:8881/index/"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>這是首頁</h1>
</body>
</html>
另一方面,index.html 還可以是一個模板檔案,我們通過 render 函式最後會將該模板檔案轉成一個完整的 HTML 檔案,返回給客戶端:
# index.html
(django-manual) [root@server first_django_app]# cat templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{{ title }}</h1>
<p>{{ content }}</p>
</body>
</html>
# 修改 urls.py 中的檢視函式
(django-manual) [root@server first_django_app]# cat first_django_app/urls.py
...
def index(request, *args, **kwargs):
return render(request, "index.html", {"title":"首頁", "content": "這是正文"})
...
最後請求結果可以看到完整的 HTML 文字:
[root@server ~]# curl "http://127.0.0.1:8881/index/"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首頁</h1>
<p>這是正文</p>
</body>
</html>
3.4 呼叫 redirect 函式
實現重定向功能,它的常用形式如下:
from django.shortcuts import redirect
# 指定完整的網址
def redirect_view1(request):
# 忽略其他
return redirect("http://www.baidu.com")
# 指定內部path路徑
def redirect_view2(request):
# 忽略其他
return redirect("/index/")
def redirect_view3(request):
# 忽略其他
return redirect(reverse('blog:article_list'))
def redirect_view4(request):
# 忽略其他
return redirect('some-view-name', foo='bar')
4. 給檢視加裝飾器
在 Django 工程中,由於檢視有兩種:FBV 和 CBV,因此檢視加裝飾器的情況也分為兩種:給檢視函式加裝飾器和給檢視類加裝飾器。由於檢視函式是普通的 Python 函式,因此給檢視函式加裝飾器和給普通函式加裝飾器方式一致。下面程式碼中我們給檢視函式 index 加了一個簡單的裝飾器,用於在執行檢視函式前和後各列印一段字元:
from django.shortcuts import render, HttpResponse, redirect
def wrapper(f):
def innser(*args, **kwargs):
print('before')
ret = f(*args, **kwargs)
print('after')
return ret
return innser
@wrapper
def index(request):
return render(request, 'index.html')
由於類中的方法與普通函式不完全相同,因此不能直接將函式裝飾器應用於類中的方法 ,我們需要先將其轉換為方法裝飾器。Django 中提供了 method_decorator
裝飾器用於將函式裝飾器轉換為方法裝飾器:
# 定義函式裝飾器
def wrapper(f):
def innser(*args, **kwargs):
print('before')
ret = f(*args, **kwargs)
print('after')
return ret
return innser
# 另一個程式碼檔案中使用wrapper裝飾器
from django.views import View
from django.utils.decorators import method_decorator
@method_decorator(wrapper, name='get')
class HelloView(View):
# @method_decorator(wrapper)
def get(self, request, *args, **kwargs):
print('get')
return HttpResponse('get\n')
def post(self, request, *args, **kwargs):
print('post')
return HttpResponse('post\n')
def put(self, request, *args, **kwargs):
print('put')
return HttpResponse('put\n')
def delete(self, request, *args, **kwargs):
print('delete')
return HttpResponse('delete\n')
@csrf_exempt
# @method_decorator(wrapper)
def dispatch(self, request, *args, **kwargs):
return super(HelloView, self).dispatch(request, *args, **kwargs)
對於給 View 類新增裝飾器,我們有如下幾種性質:
-
將 method_decorator 裝飾器直接新增到 View 類上。第一個引數指定需要新增的裝飾器,如 wrapper,name 引數可以選擇將裝飾器 wrapper 作用到 View 類中的哪個函式。給類新增裝飾器時,必須要指定 name 引數,不然執行 Django 服務時會報錯;
-
method_decorator 裝飾器可以直接作用於 View 類中的函式上。如果是在 get、post 這類 HTTP 請求方法的函式上,則裝飾器只會作用於呼叫該函式的 http 請求上;
-
如果 method_decorator 裝飾器作用於 dispatch 函式上。由於對於檢視中所有的 HTTP 請求都會先呼叫 dispatch 方法找到對應請求方法的函式,然後再執行。所以這樣對應檢視內的所有 HTTP 請求方式都先經過裝飾器函式處理;
5. 小結
本小節中,我們介紹了檢視的兩種形式 FBV 和 CBV,並進行了簡單的說明。接下來我們詳細描述了 Django 中的 HttpRequest 和 HttpResponse 物件,並進行了程式碼說明。最後我們介紹了給 FBV 和 CBV 加上裝飾器的方法。