rest-framework框架——視圖三部曲

1、mixins類編寫視圖

一、配置url

urlpatterns = [
    ...
    re_path(r'^authors/$', views.AuthorView.as_view(), name="author"),
    re_path(r'^authors/(?P<pk>\d+)/$', views.AuthorDetailView.as_view(), name="detail_author")
]

二、編寫Author的序列化類

/app01/serializer.py:前端

class AuthorModelSerializers(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = "__all__"

三、編寫Author的視圖

# Author
from rest_framework import mixins, generics


class AuthorView(mixins.ListModelMixin,        # 擴展了列出查詢集功能
                 mixins.CreateModelMixin,      # 擴展了建立和保存新模型實例功能
                 generics.GenericAPIView):     # 繼承擴展了REST框架的APIView類,爲標準列表和詳細視圖添加了常見的行爲
    queryset = Author.objects.all()     # 配置queryset:告知這個類此次處理的數據
    serializer_class = AuthorModelSerializers     # 告知處理用到的序列化組件

    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 AuthorDetailView(mixins.RetrieveModelMixin,   # 擴展在響應中實現返回現有模型實例功能(獲取單條數據)
                       mixins.DestroyModelMixin,    # 擴展示有模型實例的刪除功能
                       mixins.UpdateModelMixin,     # 擴展更新和保存現有模型實例功能
                       generics.GenericAPIView):
    queryset = Author.objects.all()     # 配置queryset:告知這個類此次處理的數據
    serializer_class = AuthorModelSerializers     # 告知處理用到的序列化組件

    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 delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

  注意:node

(1)queryset和serializer_class變量

  這兩個變量是必須的。queryset告知這個類此次處理的數據。serializer_class告知這個類數據處理用到的序列化組件。python

(2)五類mixins做用和對應的http方法

  

(3)GenericAPIView

  這個類擴展了REST框架的APIView類,爲標準列表和詳細視圖添加了常見的行爲。數據庫

  提供的每一個具體的通用視圖都是經過把GenericAPIView與一個或多個mixin類進行組合來構建的。django

(4)測試驗證

   

 

2、基於mixins封裝視圖示例

一、基本視圖

# 方式三:基於rest_framework框架實現序列化(pip install djangorestframework)
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import BookSerializer   # 自定義序列化類


class BookView(APIView):
    query_set = Book.objects.all()        # 將query_set抽離
    serializer_class = BookSerializer     # 拿到序列化器

    def get(self, request):
        # 第一個圖書對象
        # book_obj = Book.objects.first()
        # ret = BookSerializer(book_obj)

        # book_list = Book.objects.all()
        book_list = self.query_set
        # ret = BookSerializer(book_list, many=True)    # 使用序列化器序列化
        ret = self.serializer_class(book_list, many=True)
        """
        序列化的數據保存在ret.data中
        """
        return Response(ret.data)
    """
    得出來的結果會使用Django REST framework模板,在serializers.py中定製好序列化類後,顯示效果以下所示:
    HTTP 200 OK
    Allow: GET, HEAD, OPTIONS
    Content-Type: application/json
    Vary: Accept

    [
        {
            "id": 1,
            "title": "python開發",
            "category": "Python",
            "pub_time": "2011-08-27",
            "publisher": {
                "id": 1,
                "title": "人民日報社"
            },
            "author": [
                {
                    "id": 1,
                    "name": "xxx"
                },
                {
                    "id": 2,
                    "name": "sssxx"
                }
            ]
        },
       ...
    ]
    """

    def post(self, request):
        print(request.data)
        serializer = BookSerializer(data=request.data)  # 序列化器校驗前端傳回來的數據
        if serializer.is_valid():
            serializer.save()   # 驗證成功後保存數據庫
            # 由於ModelSerializer的create方法不支持source的用法。所以必須還自定義一個create方法。
            return Response(serializer.validated_data)   # validated_data存放驗證經過的數據
        else:
            return Response(serializer.errors)           # errors存放錯誤信息

    '''
    發送post請求接口設計
    POST /books/list
    {
        "title": "nodejs的使用教程",
        "w_category": "1",
        "pub_time": "2018-10-27",
        "publisher_id": 1,
        "author_list": [1,2,3]
    }
    '''


class BookEditView(APIView):
    def get(self, request, id):
        """查看單條數據"""
        book_obj = Book.objects.filter(id=id).first()
        ret = BookSerializer(book_obj)
        return Response(ret.data)

    '''
    GET /books/retrieve/3
    {
        "id": 3,
        "title": "Linux開發",
        "category": "Linux",
        "pub_time": "2008-08-27",
        "publisher": {
            "id": 3,
            "title": "長江日報社"
        },
        "author": [
            {
                "id": 1,
                "name": "阿薩德"
            },
            {
                "id": 3,
                "name": "阿斯達"
            }
        ]
    }
    '''

    def put(self, request, id):
        """更新操做"""
        book_obj = Book.objects.filter(id=id).first()
        serializer = BookSerializer(
            book_obj,             # 待更新對象
            data=request.data,    # 要更新的數據
            partial=True          # 重點:進行部分驗證和更新
        )
        if serializer.is_valid():
            serializer.save()     # 保存
            return Response(serializer.validated_data)   # 返回驗證經過的數據
            # return Response(serializers.data)    # 返回全部數據
        else:
            return Response(serializer.errors)      # 返回驗證錯誤的數據

    def delete(self, request, id):
        """刪除操做"""
        book_obj = Book.objects.filter(id=id).first()
        book_obj.delete()
        return Response("")

二、基於mixins封裝視圖

  升級:改寫使用通用類,繼承通用方法。json

class GenericAPIView(APIView):
    # 通用APIView模板類
    query_set = None
    serializer_class = None

    def get_queryset(self):
        return self.query_set

    def get_serializer(self, *args, **kwargs):
        return self.serializer_class(*args, **kwargs)   # 實例化,且接收全部的參數


class BookView(GenericAPIView):
    # 以方法的形式調用獲取
    query_set = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request):
        book_list = self.get_queryset()
        ret = self.get_serializer(book_list, many=True)
        return Response(ret.data)

    def post(self, request):
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors)

三、第一次封裝

 

class GenericAPIView(APIView):
    # 通用APIView模板類
    query_set = None
    serializer_class = None

    def get_queryset(self):
        return self.query_set

    def get_serializer(self, *args, **kwargs):
        return self.serializer_class(*args, **kwargs)   # 實例化,且接收全部的參數


class ListModelMixin(object):
    def list(self, request):
        queryset = self.get_queryset()
        ret = self.get_serializer(queryset, many=True)
        return Response(ret.data)


class CreateModelMixin(object):
    def create(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors)


class RetrieveModelMixin(object):
    def retrieve(self, request, id):   # 查看單條數據
        book_obj = self.get_queryset().filter(id=id).first()
        ret = self.get_serializer(book_obj)
        return Response(ret.data)


class UpdateModelMixin(object):
    def update(self, request, id):
        book_obj = self.get_queryset().filter(id=id).first()
        serializer = self.get_serializer(
            book_obj,  # 待更新對象
            data=request.data,  # 要更新的數據
            partial=True  # 重點:進行部分驗證和更新
        )
        if serializer.is_valid():
            serializer.save()  # 保存
            return Response(serializer.validated_data)  # 返回驗證經過的數據
            # return Response(serializers.data)    # 返回全部數據
        else:
            return Response(serializer.errors)  # 返回驗證錯誤的數據


class DestroyModelMixin(object):
    def delete(self, request, id):
        book_obj = self.get_queryset().filter(id=id).first()
        book_obj.delete()
        return Response("")

class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):   # 一層封裝
    query_set = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)


class BookEditView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):  # 一層封裝
    query_set = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, id):
        """查看單條數據"""
        return self.retrieve(request, id)

    def put(self, request, id):
        """更新操做"""
        return self.update(request, id)

    def delete(self, request, id):
        """刪除操做"""
        return self.destroy(request, id)

四、第二次封裝

  進一步封裝以下所示:api

# 二層封裝
class ListCreateAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
    pass


class RetrieveUpdateDestroyAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    pass


# class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):   # 一層封裝
class BookView(ListCreateAPIView):          # 使用二層封裝
    query_set = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)


# class BookEditView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):  # 一層封裝
class BookEditView(RetrieveUpdateDestroyAPIView):     # 使用二層封裝
    query_set = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, id):
        """查看單條數據"""
        return self.retrieve(request, id)

五、第三次封裝

  通過兩次封裝後,剩下兩個視圖,但依然不夠簡潔。因爲兩個get請求的返回值不一樣,寫了兩個視圖來解決。app

  如今能夠經過路由傳參的方式來解決上述問題。框架

(1)改寫urls.py

from django.urls import path, include
from .views import BookView, BookEditView, BookModelViewSet


urlpatterns = [
    # path('list', BookView.as_view()),   # 查看全部的圖書
    # 注意url中參數命名方式,2.0以前的寫法:'retrieve/(?P<id>\d+)'
    # 2.0以後的寫法:<>內聲明類型,冒號後面跟着關鍵字參數
    # path('retrieve/<int:id>', BookEditView.as_view())   # 單條數據查看

    path('list', BookModelViewSet.as_view({"get": "list", "post": "create"})),
    path('retrieve/<int:id>', BookModelViewSet.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}))
]

 

(2)使as_view支持傳參

  as_view方法自己是不支持傳參的,所以須要重寫該方法。ide

class ViewSetMixin(object):
    def as_view(self):
        """
        按照參數指定的去匹配
        get-->list
        :return:
        """
        pass

class BookModelViewSet(ViewSetMixin, GenericAPIView, ListModelMixin, CreateModelMixin,
                       RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    pass

  可是咱們不用本身去定義該方法,能夠直接使用框架已經提供好的ViewSetMixin:

from rest_framework.viewsets import ViewSetMixin

  它作的主要事情就是重寫as_view方法:

class ViewSetMixin:
    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            # We also store the mapping of request methods to actions,
            # so that we can later set the action attribute.
            # eg. `self.action = 'list'` on an incoming GET request.
            self.action_map = actions

            # Bind methods to actions
            # This is the bit that's different to a standard view
            # 這裏actions是前面url中提供的字典參數{"get": "list", "post": "create"}
            for method, action in actions.items():
                # 循環獲得的method是get,action是list
                handler = getattr(self, action)   # self是本身的視圖類
                setattr(self, method, handler)   # 在本身的視圖類中找list方法

            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get

            self.request = request
            self.args = args
            self.kwargs = kwargs

            # And continue as usual
            # 作分發:get-->self.get;post-->self.post
            return self.dispatch(request, *args, **kwargs)

  因爲self.get已經等於self.list,分發get對應的是list。

(3)視圖改寫

from rest_framework.viewsets import ViewSetMixin

class BookModelViewSet(ViewSetMixin, GenericAPIView, ListModelMixin, CreateModelMixin,
                       RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    query_set = Book.objects.all()
    serializer_class = BookSerializer

  因爲get去匹配時已經自動對應到了self.list方法。所以再也不自定義視圖方法。

(4)簡化繼承

from rest_framework.viewsets import ViewSetMixin

class ModelViewSet(ViewSetMixin, GenericAPIView, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    pass

class BookModelViewSet(ModelViewSet):
    query_set = Book.objects.all()
    serializer_class = BookSerializer

3、Mixins源碼分析

一、CreateModelMixin

class CreateModelMixin(object):
    """Create a model instance ==>建立一個實例"""
    def create(self, request, *args, **kwargs):
        # 獲取相關serializer
        serializer = self.get_serializer(data=request.data)
        
        # 進行serializer的驗證;raise_exception=True,一旦驗證不經過,再也不往下執行,直接引起異常
        serializer.is_valid(raise_exception=True)
        
        # 調用perform_create()方法,保存實例
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
    # 保存實例
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

  注意:

(1)perform_create( )對serializer直接進行save保存,當在一些情境下,須要對perform_create( )進行重寫。

(2)這個類的運行流程以下所示:

  

二、ListModelMixin

class ListModelMixin(object):
    """List a queryset.==> 列表頁獲取"""
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        
        # 這是一個分頁功能,若是在viewset中設置了pagination_class,那麼這裏就會起做用
        # 獲取當前頁的queryset,若是不存在分頁,返回None
        page = self.paginate_queryset(queryset)
        
        if page is not None:
        # 分頁不爲空,那麼不能簡單的執行Response(serializer.data)
        # 還須要將相關的page信息序列化在進行響應
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

  ListModelMixin通常用來獲取列表頁,不須要重寫方法。

三、RetriveModelMixin

class RetrieveModelMixin(object):
    """
    Retrieve a model instance.==> 獲取某一個對象的具體信息
    """
    def retrieve(self, request, *args, **kwargs):
        # 通常訪問的url都爲/obj/id/這種新式
        # get_object()能夠獲取到這個id的對象
        # 注意在viewset中設置lookup_field獲取重寫get_object()方法能夠指定id具體對象是什麼~!
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

四、DestoryModelMixin

class DestroyModelMixin(object):
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()

五、總結

  這一章簡要分析了源碼的內容以及各個mixins的邏輯,最重要的仍是學會重寫它們相關的方法。通常狀況下,當咱們在操做某一個model的時候,涉及到另一個model中數據的修改,那麼就須要對這個mixins下執行save的邏輯的方法進行重寫。

4、使用通用的基於類的視圖

  經過使用mixin類,使用更少的代碼重寫了這些視圖,但咱們還能夠再進一步。REST框架提供了一組已經混合好(mixed-in)的通用視圖,咱們可使用它來簡化咱們的views.py模塊。

from rest_framework import mixins, generics

class AuthorView(generics.ListCreateAPIView):
    queryset = Author.objects.all()     # 配置queryset:告知這個類此次處理的數據
    serializer_class = AuthorModelSerializers     # 告知處理用到的序列化組件

class AuthorDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Author.objects.all()     # 配置queryset:告知這個類此次處理的數據
    serializer_class = AuthorModelSerializers     # 告知處理用到的序列化組件

一、ListCreateAPIView源碼

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)

  能夠看到源碼中將Mixins混合類和get\post函數都封裝進去了。

二、RetrieveUpdateDestroyAPIView源碼

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)

  其餘RetrieveDestroyAPIView、RetrieveUpdateAPIView、UpdateAPIView、DestroyAPIView等封裝方式徹底相似。

5、運用viewsets.ModelViewSet的視圖

一、改寫urls.py

from django.contrib import admin
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    '''代碼省略'''
    # re_path(r'^authors/$', views.AuthorView.as_view(), name="author"),
    # re_path(r'^authors/(?P<pk>\d+)/$', views.AuthorDetailView.as_view(), name="detail_author"),

    # as_view參數指定什麼請求走什麼方法
    re_path(r'^authors/$', views.AuthorViewSet.as_view({"get": "list", "post": "create"}), name="author_list"),
    re_path(r'^authors/(?P<pk>\d+)/$', views.AuthorViewSet.as_view({
        'get': 'retrieve',
        'put': 'update',
        'patch': 'partial_update',
        'delete': 'destroy'
    }), name="author_detail"),
]

  注意:

(1)as_view參數

  利用參數來指定什麼請求方式由哪個內部方法來執行。尤爲注意兩種不一樣get請求用不一樣的方法來處理。

二、引入viewsets改寫視圖

from rest_framework import viewsets

class AuthorViewSet(viewsets.ModelViewSet):
    queryset = Author.objects.all()     # 配置queryset:告知這個類此次處理的數據
    serializer_class = AuthorModelSerializers     # 告知處理用到的序列化組件

5、ModelViewSet源碼分析

一、查看viewsets.ModelViewSet源碼

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

  ModelViewSet繼承了全部五個混合類。還繼承了GenericViewSet類。

二、查看GenericViewSet源碼

class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    pass

  能夠看到GenericViewSet繼承了前面學的generics.GenericAPIView類,這個類擴展了REST框架的APIView類,可是它並無改寫dispatch方法,所以url中能夠添加參數與它無關。

  GenericViewSet還繼承了ViewSetMixin類。

三、查看分析ViewSetMixin源碼

class ViewSetMixin(object):
    """
    Overrides `.as_view()` so that it takes an `actions` keyword that performs
    the binding of HTTP methods to actions on the Resource.
    覆蓋as_view方法須要接收一個actions參數來實現將HTTP方法綁定到資源。
    For example, to create a concrete view binding the 'GET' and 'POST' methods
    to the 'list' and 'create' actions...
    舉例來講,若是要建立一個具體視圖綁定'GET'和'POST'方法到'list'和'create'操做。可寫爲以下格式:
    view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
    """
    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
        """
        Because of the way class based views create a closure around the
        instantiated view, we need to totally reimplement `.as_view`,
        and slightly modify the view function that is created and returned.
        """
        # actions must not be empty   actions來接收{'get': 'list', 'post': 'create'}
        if not actions:   # 爲空則報錯
            raise TypeError("The `actions` argument must be provided when "
                            "calling `.as_view()` on a ViewSet. For example "
                            "`.as_view({'get': 'list'})`")

        # sanitize keyword arguments
        for key in initkwargs:.....   # 這裏的值是空的

        def view(request, *args, **kwargs):...

        return csrf_exempt(view)    # 執行as_view 最終返回的仍是view函數

  注意:

(1)分析函數傳參方式:**字典傳參等同於關鍵字傳參。

def foo(action=None, **kwargs):
    print(action)
    print(kwargs)

foo({"a": 1}, b=2, c=3)
"""
{'a': 1}
{'b': 2, 'c': 3}
"""
foo(a=2, b=3)
"""
None
{'a': 2, 'b': 3}
"""
foo(**{"a": 1, "b": 3})
"""
None
{'a': 1, 'b': 3}
"""
foo({"a": 1, "b": 3})
"""
{'a': 1, 'b': 3}
{}
"""

  因而可知as_view傳遞的參數{"get": "list", "post": "create"}是ViewSetMixin內改寫的as_view方法,由actions參數來接收的。

  因爲acitons默認值是None,所以not None實際上是True,if not actions: 實際上是actions爲空則報錯的意思。

(2)as_view函數最終返回值是view函數

def view(request, *args, **kwargs):
    self = cls(**initkwargs)
    # We also store the mapping of request methods to actions,
    # so that we can later set the action attribute.
    # eg. `self.action = 'list'` on an incoming GET request.
    self.action_map = actions  # 傳入的字典數據{'get': 'list', 'post': 'create'}

    # Bind methods to actions
    # This is the bit that's different to a standard view
    for method, action in actions.items():  # method:請求方式   action:實例方法
        handler = getattr(self, action)  # 反射獲得self.list  self.create方法
        setattr(self, method, handler)  # 給請求方式設置對應的實例方法

    if hasattr(self, 'get') and not hasattr(self, 'head'):
        self.head = self.get

    self.request = request
    self.args = args
    self.kwargs = kwargs

    # And continue as usual
    return self.dispatch(request, *args, **kwargs)

  在view函數中用self.action_map = actions 來接收傳入的字典數據{'get': 'list', 'post': 'create'}。

  循環for method, action in actions.items():   拿到method:請求方式和action:實例方法。

  再經過反射方法:handler = getattr(self, action) 獲得self.list self.create方法。

  最後經過反射方法setattr(self, method, handler)  給請求方式設置對應的實例方法:之後再找getattr(self, "get")的時候找到是self.list;找getattr(self, "post")的時候找到是self.create;

(3)反射方法詳解

1. getattr()函數是Python自省的核心函數,具體使用大致以下:
class A: 
def __init__(self): 
self.name = 'zhangjing'
#self.age='24'
def method(self): 
print"method print"
  
Instance = A() 
print getattr(Instance , 'name, 'not find') #若是Instance 對象中有屬性name則打印self.name的值,不然打印'not find'
print getattr(Instance , 'age', 'not find') #若是Instance 對象中有屬性age則打印self.age的值,不然打印'not find'
print getattr(a, 'method', 'default') #若是有方法method,不然打印其地址,不然打印default 
print getattr(a, 'method', 'default')() #若是有方法method,運行函數並打印None不然打印default 

2. hasattr(object, name)

說明:判斷對象object是否包含名爲name的特性(hasattr是經過調用getattr(ojbect, name)是否拋出異常來實現的)

3. setattr(object, name, value)

這是相對應的getattr()。參數是一個對象,一個字符串和一個任意值。字符串可能會列出一個現有的屬性或一個新的屬性。這個函數將值賦給屬性的。該對象容許它提供。例如,setattr(x,「foobar」,123)至關於x.foobar = 123。

4. delattr(object, name)

與setattr()相關的一組函數。參數是由一個對象(記住python中一切皆是對象)和一個字符串組成的。string參數必須是對象屬性名之一。該函數刪除該obj的一個由string指定的屬性。delattr(x, 'foobar')=del x.foobar

四、找到view函數執行完後最終返回dispatch方法 並完成分析

  從ModelViewSet——》GenericViewSet——》GenericAPIView——》APIView一路回溯到APIView才找到dispatch方法。

def dispatch(self, request, *args, **kwargs):
    self.args = args
    self.kwargs = kwargs
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        self.initial(request, *args, **kwargs)
        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                              self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        response = handler(request, *args, **kwargs)
    except Exception as exc:
        response = self.handle_exception(exc)
    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

  當訪問是走的路由是re_path(r'^authors/$', views.AuthorViewSet.as_view({"get": "list", "post": "create"}), name="author_list"),get請求handler對應的是self.list。

  而當訪問走的路由是re_path(r'^authors/(?P<pk>\d+)/$', views.AuthorViewSet.as_view({'get': 'retrieve',....

 此時get請求handler對應的是self.retrieve。

  如此就實現了兩個類合成一個類。

五、找到並執行list方法

  查看ModelViewSet源碼:

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):

  在ListModelMixin找到list方法:

class ListModelMixin(object):
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

6、視圖組件總結

一、DRF全部視圖文件

  全部的視圖類在以下這四個文件內:

from rest_framework import views       # APIView
from rest_framework import generics    # 公共通用視圖類:GenericAPIView,及各類組合視圖類CreateAPIView、ListAPIView、RetrieveAPIView等
from rest_framework import mixins      # 混合繼承類:CreateModelMixin、ListModelMixin、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin
from rest_framework import viewsets    # 重寫as_view: ViewSetMixin;其餘類都是幫助去繼承ViewSetMixin

二、DRF視圖圖譜

  視圖類繼承順序,如Django REST Framework視圖圖譜所示:

  

 

  首先 django是繼承 view的,DRF是從APIView開始繼承起,APIView封裝了request,其中包含了data、query_params等屬性、方法。

  而後 GenericAPIView封裝了 get_queryset() 和 get_serializer();ViewSetMixin重寫了 as_view()方法。

  隨後 GenericViewSet幫忙繼承GenericAPIView和ViewSetMixin。

  最後最高層的封裝是 ModelViewSet。

相關文章
相關標籤/搜索