前言
ViewSet
只是一種基於類的視圖,它不提供任何方法處理程序(如 .get()
或.post()
),而是提供諸如.list()
和 .create()
之類的操作。
ViewSet
的方法處理程序僅使用 .as_view()
方法綁定到完成視圖的相應操作。
通常不是在urlconf
中的視圖集中顯示注冊視圖,而是要使用路由類注冊視圖集,該類會自動為你確定 urlconf
。
源碼分析
我們首先看一下viewsets.py
文件的源碼結構,如下圖
我們可以看到有5個類
- ViewSetMixin
- ViewSet:繼承自
ViewSetMixin
和APIView
- GenericViewSet:繼承自
ViewSetMixin
和GenericAPIView
- ReadOnlyModelViewSet:繼承自
RetrieveModelMixin
、ListModelMixin
、GenericViewSet
- ModelViewSet:繼承自5大
mixins
工具類和GenericViewSet
ViewSetMixin
通過上述代碼結構分析,我們了解到只要知道ViewSetMixin
是干嘛的,其他的類都繼承於它。從源碼中我們知道,ViewSetMixin
重寫了as_view()
方法,源碼如下:
def as_view(cls, actions=None, **initkwargs):
"""
由於基於類的視圖圍繞實例化視圖創建閉包的方式,我們需要完全重新實現`.as_view`,並稍微修改創建和返回的視圖函數。
對於某些路由配置,initkwargs 的名稱和描述可能會被明確覆蓋,例如,額外操作的名稱。
"""
# 名稱和描述 initkwargs 可能會被顯式覆蓋
cls.name = None
cls.description = None
# 后綴 initkwarg 保留用於顯示視圖集類型。如果提供了名稱,則此 initkwarg 應該無效。
cls.suffix = None
cls.detail = None
# 設置 basename 允許視圖反轉其操作 url。該值由路由器通過 initkwargs 提供。
cls.basename = None
# actions必須不能為空,否則報錯
if not actions:
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
# 清理關鍵字參數
for key in initkwargs:
if key in cls.http_method_names:
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" % (
cls.__name__, key))
# name和suffix是互斥的
if 'name' in initkwargs and 'suffix' in initkwargs:
raise TypeError("%s() received both `name` and `suffix`, which are "
"mutually exclusive arguments." % (cls.__name__))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if 'get' in actions and 'head' not in actions:
actions['head'] = actions['get']
self.action_map = actions
# 將方法綁定到actions, 這是與標准視圖不同的一點
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
update_wrapper(view, cls, updated=())
update_wrapper(view, cls.dispatch, assigned=())
view.cls = cls
view.initkwargs = initkwargs
view.actions = actions
return csrf_exempt(view)
從上述源碼中了解到,ViewSetMixin
重寫了as_view
方法,as_view
是將請求的方法綁定到了actions
ViewSet
class ViewSet(ViewSetMixin, views.APIView):
"""
默認情況下,基本 ViewSet 類不提供任何操作。
"""
pass
ViewSet
繼承了ViewSetMixin
和APIView
,增刪改查需要我們自己定義
GenericViewSet
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
GenericViewSet 類默認不提供任何操作,但包含通用視圖行為的基本集,例如`get_object` 和`get_queryset` 方法。
"""
pass
GenericViewSet
類相比ViewSet
,包含了一些視圖行為的通用方法
視圖集特點
- 視圖集都是優先繼承
ViewSetMixin
類,再繼承一個視圖類(GenericAPIView或APIView)
ViewSetMixin
提供了重寫的as_view()
方法,繼承視圖集的視圖類,配置路由時調用as_view()
必須傳入 請求-函數名 映射關系字典
eg: path('v1/books/<int:pk>/', views.BookGenericViewSet.as_view({"get": "my_get_obj"}))
GenericAPIView與APIView 作為兩大繼承視圖的區別
GenericViewSet
和ViewSet
都繼承了ViewSetMixin
,as_view
都可以配置 請求-函數 映射GenericViewSet
繼承的是GenericAPIView
視圖類,用來完成標准的model
類操作接口ViewSet
繼承的是APIView
視圖類,用來完成不需要model
類參與,或是非標准的model
類操作接口
post
請求在標准的model
類操作下就是新增接口,登陸的post
不滿足
post
請求驗證碼接口,不需要model
類的參與
案例:登陸的post
請求,並不是完成數據的新增,只是用post
提交數據,得到的結果也不是登陸的用戶信息,而是登陸的認證信息
ReadOnlyModelViewSet
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
提供默認`list()` 和`retrieve()` 操作的視圖集。
"""
pass
ModelViewSet
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
一個提供默認 `create()`、`retrieve()`、`update()`、`partial_update()`、`destroy()` 和 `list()` 操作的視圖集。
"""
pass
實戰案例
視圖函數如下
class StudentViewSets(viewsets.ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def my_get(self, request, *args, **kwargs):
response = self.retrieve(request, *args, **kwargs)
return APIResponse(results=response.data)
def my_list(self, request, *args, **kwargs):
response = self.list(request, *args, **kwargs)
return APIResponse(results=response.data)
我們繼承自ModelViewSet
,自帶5個mixins
工具,我們定義了2個查詢方法,然后在urls
中配置
urlpatterns = [
path('v2/student/<int:pk>/', views.StudentViewSets.as_view({"get": "my_get"})),
path('v2/student/', views.StudentViewSets.as_view({"get": "my_list"})),
]
在as_view
中添加了get
請求方式的方法,有pk
調用my_get
代表單查,沒有pk
調用my_list
代表群查,這樣寫的原因就是我們的StudentViewSets
繼承了ViewSetMixin
。