23 TemplateView 類檢視詳解
接下來的兩個小節,主要介紹 Django 中的幾個常用的檢視類,我們統一按照這樣的方式進行講解:首先使用該檢視類,完成一個簡單的例子,熟悉該類的使用;接下來深入原始碼分析,完整梳理該檢視類的實現過程,順帶梳理 Django 中和該類相關的原始碼 。
1. TemplateView 類介紹和使用
TemplateView 檢視類是用來渲染給定的模板檔案,其上下文字典包含從 URL 中捕獲的引數。首先來看看最簡單的模板渲染示例:
準備模板檔案,放到 template 目錄,在 settings.py 中需要配置好模板 (TEMPLATES) 相關的引數:
[root@server first_django_app]# cat templates/test.html <p>{{ content }}</p> <div>{{ spyinx.age }}</div>
準備好類檢視,處理相關 HTTP 請求,這裡使用今天要學習的 TemplateView 類:
class TestTemplateView(TemplateView):
template_name = 'test.html'
@csrf_exempt
def dispatch(self, request, *args, **kwargs):
return super(TestTemplateView, self).dispatch(request, *args, **kwargs)
配置相應的 URLConf:
context_data = {'content':'正文1', 'spyinx':{'age': 29}}
urlpatterns = [
path('test_template_view/',
views.TestTemplateView.as_view(extra_context=context_data),
name='test_template_View')
]
啟動 first_django_app 工程,然後使用 curl 命令簡單測試,傳送 GET 請求:
# 啟動first_django_app工程,監聽8888埠 (django-manual) [root@server first_django_app]# python manage.py runserver 0.0.0.0:8888 Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). April 16, 2020 - 12:27:49 Django version 2.2.11, using settings 'first_django_app.settings' Starting development server at http://0.0.0.0:8888/ Quit the server with CONTROL-C. # 開啟xshell另一個視窗,使用curl傳送get請求 [root@server ~]# curl http://127.0.0.1:8888/hello/test_template_view/ <p>正文1</p> <div>29</div> # 這個報405錯誤,不支援POST請求 [root@server ~]# curl -XPOST http://127.0.0.1:8888/hello/test_template_view/ [root@server ~]#
可以看到,我們只是指定了 template_name 屬性,連對應的請求函式都沒寫 (寫的 dispatch() 函式只是為了能執行POST請求,避免 csrf 報錯),只是繼承一下這個檢視類 (TemplateView) 能處理 GET 請求,並能返回渲染的模板,對一些情況來說是很方便的,節省了一些程式碼。注意的是,其他請求方式不行,因為 TemplateView 內部只實現了 get() 方法,所以只能處理 GET 請求。
上面的 context_data 是自定義的,現在我們來從資料庫中獲取資料,動態填充模板內容。同樣是涉及到模板檢視,所以可以通過 TemplateView 類來實現。
首先準備新的模板檔案 test.html。這個模板頁面使用表格分頁顯示會員資料,查詢的資料表是我們前面多次實驗的 member 表。
<html>
<head>
<style type="text/css">
.page{
margin-top: 10px;
font-size: 14px;
}
.member-table {
width: 50%;
text-align: center;
}
</style>
</head>
<body>
<p>會員資訊-第{{ current_page }}頁, 每頁{{ page_size }}條, 總共{{ sum }}條</p>
<div>
<table border="1" class="member-table">
<thead>
<tr>
<th>姓名</th>
<th>年齡</th>
<th>性別</th>
<th>職業</th>
<th>所在城市</th>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<td>member.name</td>
<td>member.age</td>
{% if member.sex == 0 %}
<td>男</td>
{% else %}
<td>女</td>
{% endif %}
<td>member.occupation</td>
<td>member.city</td>
</tr>
{% endfor %}
</tbody>
</table>
<div >
<div class="page">
</div>
</div>
</div>
</body>
</html>
修改上面的檢視函式,分頁查詢 Member 表中的資料:
class TestTemplateView(TemplateView):
template_name = 'test.html'
def get(self, request, *args, **kwargs):
params = request.GET
page = int(params.get('page', 1))
size = int(params.get('size', 5))
data = {}
data['sum'] = Member.objects.all().count()
members = Member.objects.all()[(page - 1) * size:page * size]
data['current_page'] = page
data['page_size'] = size
data['members'] = members
return self.render_to_response(context=data)
這裡我們使用了前面學習的 Django ORM 模型,獲取 member 表中的資料,然後使用 TemplateView 中的模板渲染方法 render_to_response()
返回渲染後的 HTML 文字給到客戶端。
測試結果如下,以下兩張圖片是設定了不同的查詢引數(當前頁碼和頁大小),可以看到查詢引數是起了效果的:
2. TemplateView 類深入分析
2.1 TemplateResponse 和 SimpleTemplateResponse
很早之前,我們介紹過 HttpResponse,它用於生成 HTTP 請求的相應,返回的內容由 content 屬性確定,主要是用於提供靜態內容顯示。TemplateResponse 物件則不同,它允許裝飾器或中介軟體在通過檢視構造響應之後修改響應內容。TemplateResponse 物件保留檢視提供的、用於計算響應內容的模板和上下文資料,直到最後需要時才計算相應內容並返回響應。
SimpleTemplateResponse 物件是 TemplateResponse 的父類,兩者功能和使用基本類似,幾乎是一致的。
# 原始碼位置: django/template/response.py
class TemplateResponse(SimpleTemplateResponse):
rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_request']
def __init__(self, request, template, context=None, content_type=None,
status=None, charset=None, using=None):
super().__init__(template, context, content_type, status, charset, using)
self._request = request
SimpleTemplateResponse 是繼承自 HttpResponse 物件,並做了諸多擴充套件。它的重要屬性和方法如下:
重要屬性:
- template_name:模板檔名;
- context_data : 上下文字典資料;
- rendered_content:指使用當前的模板和上下文欄位資料已渲染的響應內容。一個作為屬性的方法,呼叫該屬性時啟動渲染過程;
- is_rendered: 布林型別,判斷響應內容是否已經被渲染。
重要方法:
-
__init__(template, context=None, content_type=None, status=None, charset=None, using=None):類初始化函式,各引數的含義與
HttpResponse
相同; -
resolve_context(context):預處理會被用於模板渲染的上下文資料 ;
-
resolve_template(template):接收(如由 get_template() 返回的) backend-dependent 的模板物件、模板名字、或者多個模板名字組成的列表。返回 backend-dependent 的模板物件例項,後面用於渲染;
-
add_post_render_callback():新增渲染完成後的回撥函式,如果該方法執行時渲染已完成,回撥函式會被立即呼叫;
-
render():設定 response.content 的結果為 SimpleTemplateResponse.rendered_content 的值,執行所有渲染後的回撥函式,返回所有響應物件。render() 只會在第一次呼叫時起作用。在隨後的呼叫中,它將返回從第一個呼叫獲得的結果。
實驗部分:
我們來使用 TemplateResponse 來完成一個簡單的案例。同樣是在 first_django_app 工程中,準備的程式碼內容參考如下,分別是模板檔案、檢視檔案以及 URLConf 配置檔案。
# 模板檔案: template/test.html
<p>{{ content }}</p>
<div>{{ spyinx.age }}</div>
# URLConf配置檔案: hello_app/urls.py
urlpatterns = [
path('test-cbv/', views.TestView.as_view(), name="test-cbv")
]
# 檢視檔案: hello_app/views.py
def my_render_callback(response):
# Do content-sensitive processing
print('執行渲染完成後的回撥函式,渲染內容:\n{}\n是否完成渲染:{}'.format(response.rendered_content, response.is_rendered))
class TestView(View):
def get(self, request, *args, **kwargs):
response = TemplateResponse(request, 'test.html', context={'content': '正文1', 'spyinx':{'age': 29}})
response.add_post_render_callback(my_render_callback)
return response
我們在雲主機上使用 curl
命令傳送 HTTP 請求,觀察結果:
# 使用runserver命令啟動first_django_app工程
...
# 開啟另一個xshell視窗,使用curl命令傳送請求結果
[root@server ~]# curl http://127.0.0.1:8888/hello/test-cbv/
<p>正文1</p>
<div>29</div>
# 回到上一個視窗,檢視列印結果
(django-manual) [root@server first_django_app]# python manage.py runserver 0.0.0.0:8888
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
April 16, 2020 - 07:10:18
Django version 2.2.11, using settings 'first_django_app.settings'
Starting development server at http://0.0.0.0:8888/
Quit the server with CONTROL-C.
執行渲染完成後的回撥函式,渲染內容:
<p>正文1</p>
<div>29</div>
是否完成渲染:True
[16/Apr/2020 07:10:38] "GET /hello/test-cbv/ HTTP/1.1" 200 29
2.2 ContextMixin 和 TemplateResponseMixin
接下來,我們檢視下 Django 提供的兩個 mixin:ContextMixin 和 TemplateResponseMixin。其內容比較簡單,原始碼如下:
# 原始碼位置:django/views/generic/base.py
class ContextMixin:
"""
A default context mixin that passes the keyword arguments received by
get_context_data() as the template context.
"""
extra_context = None
def get_context_data(self, **kwargs):
kwargs.setdefault('view', self)
if self.extra_context is not None:
kwargs.update(self.extra_context)
return kwargs
class TemplateResponseMixin:
"""A mixin that can be used to render a template."""
template_name = None
template_engine = None
response_class = TemplateResponse
content_type = None
def render_to_response(self, context, **response_kwargs):
"""
Return a response, using the `response_class` for this view, with a
template rendered with the given context.
Pass response_kwargs to the constructor of the response class.
"""
response_kwargs.setdefault('content_type', self.content_type)
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
using=self.template_engine,
**response_kwargs
)
def get_template_names(self):
"""
Return a list of template names to be used for the request. Must return
a list. May not be called if render_to_response() is overridden.
"""
if self.template_name is None:
raise ImproperlyConfigured(
"TemplateResponseMixin requires either a definition of "
"'template_name' or an implementation of 'get_template_names()'")
else:
return [self.template_name]
ContextMixin 比較簡單,只提供了一個屬性 extra_context
和一個方法 get_context_data()
,它主要的功能是根據額外提供的引數,組成新的上下文字典,呼叫 get_context_data()
方法即可實現。
TemplateResponseMixin 是用來渲染模板的,它的屬性與方法如下:
屬性:
- template_name:模板檔名;
- template_engine: 模板引擎;
- response_class: 返回的 Response,預設是 TemplateResponse;
- content_type:返回客戶端的資料型別。
方法:
- render_to_response():預設直接呼叫 TemplateResponse(),完成模板渲染後返回響應給客戶端;
- get_template_names():獲取模板名稱,返回列表的形式;如果沒有設定 template_name 屬性值,則會報錯。因此對於繼承該 mixin 物件的子類,必須要設定 template_name 屬性值。
Mixin 的特點就是功能簡單,它們可以混合加到其他類中,那麼其他類就具備這些 Mixin 的功能,組合得到一個更高階的類。同樣我們在前一小節實驗的基礎上改造下 views.py
中的內容,如下:
class TestView(TemplateResponseMixin, View):
template_name = 'test.html'
def get(self, request, *args, **kwargs):
return self.render_to_response(context={'content': '正文1', 'spyinx': {'age': 29}})
這裡我們額外繼承 TemplateResponseMixin 類。首先需要新增 template_name 屬性,指定要渲染的模板檔案,然後呼叫從 TemplateResponseMixin
中繼承過來的 render_to_response()
方法並返回。從原始碼角度來看,這個檢視實現的功能和上一個小節中直接返回 TemplateResponse
例項是一樣的。
# 啟動 first_django_app 工程
...
# 開啟xshell另一個視窗,傳送http請求
[root@server first_django_app]# curl http://127.0.0.1:8888/hello/test-cbv/
<p>正文1</p>
<div>29</div>
2.3 TemplateView 類
介紹完上面那些基礎的類以及 Mixin 後,終於可以看 TemplateView 的程式碼了,我們發現它其實就是 View 類中混入了 ContextMixin 和 TemplateResponseMixin,此外還多了一個 get 函式,對應著 get 請求。
# 原始碼路徑 django/views/generic/base.py
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
Render a template. Pass keyword arguments from the URLconf to the context.
"""
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
就是這個 get 請求方法讓我們前面第一個實驗中,只需要寫對應的模板檔名,另外傳遞 extra_context 就可以渲染模板並返回。注意,這個 extra_context 屬性是繼承自 ContextMixin 物件,它的賦值過程是走的 View 類。現在可以通過程式碼分析一下這個複製過程,加深印象。
首先看 as_view() 函式傳參部分:
context_data = {'content':'正文1', 'spyinx':{'age': 29}}
urlpatterns = [
path('test_template_view/',
views.TestTemplateView.as_view(extra_context=context_data),
name='test_template_View')
]
as_view() 方法的原始碼如下。傳進來的引數在 initkwargs 中,最後在 View 類中使用該字典引數初始化例項。
# 原始碼位置: django/views/generic/base.py
@classonlymethod
def as_view(cls, **initkwargs):
# ...
def view(request, *args, **kwargs):
self = cls(**initkwargs)
# ...
# ...
return view
最後來看 View 的初始化函式。可以看到這些初始化的引數會作為類的屬性進賦值,使用的是 setattr()
方法,這樣我們在 as_view() 方法中傳遞的 extra_context 引數就被成功賦值到 extra_context 屬性上了。
class View:
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
# ...
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in kwargs.items():
setattr(self, key, value)
其實通過了解這些原始碼後,我們能很好得理解前面使用 TemplateView 類做的一些測試結果。目前來看,我們接觸到的這部分原始碼還是比較簡單易懂的,官方網站對 Django 中出現的許多常用類都做了詳細的介紹,我們只需要不停的學習原始碼、參考官方的文件、堅持不懈,一定能達到精通 Django 的目的。
3. 小結
本小節中,我們先介紹了 TemplateView 的幾種常見用法。接下來開始深入 Django 原始碼,分析 TemplateView 繼承的兩個 mixin,最後通過整個原始碼分析和測試,對於前面 TemplateView 的用法會理解的更加透徹。