兩個視圖基類介紹
APIView
APIView是REST framework提供的所有視圖的基類,繼承自Django的View父類。
相比View,主要多出來的功能有:
- 視圖方法可以返回rest_framework中的Response對象,APIView視圖會爲響應的數據渲染一個漂亮的前端頁面,而不是僅僅返回字符串。
- 會捕獲APIException異常,基本所以的都會被捕獲到,並且會幫我們處理成合適的響應信息;
- APIView的dispatch()裏面實現了像認證,權限,以及頻率限制等其他功能。
GenericAPIView
簡單使用示例
class BookGenericAPIView(GenericAPIView):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer
lookup_field = 'pk' # 默認就是pk,如果路由寫正則的時候,用的是
def get(self, request, *args, **kwargs):
# 這裏後面不用加.all(),因爲源碼內部幫我們加了
# 羣查
book_query = self.get_queryset() # 獲取query數據
book_ser = self.get_serializer(book_query, many=True)
book_data = book_ser.data
return APIResponse(results=book_data)
postman發送GET請求測試
GenericAPIView源碼
先喫一份GenericAPIView的源碼,關鍵部分已做了註釋,
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
# You'll need to either set these attributes,
# or override `get_queryset()`/`get_serializer_class()`.
# If you are overriding a view method, it is important that you call
# `get_queryset()` instead of accessing the `queryset` property directly,
# as `queryset` will get evaluated only once, and those results are cached
# for all subsequent requests.
"""
下面這些是GenericAPIView的類屬性
我們繼承GenericAPIView後,可以重寫他們
"""
# 指明視圖需要的數據(從model中查詢到的queryset對象)
queryset = None
# serializer_class指明視圖使用的序列化器
serializer_class = None
# If you want to use object lookups other than pk, set 'lookup_field'.
# For more complex lookup requirements override `get_object()`.
# 自定義主鍵,默認是pk
lookup_field = 'pk'
lookup_url_kwarg = None
# The filter backend classes to use for queryset filtering
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
# The style to use for queryset pagination.
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get_queryset(self):
"""
Get the list of items for this view.
This must be an iterable, and may be a queryset.
Defaults to using `self.queryset`.
This method should always be used rather than accessing `self.queryset`
directly, as `self.queryset` gets evaluated only once, and those results
are cached for all subsequent requests.
You may want to override this if you need to provide different
querysets depending on the incoming request.
(Eg. return a list of items that is specific to the user)
"""
assert self.queryset is not None, (
"'%s' should either include a `queryset` attribute, "
"or override the `get_queryset()` method."
% self.__class__.__name__
)
queryset = self.queryset
if isinstance(queryset, QuerySet):
# Ensure queryset is re-evaluated on each request.
queryset = queryset.all()
return queryset
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
# get_serializer_class()返回serializer_class,沒有寫serializer_class會報錯
serializer_class = self.get_serializer_class()
# get_serializer_context返回一個字典
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
def get_serializer_class(self):
"""
Return the class to use for the serializer.
Defaults to using `self.serializer_class`.
You may want to override this if you need to provide different
serializations depending on the incoming request.
(Eg. admins get full serialization, others get basic serialization)
"""
# 先斷言,如果serializer_class沒有重寫,就報錯
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
# 返回serializer_class
return self.serializer_class
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
@property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator
def paginate_queryset(self, queryset):
"""
Return a single page of results, or `None` if pagination is disabled.
"""
if self.paginator is None:
return None
return self.paginator.paginate_queryset(queryset, self.request, view=self)
def get_paginated_response(self, data):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data)
源碼分析總結
GenericAPIView是繼承APIView的,使用完全兼容APIView,主要增加了操作序列化器和數據庫查詢的方法,作用是爲下面Mixin擴展類的執行提供方法支持。通常在使用時,可以配合一個或多個Mixin擴展類
重點:GenericAPIView在APIView基礎上完成了哪些事
1)get_queryset():從類屬性queryset中獲得model的queryset數據 羣操作就走get_queryset()方法(包括羣查,羣增等)
2)get_object():從類屬性queryset中獲得model的queryset數據,再通過有名分組pk確定唯一操作對象 單操作就走get_object()方法(包括單查,單增等)
3)get_serializer():從類屬性serializer_class中獲得serializer的序列化類
五個視圖擴展類
1.ListModelMixin(羣查)
列表視圖擴展類,提供 list 方法快速實現查詢視圖,返回200狀態碼。除了查詢,該list方法會對數據進行過濾和分頁
2.CreateModelMixin(單增) #注意:沒有羣增的方法,需要自己手動寫(******)
創建視圖擴展類,提供create方法快速創建資源的視圖,成功返回201的狀態碼
3.RetrieveModelMixin(單查)
詳情視圖擴展類,提供retrieve方法,可以快速實現返回一個存在的數據對象。
4.UpdateModelMixin(更新,修改) #只有單整體改和單局部改,沒有羣整體改和羣局部改
更新視圖擴展類,提供update方法,可以快速實現更新一個存在的數據對象,同時也提供partial_update方法,可以實現局部更新。
5.DestoryModelMixin(刪除) 一般不怎麼用到
刪除視圖擴展類,提供destory方法,可以快速實現刪除一個存在數據對象。
視圖擴展類使用示例
class BookMixinGenericAPIView(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, CreateModelMixin, GenericAPIView):
# 調用工具類的時候會去找serializer_class等,所以得先定義
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer
lookup_field = 'pk'
def get(self, request, *args, **kwargs):
if request.GET.get('pk'):
response = self.retrieve(request, *args, **kwargs) # 單查,使用RetrieveModelMixin
else:
# 羣查
response = self.list(request, *args, **kwargs)
return APIResponse(results=response.data) # 記得加data吧序列化後的數據拿出來
# 單增
def post(self, request, *args, **kwargs):
response = self.create(request, *args, **kwargs)
return APIResponse(results=response)
# 單整體修改
def put(self, request, *args, **kwargs):
response = self.update(request, *args, **kwargs)
return APIResponse(results=response)
# 單局部修改
def patch(self, request, *args, **kwargs):
response = self.partial_update(request, *args, **kwargs)
return APIResponse(results=response)
常用功能子類視圖(工具視圖)
工具視圖(繼承了GenericAPIView和各種Mixins工具類)
- 1)工具視圖都是GenericAPIView的子類,並且不同的子類繼承了不同的工具類去完成不同的功能
- 2)工具視圖的功能可以滿足需求,只需要繼承工具視圖,並且提供queryset與serializer_class即可
- 3 ) 其實這些東西就是想幫我們省代碼,因爲Django的目標就是爲開發者實現快速開發,是一個重量級框架
使用示例
我們不用再自己寫get(),put()等方法,因爲DRF源碼裏面已經幫我們做了。
from rest_framework.generics import ListCreateAPIView, UpdateAPIView
# 基於常用功能子類視圖實現羣查,新增/更改一條記錄
class BookListCreateAPIView(ListCreateAPIView, UpdateAPIView):
serializer_class = serializers.BookModelSerializer
queryset = models.Book.objects.filter(is_delete=False)
源碼分析
先來一份源碼,因爲這裏很簡單,我就不註釋了。
class CreateAPIView(mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for creating a model instance.
"""
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
class RetrieveAPIView(mixins.RetrieveModelMixin,
GenericAPIView):
"""
Concrete view for retrieving a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
class DestroyAPIView(mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for deleting a model instance.
"""
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class UpdateAPIView(mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for updating a model instance.
"""
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
源碼分析總結:
工具視圖在源碼裏邊的實現是:繼承GenericAPIView
,以及各種Mixin工具類,然後去實現增刪改查方法,也就是那幾行增刪改查的方法都幫我們實現了,如果工具視圖可以滿足我們的需求的話,我們只需要繼承對應的工具視圖類就行了。
視圖集(重點掌握)
視圖集GenericViewSet
源碼裏面只寫了個pass,它繼承自ViewSetMixin
和generics.GenericAPIView
,ViewSetMixin
重寫了as_view()
方法,使得在路由中使用as_view()
可以傳參, generics.GenericAPIView
前面已經分析過源碼,其實就是定義了一些query_set
,serialize_class
類屬性,以及get_queryset
,get_serialize
等方法,
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass
GenericViewSet
我們看看怎麼用的
使用方法:
路由:
url(r'^(?P<version>[v1|v2]+)/v1/$', views.View1View.as_view({'get': 'list'})),
視圖:
from api.utils.serializsers.pager import PagerSerialiser
from rest_framework.viewsets import GenericViewSet
class View1View(GenericViewSet):
queryset = models.Role.objects.all()
serializer_class = PagerSerialiser
pagination_class = PageNumberPagination
def list(self, request, *args, **kwargs):
# 獲取數據
roles = self.get_queryset() # models.Role.objects.all()
# [1, 1000,] [1,10]
pager_roles = self.paginate_queryset(roles)
# 序列化
ser = self.get_serializer(instance=pager_roles, many=True)
return Response(ser.data)
可以看到單純使用GenericViewSet其實作用不大,因爲還要去定義我們的queryset,serializer_class,一般我們使用它都是通過多繼承來用的,除了繼承它,還要繼承一些工具集。比如:
class View1View(CreateModelMixin,GenericViewSet):
下面在來介紹一下最牛的ModelViewSet,如果用這個,我們視圖只需要寫3行代碼就行,不過用它就基本不能自定製了,它一共繼承了6個類,增,刪,改,查,羣查,以及GenericViewSet
,它把我們的視圖工具集全都繼承了。
源碼:
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
使用
路由系統:
url(r'^(?P<version>[v1|v2]+)/v1/$', views.View1View.as_view({'get': 'list','post':'create'})),
url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)/$', views.View1View.as_view({'get': 'retrieve','delete':'destroy','put':'update','patch':'partial_update'})),
視圖:
我們寫好分頁的類以及序列化的類,然後視圖只需要寫這三行代碼就OK了
from api.utils.serializsers.pager import PagerSerialiser
from rest_framework.viewsets import GenericViewSet,ModelViewSet
from rest_framework.mixins import ListModelMixin,CreateModelMixin
class View1View(ModelViewSet):
queryset = models.Role.objects.all()
serializer_class = PagerSerialiser
pagination_class = PageNumberPagination
總結:
有了這麼多種寫法,你一定會問,該寫哪種啊?
視圖類歸納起來大概有三種寫法,分別繼承APIView, GenericViewSet以及ModelViewSet,先說ModelViewSet,它一共繼承了6個類(面試說出來),幫我們實現了增刪改查的功能,如果我們的視圖功能是比較簡單的,只是基本的增刪改查,那麼就使用ModelViewSet,因爲它幫我們實現了這些基本的功能,我們用起來更高效,但是它的自定製太差了,如果我們要實現的視圖的功能是比較複雜的,就要用比較原始一點的APIView或者GenericViewSet,具體而言,如果我們的增刪改查,所有的功能都比較複雜,都想自己去寫,就使用APIView,如果我們的更新功能比較複雜,其他比較簡單,那麼我們就可以繼承GenericViewSet以及UpdateModelMixin,GenericViewSet開發的時候,比APIView有一個優點就是他可以在路由裏面根據請求映射到對應的視圖函數裏面。如果我們的某個類裏邊只要實現get或者post方法,那麼也可以使用工具視圖,不過這個應該比較少用
a. 增刪改查 ModelViewSet
b. 增刪 CreateModelMixin,DestroyModelMixin GenericViewSet
c. 複雜邏輯 GenericViewSet 或 APIView