1. 程式人生 > >Django Rest Framework源碼剖析(八)-----視圖與路由

Django Rest Framework源碼剖析(八)-----視圖與路由

name userinfo RF self. provided text 官網 tom .cn

一、簡介

django rest framework 給我們帶來了很多組件,除了認證、權限、序列化...其中一個重要組件就是視圖,一般視圖是和路由配合使用,這種方式給我們提供了更靈活的使用方法,對於使用者而言不同的視圖具有不同的功能,這樣我們可以根據需求定制自己視圖。以下是官網傳送門:http://www.django-rest-framework.org/api-guide/views/

在之前的文章中,由於參雜了權限、認證等(如果不了解請看博客的以前的文章),但在本章中基本可以不使用,所進使用以下簡單模型進行說明:

settings中註冊django rest framework

INSTALLED_APPS = [
    
django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, app01.apps.App01Config, rest_framework, ]

models.py

from django.db import models

# Create your models here.
class UserInfo(models.Model): username = models.CharField(max_length=32,unique=True) password = models.CharField(max_length=64)

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r^admin/, admin.site.urls),
    url(r
^api/v1/users, views.UerView.as_view()), ]
二、各類視圖使用

1.APIView

該視圖是最基本的視圖,之前的章節中已經介紹如何使用,參考:Django Rest Framework源碼剖析(一)-----認證

2.GenericAPIView

該視圖為我們封裝一些靜態字段,用於調用其他組件,示例(解釋請看註解):

from django.shortcuts import render

# Create your views here.

from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from rest_framework import pagination
from app01 import models

class Userserializer(serializers.ModelSerializer):   #序列化類定義,前面章節已經介紹
    class Meta:
        model = models.UserInfo
        fields = __all__  #序列化字段

class Mypagination(pagination.PageNumberPagination):  # 分頁類定義,前面章節也已經介紹
    """自定義分頁"""
    page_size=2  #默認每頁顯示個數配置
    page_query_param = p # 頁面傳參的key,默認是page
    page_size_query_param=size  # 指定每頁顯示個數參數
    max_page_size=4 # 每頁最多顯示個數配置,使用以上配置,可以支持每頁可顯示2~4條數據

class UerView(GenericAPIView):            #視圖
    queryset = models.UserInfo.objects.all()  #數據的queryset
    serializer_class = Userserializer         # 序列化類使用
    permission_classes = []                   # 權限認證,這裏不做認證,前面章節也有介紹權限
    pagination_class = Mypagination #分頁類,前面章節也已經介紹
    def get(self,*args,**kwargs):
        roles=self.get_queryset()  # 獲取queryset,實際取queryset,也就是models.UserInfo.objects.all()
        page_res=self.paginate_queryset(queryset=roles)  #分頁結果
        res=self.get_serializer(instance=page_res,many=True)  #序列化
        return Response(res.data)  #返回結果

訪問http://127.0.0.1:8000/api/v1/users,查看結果如下:

技術分享圖片

3.GenericViewSet

該視圖類需要和路由配合使用,修改後的urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r^admin/, admin.site.urls),
   # url(r‘^api/v1/users‘, views.UserView.as_view()),
    url(r^api/v2/users, views.UserView.as_view({get:show,post:create})), #重寫as_view方法,並對請求方法進行映射

]

views.py

from django.shortcuts import render

# Create your views here.

from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from rest_framework import pagination
from rest_framework.viewsets import GenericViewSet
from app01 import models

class Userserializer(serializers.ModelSerializer):   #序列化類定義,前面章節已經介紹
    class Meta:
        model = models.UserInfo
        fields = __all__  #序列化字段

class Mypagination(pagination.PageNumberPagination):  # 分頁類定義,前面章節也已經介紹
    """自定義分頁"""
    page_size=2  #默認每頁顯示個數配置
    page_query_param = p # 頁面傳參的key,默認是page
    page_size_query_param=size  # 指定每頁顯示個數參數
    max_page_size=4 # 每頁最多顯示個數配置,使用以上配置,可以支持每頁可顯示2~4條數據

class UserView(GenericViewSet):            #視圖
    queryset = models.UserInfo.objects.all()  #數據的queryset
    serializer_class = Userserializer         # 序列化類使用
    permission_classes = []                   # 權限認證,這裏不做認證,前面章節也有介紹權限
    pagination_class = Mypagination #分頁類,前面章節也已經介紹
    def show(self,*args,**kwargs):  #與url中映射的方法名稱相同
        roles=self.get_queryset()  # 獲取queryset,實際取queryset,也就是models.UserInfo.objects.all()
        page_res=self.paginate_queryset(queryset=roles)  #分頁結果
        res=self.get_serializer(instance=page_res,many=True)  #序列化
        return Response(res.data)  #返回結果

    def create(self,*args,**kwargs):
        pass

訪問http://127.0.0.1:8000/api/v2/users,結果和上面結果一樣如圖:

技術分享圖片

4.ModelViewSet

modelViewSet繼承了mixins.CreateModelMixin、mixins.RetrieveModelMixin、mixins.UpdateModelMixin、mixins.DestroyModelMixin、mixins.ListModelMixin、GenericViewSet所以它有很父類的所有功能,而這些父類分別提供創建、獲取、更新、刪除等方法,所以我們不需要寫增刪該查,視圖已經幫我實現了,示例:

此時路由urls.py

"""resetful URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  url(r‘^$‘, views.home, name=‘home‘)
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  url(r‘^$‘, Home.as_view(), name=‘home‘)
Including another URLconf
    1. Import the include() function: from django.conf.urls import url, include
    2. Add a URL to urlpatterns:  url(r‘^blog/‘, include(‘blog.urls‘))
"""
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r^admin/, admin.site.urls),
    url(r^api/v1/users$, views.UserView.as_view({get:list,post:create})),
    #路由,retrieve、delete、create、update、partial_update,在UserView的父類全部實現,其中pk參數必須攜帶,想當於key,類可操作
    url(r^api/v1/users/(?P<pk>\d+)$, views.UserView.as_view({get:retrieve,delete:destroy,post:create,put:update,patch:partial_update})),

]

視圖views.py

from django.shortcuts import render

# Create your views here.

from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from rest_framework import serializers
from rest_framework import pagination
from rest_framework.viewsets import ModelViewSet
from app01 import models

class Userserializer(serializers.ModelSerializer):   #序列化類定義,前面章節已經介紹
    class Meta:
        model = models.UserInfo
        fields = __all__  #序列化字段

class Mypagination(pagination.PageNumberPagination):  # 分頁類定義,前面章節也已經介紹
    """自定義分頁"""
    page_size=2  #默認每頁顯示個數配置
    page_query_param = p # 頁面傳參的key,默認是page
    page_size_query_param=size  # 指定每頁顯示個數參數
    max_page_size=4 # 每頁最多顯示個數配置,使用以上配置,可以支持每頁可顯示2~4條數據

class UserView(ModelViewSet):            #視圖
    queryset = models.UserInfo.objects.all()  #數據的queryset
    serializer_class = Userserializer         # 序列化類使用
    permission_classes = []                   # 權限認證,這裏不做認證,前面章節也有介紹權限
    pagination_class = Mypagination #分頁類,前面章節也已經介紹

訪問http://127.0.0.1:8000/api/v1/users/1,查看多增刪改。

技術分享圖片

三、路由

前面的示例中已經用到類路由的功能,在這裏介紹下router使用方法,一般情況,我們寫路由時候對於增刪該查可能寫多個路由,其實我們可以借助DRF的router自動幫我們生成路由,示例:

"""resetful URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  url(r‘^$‘, views.home, name=‘home‘)
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  url(r‘^$‘, Home.as_view(), name=‘home‘)
Including another URLconf
    1. Import the include() function: from django.conf.urls import url, include
    2. Add a URL to urlpatterns:  url(r‘^blog/‘, include(‘blog.urls‘))
"""
from django.conf.urls import url,include
from django.contrib import admin
from app01 import views
from rest_framework import routers
router=routers.DefaultRouter() #是列化router類
router.register(ruserinfo$,views.UserView)  #註冊一個url,後續會生成我們想要的個url

##生成的url


urlpatterns = [
    url(r^admin/, admin.site.urls),
    url(r^api/v1/users$, views.UserView.as_view({get:list,post:create})),
    #路由,retrieve、delete、create、update、partial_update,在UserView的父類全部實現
    url(r^api/v1/users/(?P<pk>\d+)$, views.UserView.as_view({get:retrieve,delete:destroy,post:create,put:update,patch:partial_update})),
    url(r^api/v1,include(router.urls)),#將生成的url加入到路由中
]

此時我們找一個不存在的頁面查看路由:

技術分享圖片

四、源碼剖析

需要知道的知識點:

  • 子類擁有父類的所有功能

由於視圖類較多,所以我們對源碼的剖析就不一一分析了,這裏以一個常用的GenericAPIView為例子分析下源碼。首先從繼承角度來說,GenericAPIView繼承了APIView,而APIView又繼承了View,而APIView是對django原生的View類的dispatch方法進行了重寫(在認證的篇章已經說明),所以GenericAPIView具有APIView和View的所有功能,下面是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.
    queryset = None           # queryset數據配置,這就是為什麽我們示例中的queryset = models.UserInfo.objects.all()
    serializer_class = None   # 序列化類配置

    # If you want to use object lookups other than pk, set ‘lookup_field‘.
    # For more complex lookup requirements override `get_object()`.
    lookup_field = pk # 瀏覽條數據使用的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):               # 獲取queryset方法
        """
        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             #  將配置querset 轉化為類屬性
        if isinstance(queryset, QuerySet):   #  判斷配置queryset是不是QuerySet類型
            # Ensure queryset is re-evaluated on each request.
            queryset = queryset.all()        # 返回所有該Queryset類型的數據
        return queryset

    def get_object(self):                   # 獲取單個的對象,需要在url中配置pk,其中pk的參數一般是ID
        """
        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.
        """
        serializer_class = self.get_serializer_class()  # 執行get_serializer_class用於獲取序列化類
        kwargs[context] = self.get_serializer_context() 
        return serializer_class(*args, **kwargs)  # 序列化數據,返回序列化以後的結果

    def get_serializer_class(self):  # 獲取序列化類 ,也就是serialzier_class配置
        """
        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 

    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): # 過濾,參數為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     #不存在返回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)         
五、總結

使用建議:

1.對於簡單的數據模型來說,建議使用modelViewSet,因為它已經幫我們寫好了增刪該查接口。

2.對於復雜的數據模型,比如涉及多個表操作或有事務要求時,此時更建議使用APIView或者GenericAPIView,這樣可以根據需求我們自己寫增刪該查接口。

Django Rest Framework源碼剖析(八)-----視圖與路由