從零學DRF:源碼剖析與實戰(四)——視圖

兩個視圖基類介紹
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,它繼承自ViewSetMixingenerics.GenericAPIViewViewSetMixin重寫了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以及UpdateModelMixinGenericViewSet開發的時候,比APIView有一個優點就是他可以在路由裏面根據請求映射到對應的視圖函數裏面。如果我們的某個類裏邊只要實現get或者post方法,那麼也可以使用工具視圖,不過這個應該比較少用

a. 增刪改查 ModelViewSet
b. 增刪 CreateModelMixin,DestroyModelMixin GenericViewSet
c. 複雜邏輯 GenericViewSet 或 APIView

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章