一、層次結構
GenericViewSet(ViewSetMixin, generics.GenericAPIView) ---DRF GenericAPIView(views.APIView) ---DRF APIView(View) ---DRF View ---Django
第一階:
# 第一階: ViewSetMixin ViewSet(ViewSetMixin, views.APIView) GenericViewSet(ViewSetMixin, generics.GenericAPIView) ModelViewSet(mixins.{C/R/U/D/L}ModelMixin, GenericViewSet) ReadOnlyModelViewSet(mixins.{R/L}ModelMixin, GenericViewSet)
事實上,除了ViewSetMixin以外,剩余的4個同階類的內容都為空(只有PASS),ViewSetMixin增加了什么行為,后續再解釋。
第二階:
# 第二階: GenericAPIView(views.APIView) CreateAPIView ListAPIView RetrieveAPIView DestroyAPIView UpdateAPIView ListCreateAPIView RetrieveUpdateAPIView RetrieveDestroyAPIView RetrieveUpdateDestroyAPIView
除GenericAPIView以外,剩余的同階類,實質上是GenericAPIView與mixins.{CRUDL}ModelMixin的組合繼承。
在類中,通過重寫相應的HTTP方法(get、put、delete等),調用mixis.{CRUDL}ModelView中的create、list、retrieve等方法。
# Concrete view classes that provide method handlers # by composing the mixin classes with the base view. class CreateAPIView(mixins.CreateModelMixin, GenericAPIView): """ Concrete view for creating a model instance. """ def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) # 以上為CreateAPIView源代碼
第三階:
# 第三階 APIView(View)
APIView(View)為獨一階,地位非常獨特。
第四階:
# 第四階 ContextMixin View TemplateResponseMixin TemplateView(TemplateResponseMixin, ContextMixin, View) RedirectView(View)
第四階由Django提供,較為底層,一般很少去關注和使用,這里也不展開做詳盡分析。
二、View、APIView、GenericAPIView、GenericViewSet的差別
1. Django View
如果使用Django自帶的View,獲取課程列表,代碼大致是這樣的:
import json from django.views.generic.base import View from django.core import serializers from django.http import JsonResponse from .models import Course class CourseListView(View): def get(self, request): """ 通過Django的View實現課程列表頁 """ courses = Course.objects.all()[:10] json_data = serializers.serialize('json', Courses) json_data = json.loads(json_data) return JsonResponse(json_data, safe=False) # 上述代碼使用Django自身的模塊,返回application/json的數據,可以返回HttpResponse,也可以是其子類JsonResponse # 在Django中也有serializers,不同於DRF的serializers,它只能對基本類型進行JSON序列化、反序列化
這是一個普通的CBV,Django通過as_view和dispatch函數,將request請求傳遞給get(self, request)方法,從而返回response。
關於這部分的內容,可以參考 基於類的視圖
2. APIView
如果用APIView來實現,代碼類似於:
from rest_framework.views import APIView from rest_framework.response import Response from .serializers import CourseSerializer class CourseListView(APIView): def get(self, request, format=None): """ 通過APIView實現課程列表頁 """ courses = Course.objects.all() serializer = CourseSerializer(courses, many=True) return Response(serializer.data) # 在APIView這個例子中,調用了drf本身的serializer和Response。
APIView與View的不同之處在於:
- 請求和返回使用的DRF的
Request、
Response,
而不是Django的HttpRequest、
HttpResponse;
- 任何APIException異常都會被捕獲到,並且處理成合適的響應信息;
- 在進行dispatch()分發前,會對請求進行身份認證、權限檢查、流量控制。
支持定義的屬性:
- authentication_classes 列表或元組,身份認證類
- permissoin_classes 列表或元組,權限檢查類
- throttle_classes 列表或元組,流量控制類
3. GenericAPIView
如果用GenericAPIView實現,代碼類似於:
# url(r'^books/(?P<pk>\d+)/$', views.BookDetailView.as_view()), class BookDetailView(GenericAPIView): queryset = BookInfo.objects.all() serializer_class = BookInfoSerializer def get(self, request, pk): book = self.get_object() serializer = self.get_serializer(book) return Response(serializer.data)
由於mixins.{CRUDL}ModelMixin的存在,我們往往可以這么寫,
from rest_framework import mixins from rest_framework import generics class CourseListView(mixins.ListModelMixin, generics.GenericAPIView): """ 課程列表頁 """ queryset = Course.objects.all() serialize_class = CourseSerializer def get(self, request, *args, **kwargs): # list方法是存在於mixins中的,同理,create等等也是,GenericAPIView沒有這些方法! return self.list(request, *args, **kwargs)
# 如果我們直接繼承ListAPIView(mixins.ListModelMixin, GenericAPIView),那么def get(...)方法都可以不寫 class CourseListView(ListAPIView): """ 課程列表頁 """ queryset = Course.objects.all() serialize_class = CourseSerializer

支持定義的屬性:
- queryset 指定queryset
- serializer_class 指定serializer
- pagination_class 指定分頁類
- filter_backends 指定過濾類
- lookup_field 查詢單一數據庫對象時使用的條件字段,默認為'pk'
- lookup_url_kwarg 查詢單一數據時URL中的參數關鍵字名稱,默認與look_field相同
提供的方法:
- get_queryset(self)
- 通過訪問self.queryset,獲取queryset,兩者一般擇其一;
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
- get_serializer_class(self)
- 通過訪問self.serializer_class,獲取serializer_class,兩者一般擇其一;
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) """ 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__ ) return self.serializer_class
- get_serializer(self, args, *kwargs)
- 如果我們在View中,想要獲取serializer instance,可以直接調用此方法。
- get_serializer_context(self)
- 創建request、format、view三個數據對象,作為serializer實例化時的context屬性;
def get_serializer(self, *args, **kwargs): """ Return the serializer instance that should be used for validating and deserializing input, and for serializing output. """ serializer_class = self.get_serializer_class() kwargs['context'] = self.get_serializer_context() return serializer_class(*args, **kwargs) def get_serializer_context(self): """ Extra context provided to the serializer class. """ return { 'request': self.request, 'format': self.format_kwarg, 'view': self }
- get_object(self)
- 該方法對queryset進行過濾操作,返回的obj供view顯示。如果你需要非標准的過濾操作,可以重寫該方法;
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
- filter_queryset()
- 將filter_backends的過濾類,應用到queryset上;
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
4. GenericViewSet
GenericViewSet(ViewSetMixin, generics.GenericAPIView),實際上等於
ViewSetMixin + GenericAPIView,而ViewSetMixin的主要工作,就是重寫了as_view方法。
class ViewSetMixin(object): """ This is the magic. Overrides `.as_view()` so that it takes an `actions` keyword that performs the binding of HTTP methods to actions on the Resource. For example, to create a concrete view binding the 'GET' and 'POST' methods to the 'list' and 'create' actions... view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) """
我們回過頭來看,上述不同視圖在接受web請求時,as_view和CBV方法(get、put、post等)的協同工作方式是不同的:
View:
- URL映射至CBV的as_view方法,該方法通過調用dispatch方法,負責HTTP請求和CBV方法之間的映射(POST to post、GET to get、PUT to put);
APIView:
- 同上
GenericAPIView:
- 同上,區別是通過mixin.{CRUDL}ModelMixin引入了action的概念,可以手動或者自動地在get/put/post等方法中調用list/create/retrieve等action
GenericViewSet
- 重寫了as_view方法,支持類似 MyViewSet.as_view({'get': 'list', 'post': 'create'}) 的動態綁定功能,或者由router.register進行注冊;
如何選擇:
- 如果使用router.register進行URL請求的注冊與綁定,建議使用GenericViewSet,最為高效、規范、合理;
- 如果需要重構原有的FBV,建議使用GenericAPIView,改動小、變動少;