django之CBV用法詳解
基於類的檢視(CBV)
檢視是可呼叫的,它接收請求並返回響應。這可能不僅僅是一個函式,Django提供了一些可用作檢視的類的示例。這些允許您通過利用繼承和mixin來構建檢視並重用程式碼。
基於類的檢視(Class-based views)提供了另一種將檢視實現為Python物件而不是函式的方法。它們不替換基於函式的檢視,但與基於函式的檢視相比具有一定的差異和優勢:
- 提高了程式碼的複用性,可以使用面嚮物件的技術,比如Mixin(多繼承)
- 可以用不同的函式針對不同的HTTP方法處理,而不是通過很多if判斷,提高程式碼可讀性
內建的基於類的檢視的層次結構:
- 基本檢視:view 、TemplateView、RedirectView
- 通用顯示檢視:DetailView、ListView
- 通用編輯檢視:FormView、CreateView、 UpdateView、DeleteView
- 通用日期檢視: ArchiveIndexView、YearArchiveView、 MonthArchiveView、 WeekArchiveView DayArchiveView、TodayArchiveView、DateDetailView
- 基於類的檢視mixins
- 簡單的mixins:ContextMixin、TemplateResponseMixin
- 單個物件mixins:SingleObjectMixin、SingleObjectTemplateResponseMixin
- 多個物件混合:MultipleObjectMixin、MultipleObjectTemplateResponseMixin
一、類檢視的基本使用
所有類檢視都繼承自Django提供的父類View,可以使用from django.views import View或from
django.views.generic import View來匯入父類View。
#views.py from django.urls import reverse from django.views import View from django.http import HttpResponse from django.shortcuts import render, redirect class Register(View): # 處理GET請求 def get(self,request, *args, **kwargs): return render(request,'App/register.html') # 處理POST請求 def post(self, request, *args, **kwargs): # 註冊業務處理 ... return redirect(reverse("App:login"))
路由註冊:
urlpatterns = [
# 函式註冊
path("register/",views.register,name='register')
# 類檢視註冊
path(r'register/',views.RegisterView.as_view(),name='register')
]
二、基本檢視
1.根檢視View類
提供適合各種應用程式的基本檢視類。所有檢視都繼承自 View 該類,該類處理將檢視連結到URL,HTTP方法排程和其他簡單功能。
View類核心程式碼在as_view和dispatch方法中,其中as_view是類方法(@classonlymethod),只能通過類呼叫,不能通過物件呼叫,它是類檢視的入口點。注意這裡呼叫的時候是通過類名.as_view()呼叫的。
其中,as_view方法主要執行邏輯:
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
# 引數檢查
for key in initkwargs:
if key in cls.http_method_names: # 引數名不能是指定http的方法名
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key): # 引數名必須是類已有屬性
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
# 檢視處理函式
def view(request, *args, **kwargs):
self = cls(**initkwargs) # 例項化當前類的物件
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
# 方法派發
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view # 返回檢視函式
整個as_view方法是一個裝飾器方法,它返回內部函式view,所以as_view()執行其實就是內部函式view執行。內部函式view主要邏輯就是:as_view()=>view()=>dispatch()=>相應的http方法
- 例項化本類物件
- 接收請求物件和引數(setup)
- 呼叫dispatch方法進行派發
呼叫as_view方法可以傳遞引數,但要注意:
- 不能使用請求方法的名字作為引數的名字
- 只能接受檢視類已經存在的屬性對應的引數
dispatch方法是例項方法,它的主要程式碼:
def dispatch(self, request, *args, **kwargs):
#檢查請求方法是不是在http_method_names中包含
#http_method_names包括八種方法:['get', 'post', 'put', 'patch', 'delete', 'head',
#'options', 'trace']
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else: #不在呼叫http_method_not_allowed報錯
handler = self.http_method_not_allowed
#呼叫和請求方法同名的例項方法處理使用者請求,例項方法需要使用者自己定義
return handler(request, *args, **kwargs)
dispatch主要完成http請求方法的派發,呼叫檢視類對應例項方法處理使用者請求,所有使用者需要定義和http請求方法同名的例項方法完成功能,所以一般CBV的模組寫法是:
from django.views import View
class IndexView(View):
def get(self,request):
return HttpResponse("get")
def post(self,request):
return HttpResponse("post")
def put(self,request):
return HttpResponse("put")
def delete(self,request):
return HttpResponse("delete")
2.TemplateView
TemplateView可以根據上下文渲染指定模板,返回響應物件。它繼承了ContentMixin、View、
TemplateResponseMixin。
- ContentMixin用於獲取渲染模板的變數。你可以重寫get_context_data方法返回模板渲染的引數
- TemplateResponseMixin 用於渲染模板
- template_name模板文名(必須設定)
- template_engine模板引擎(有預設值)
- response_class模板渲染類,預設是TemplateResponse
- content_type內容型別,預設是text/html
- get_template_names你可以重寫這個方法返回模板名稱
#路由
urlpatterns = [
url(r'^hello/(\w+)/$',views.HelloView.as_view(template_name='hello.html'),name='hello'),
]
#views.py
class HelloView(TemplateView):
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context['name'] = args[0]
return self.render_to_response(context)
- 注意as_view方法引數只能是template_name、template_engine、response_class、content_type
3.RedirectView
重定向的指定url
get_redirect_url用於構造重定向的目標URL,可以重寫。
預設實現url用作起始字串,並%使用URL中捕獲的命名組在該字串中執行命名引數的擴充套件。
如果url未設定,則get_redirect_url()嘗試反轉 pattern_name使用URL中捕獲的內容(使用已命名和未命名的組)。
如果請求query_string,它還會將查詢字串附加到生成的URL。子類可以實現他們希望的任何行為,只要該方法返回可重定向的URL字串即可。
三、通用顯示檢視
本類檢視主要使用者資料展示,包括ListView顯示物件列表資訊和DetailView顯示物件詳細資訊
3.1 ListView
顯示物件列表頁面
-
MultipleObjectTemplateResponseMixin
- 提供了模板檔名
- 如果沒有指定模板檔名,則預設模板檔名規則是:應用名/模型名_list.html
-
MultipleObjectMixin
核心類,提供了渲染模板所有需要的模型或查詢結果集(不一定是QuerySet,可以是物件列表),分頁。
-
queryset屬性用於渲染模板所需物件列表,也可以重寫get_queryset方法獲取
-
model,如果沒指定queryset,則根據指定model獲取物件列表
-
context_object_name模板中物件列表的名稱,如果不指定,則根據model獲取物件列表名稱:model_list
-
paginate_by指定分頁每頁的記錄個數,預設是None,不分頁
-
page_kwarg指定分頁請求路徑中命名分組名或get傳參中鍵的名稱,預設是page
-
paginate_orphans是指分頁最後一頁如果記錄不滿一頁的處理方式,預設是0,和前一頁合併顯示,如果不為0,則單獨顯示一頁
-
分頁具體實現:
def get_context_data(self, **kwargs): """ Get the context for this view. """ queryset = kwargs.pop('object_list',self.object_list) page_size = self.get_paginate_by(queryset) context_object_name = self.get_context_object_name(queryset) if page_size: paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size) context = { 'paginator': paginator, #在模板中可以使用分頁器 'page_obj': page, #分頁物件 'is_paginated': is_paginated, 'object_list': queryset #當前頁資料 } else: context = { 'paginator': None, 'page_obj': None, 'is_paginated': False, 'object_list': queryset } if context_object_name is not None: #如果context_object_name不為空 context[context_object_name] = queryset context.update(kwargs) return super(MultipleObjectMixin, self).get_context_data(**context)
-
-
BaseListView預設實現get請求
3.2 DetailView
顯示物件的詳細資訊
- SingleObjectMixin
- pk_url_kwarg 預設值pk,從請求路徑中獲取主鍵的值,請求路徑中引數必須是命名組,組名必須和pk_url_kwarg的值一樣
- slug_url_kwarg預設值是slug,從請求路徑中獲取查詢引數sug的值,請求路徑中引數必須是命名組,組名必須和slug_url_kwarg的值一樣,如果引數中有pk_url_kwarg的值,則slug_url_kwarg不起作用
- slug_field查詢欄位的名稱
- context_object_name模板中引用物件的名稱,預設模板中物件名稱是object
四、通用編輯檢視
本類檢視主要完成物件的增刪改。包括FormView、CreateView、 UpdateView、DeleteView
4.1 FormView
顯示錶單的檢視。出錯時,重新顯示帶有驗證錯誤的表單;成功時,重定向到新的URL。
- FormMixin
- form_class 表單類名
- success_url表單驗證成功後調整的url
- form_valid() 驗證資料成功後的處理
- form_invalid() 驗證不成功的處理
- ProcessFormView
- get渲染表單
- post表單提交
- put建立或修改物件
4.2 CreateView
顯示用於建立物件的表單的檢視,使用驗證錯誤(如果有)重新顯示錶單並儲存物件
- 重要屬性:
- template_name模板檔名
- fields指定的欄位列表
- model關聯模型名
- form_class表單類,如果沒有設定會預設是模型名
4.3 UpdateView
UpdateView的用法和CreateView基本一樣
4.4 DeleteView
刪除指定物件的檢視
五、類檢視使用裝飾器
為類檢視新增裝飾器,可以使用兩種方法。
為了理解方便,我們先來定義一個為函式檢視準備的裝飾器(在設計裝飾器時基本都以函式檢視作為考慮的被裝飾物件),及一個要被裝飾的類檢視。
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求路徑%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
class DemoView(View):
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
在類檢視中使用為函式檢視準備的裝飾器時,不能直接新增裝飾器,需要使用method_decorator將其轉換為適用於類檢視方法的裝飾器。
method_decorator裝飾器使用name引數指明被裝飾的方法
# 為全部請求方法新增裝飾器
@method_decorator(my_decorator, name='dispatch')
class DemoView(View):
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
# 為特定請求方法新增裝飾器
@method_decorator(my_decorator, name='get')
class DemoView(View):
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
如果需要為類檢視的多個方法新增裝飾器,但又不是所有的方法(為所有方法新增裝飾器參考上面例子),可以直接在需要新增裝飾器的方法上使用method_decorator,如下所示
from django.utils.decorators import method_decorator
# 為特定請求方法新增裝飾器
class DemoView(View):
@method_decorator(my_decorator) # 為get方法添加了裝飾器
def get(self, request):
print('get方法')
return HttpResponse('ok')
@method_decorator(my_decorator) # 為post方法添加了裝飾器
def post(self, request):
print('post方法')
return HttpResponse('ok')
def put(self, request): # 沒有為put方法新增裝飾器
print('put方法')
return HttpResponse('ok')