Skip to content

Latest commit

 

History

History
129 lines (94 loc) · 7.42 KB

50.RESTful架构和DRF进阶.md

File metadata and controls

129 lines (94 loc) · 7.42 KB

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屬性來指定如何篩選資料,有興趣的讀者可以自行研究。