A place for study and research

Python 100 Days Day50 RESTful and DRF Advance

|

author: jackfrued

RESTful架構和DRF進階

除了上一節講到的方法,使用DRF創建REST風格的數據接口也可以通過CBV(基於類的視圖)的方式。使用CBV創建數據接口的特點是代碼簡單,開發效率高,但是沒有FBV(基於函數的視圖)靈活,因為使用FBV的方式,數據接口對應的視圖函數執行什麽樣的代碼以及返回什麽的數據是高度可定制的。下面我們以定制學科的數據接口為例,講解通過CBV方式定制數據接口的具體做法。

使用CBV

繼承APIView的子類

修改之前項目中的polls/views.py,去掉show_subjects視圖函數,添加一個名為SubjectView的類,該類繼承自ListAPIViewListAPIView能接收GET請求,它封裝了獲取數據列表並返回JSON數據的get方法。ListAPIViewAPIView 的子類,APIView還有很多的子類,例如CreateAPIView可以支持POST請求,UpdateAPIView可以支持PUT和PATCH請求,DestoryAPIView可以支持DELETE請求。SubjectView 的代碼如下所示。

from rest_framework.generics import ListAPIView


class SubjectView(ListAPIView):
    # 通過queryset指定如何獲取學科數據
    queryset = Subject.objects.all()
    # 通過serializer_class指定如何序列化學科數據
    serializer_class = SubjectSerializer

剛才說過,由於SubjectView的父類ListAPIView已經實現了get方法來處理獲取學科列表的GET請求,所以我們只需要聲明如何獲取學科數據以及如何序列化學科數據,前者用queryset屬性指定,後者用serializer_class屬性指定。要使用上面的SubjectView,需要修改urls.py文件,如下所示。

urlpatterns = [
    path('api/subjects/', SubjectView.as_view()),   
]

很顯然,上面的做法較之之前講到的FBV要簡單很多。

繼承ModelViewSet

如果學科對應的數據接口需要支持GET、POST、PUT、PATCH、DELETE請求來支持對學科資源的獲取、新增、更新、刪除操作,更為簡單的做法是繼承ModelViewSet來編寫學科視圖類。再次修改polls/views.py文件,去掉SubjectView類,添加一個名為SubjectViewSet的類,代碼如下所示。

from rest_framework.viewsets import ModelViewSet


class SubjectViewSet(ModelViewSet):
    queryset = Subject.objects.all()
    serializer_class = SubjectSerializer

通過查看ModelViewSet類的源代碼可以發現,該類共有6個父類,其中前5個父類分別實現對POST(新增學科)、GET(獲取指定學科)、PUT/PATCH(更新學科)、DELETE(刪除學科)和GET(獲取學科列表)操作的支持,對應的方法分別是createretrieveupdatedestroylist。由於ModelViewSet的父類中已經實現了這些方法,所以我們幾乎沒有編寫任何代碼就完成了學科數據全套接口的開發,我們要做的僅僅是指出如何獲取到數據(通過queryset屬性指定)以及如何序列化數據(通過serializer_class屬性指定),這一點跟上面繼承APIView的子類做法是一致的。

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

要使用上面的SubjectViewSet,需要在urls.py文件中進行URL映射。由於ModelViewSet相當於是多個視圖函數的匯總,所以不同於之前映射URL的方式,我們需要先創建一個路由器並通過它注冊SubjectViewSet,然後將注冊成功後生成的URL一並添加到urlspattern列表中,代碼如下所示。

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('api/subjects', SubjectViewSet)
urlpatterns += router.urls

除了ModelViewSet類外,DRF還提供了一個名為ReadOnlyModelViewSet 的類,從名字上就可以看出,該類是只讀視圖的集合,也就意味著,繼承該類定制的數據接口只能支持GET請求,也就是獲取單個資源和資源列表的請求。

數據分頁

在使用GET請求獲取資源列表時,我們通常不會一次性的加載所有的數據,除非數據量真的很小。大多數獲取資源列表的操作都支持數據分頁展示,也就說我們可以通過指定頁碼(或類似於頁碼的標識)和頁面大小(一次加載多少條數據)來獲取不同的數據。我們可以通過對QuerySet對象的切片操作來實現分頁,也可以利用Django框架封裝的PaginatorPage對象來實現分頁。使用DRF時,可以在Django配置文件中修改REST_FRAMEWORK並配置默認的分頁類和頁面大小來實現分頁,如下所示。

REST_FRAMEWORK = {
    'PAGE_SIZE': 10,
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination'
}

除了上面配置的PageNumberPagination分頁器之外,DRF還提供了LimitOffsetPaginationCursorPagination分頁器,值得一提的是CursorPagination,它可以避免使用頁碼分頁時暴露網站的數據體量,有興趣的讀者可以自行了解。如果不希望使用配置文件中默認的分頁設定,可以在視圖類中添加一個pagination_class屬性來重新指定分頁器,通常可以將該屬性指定為自定義的分頁器,如下所示。

from rest_framework.pagination import PageNumberPagination


class CustomizedPagination(PageNumberPagination):
    # 默認頁面大小
    page_size = 5
    # 頁面大小對應的查詢參數
    page_size_query_param = 'size'
    # 頁面大小的最大值
    max_page_size = 50
class SubjectView(ListAPIView):
    # 指定如何獲取數據
    queryset = Subject.objects.all()
    # 指定如何序列化數據
    serializer_class = SubjectSerializer
    # 指定如何分頁
    pagination_class = CustomizedPagination

如果不希望數據分頁,可以將pagination_class屬性設置為None來取消默認的分頁器。

數據篩選

如果希望使用CBV定制獲取老師信息的數據接口,也可以通過繼承ListAPIView來實現。但是因為要通過指定的學科來獲取對應的老師信息,因此需要對老師數據進行篩選而不是直接獲取所有老師的數據。如果想從請求中獲取學科編號並通過學科編號對老師進行篩選,可以通過重寫get_queryset方法來做到,代碼如下所示。

class TeacherView(ListAPIView):
    serializer_class = TeacherSerializer

    def get_queryset(self):
        queryset = Teacher.objects.defer('subject')
        try:
            sno = self.request.GET.get('sno', '')
            queryset = queryset.filter(subject__no=sno)
            return queryset
        except ValueError:
            raise Http404('No teachers found.')

除了上述方式之外,還可以使用三方庫django-filter來配合DRF實現對數據的篩選,使用django-filter後,可以通過為視圖類配置filter-backends屬性並指定使用DjangoFilterBackend來支持數據篩選。在完成上述配置後,可以使用filter_fields 屬性或filterset_class屬性來指定如何篩選數據,有興趣的讀者可以自行研究。

Comments