官方文檔:http://www.django-rest-framework.org/ drf爲我們提供強大的通用view的功能,本博客對這些view進行簡要的總結分析。 首先,我們看一下主要的幾種view以及他們之間的關係。
這其中,還涉及了mixins,主要也分爲5類:
下面我們以課程(course)作爲一個例子,對view進行一個總結。
1. django View
首先,我們使用django自帶的view,獲取一個課程的列表:
# drf是通過json的格式進行數據交互的,所以這裏也返回json數據 import json from django.views.generic.base import View from django.core import serializers from django.http import HttpResponse,JsonResponse from .models import Course class CourseListView(View): def get(self, request): """ 通過django的view實現課程列表頁 """ courses = Course.objects.all()[:10] json_data = serializers.serialize('json', courses) json_data = json.loads(json_data) return JsonResponse(json_data, safe=False)
2. APIView
接下來,我們用APIView來實現
from rest_framework.views import APIView from rest_framework.response import Response # 這個serializers是在其他文件自定義的,這裏對這個不進行過多介紹 from .serializers import CourseSerializer class CourseListView(APIView): def get(self, request, format=None): """ 通過APIView實現課程列表頁 """ courses = Course.objects.all() serializer = CourseSerializer(courses, many=True) return Response(serializer.data)
在APIView這個例子中,調用了drf本身的serializer以及Response方法。 APIView對django本身的View進行封裝,從上述的代碼,這樣分析,兩者的差別看起來不是很大,但實際中APIView做了很多東西,它定義了很多屬性與方法,舉幾個例子
# 這三個是常用的屬性 authentication_classes : 用戶登錄認證方式,session或者token等等 permission_classes : 權限設置,是否需要登錄等 throttle_classes : 限速設置,對用戶進行一定的訪問次數限制等等。
到這裏,可能還不能體現drf通過view的強大之處,那麼接下來的GenericAPIView就展示了它強大的功能。
3. GenericAPIView
from rest_framework import mixins from rest_framework import generics class CourseListView(mixins.ListModelMixin, generics.GenericAPIView): """ 課程列表頁 """ queryset = Course.objects.all() serialize_class = CourseSerializer def get(self, request, *args, **kwargs): # list方法是存在於mixins中的,同理,create等等也是 # GenericAPIView沒有這些方法! return self.list(request, *args, **kwargs)
在這個例子中,繼承了mixins中的ListModelMixin,在get( )方法中,調用了它的list( )方法,list方法會返回queryset的json數據。這裏對mixins不進行過多的介紹。 GenericAPIView對APIView再次封裝,實現了強大功能:
- 加入queryset屬性,可以直接設置這個屬性,不必再將實例化的courses,再次傳給seriliazer,系統會自動檢測到。除此之外,可以重載get_queryset(),這樣就不必設置’queryset=*’,這樣就變得更加靈活,可以進行完全的自定義。
- 加入serializer_class屬性與實現get_serializer_class()方法。兩者的存在一個即可,通過這個,在返回時,不必去指定某個serilizer
- 設置過濾器模板:filter_backends
- 設置分頁模板:pagination_class
- 加入 lookup_field=”pk”,以及實現了get_object方法: 這個用得場景不多,但十分重要。它們兩者的關係同1,要麼設置屬性,要麼重載方法。它們的功能在於獲取某一個實例時,指定傳進來的後綴是什麼。
舉個例子,獲取具體的某個課程,假設傳進來的ulr爲:http://127.0.0.1:8000/course/1/,系統會默認這個1指的是course的id。那麼,現在面臨一個問題,假設我定義了一個用戶收藏的model,我想要知道我id爲1的課程是否收藏了,我傳進來的url爲:http://127.0.0.1:8000/userfav/1/,系統會默認獲取userfav的id=1的實例,這個邏輯明顯是錯的,我們需要獲取course的id=1的收藏記錄,所以我們就需要用到這個屬性或者重載這個方法 lookup_field=”course_id”.
在generics除了GenericAPIView還包括了其他幾個View: CreateAPIView、ListAPIView、RetrieveAPIView、ListCreateAPIView···等等,其實他們都只是繼承了相應一個或多個mixins和GenericAPIView,這樣,有什麼好處?我們看一下同樣一個例子的代碼:
class CourseListView(ListAPIView): """ 課程列表頁 """ queryset = Course.objects.all() serialize_class = CourseSerializer
這樣,就完成了和剛剛一模一樣的功能!
4.GenericViewSet
- GenericAPIView的不足之處 既然GenericAPIView以及它相關的View已經完成了許許多多的功能,那麼還要ViewSet幹嘛!
首先,我們思考一個問題,同樣上面的例子,我們在功能上,要獲取課程的列表,也要獲取某個課程的具體信息。那麼怎麼實現,按照GenericAPIView,我們可以這樣實現:
class CourseView(ListAPIView,RetrieveAPIView): # 只需要在上面的基礎上,再繼承RetrieveAPIView就ok了。 queryset = Course.objects.all() serialize_class = CourseSerializer
但這樣實現有一個問題,關於serialize_class,顯然,當獲取課程列表時,只需要傳回去所有課程的簡要信息,如課程名字,老師,封面等等,但當獲取課程的具體信息,我們還要將他們的章節以及相關下載資料(很明顯,章節是另外一個model,有一個外鍵指向course),這些信息會很多,在獲取課程列表,將這些傳回去顯然是不理智的。那麼,還需要再定義一個CourseDetailSerializer,在get /courses/的時候,使用CourseSerializer,在get /courses/id/的時候,使用CourseDetailSerializer。 那麼,問題來了,我們怎麼獲取到是哪個action方法?這個時候,viewset就出場了!
- viewset的功能 GenericViewSet繼承了GenericAPIView,依然有get_queryset,get_serialize_class相關屬性與方法,GenericViewSet重寫了as_view方法,可以獲取到HTTP的請求方法。 解決剛剛的問題
from rest_framework import viewsets import... class CourseViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): queryset = Course.objects.all() def get_serializer_class(self): # 重寫get_serializer_class方法 if self.action == 'list': return CourseSerializer return CourseDetailSerializer
- http請求方法與mixins的方法進行綁定 但GenericViewSet本身依然不存在list, create方法,需要我們與mixins一起混合使用,那麼新問題來了?我們依然需要自己寫get、post方法,然後再return list或者create等方法嗎?當然不!重寫as_view的方法爲我們提供了綁定的功能,我們在設置url的時候:
# 進行綁定 courses = CourseViewSet.as_view({ 'get': 'list', 'post': 'create' }) urlpatterns = [ ... # 常規加入url匹配項 url(r'courses/', CourseViewSet.as_view(), name='courses'),]
這樣,我們就將http請求方法與mixins方法進行了關聯。那麼還有更簡潔的方法嗎?很明顯,當然有,這個時候,route就登場了!
- route方法註冊與綁定 from rest_framework.routers import DefaultRouter router = DefaultRouter() # 只需要實現一次 router.register(r'courses', CourseViewSet, base_name='courses') urlpatterns = [ ... # 只需要 url(r'^', include(router.urls)),] route中使用的一定要是ViewSet,用router.register的方法註冊url不僅可以很好的管理url,不會導致url過多而混亂,而且還能實現http方法與mixins中的相關方法進行連接。 在viewset中,還提供了兩個以及與mixins綁定好的ViewSet,當然,這兩個ViewSet完全可以自己實現:
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet): # 滿足只有GET方法請求的情景 pass class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet): # 滿足所有請求都有的情景 pass
到這裏,ViewSet的強大功能就介紹完了,強烈建議在做drf的時候,使用ViewSet與mixins方法結合進行開發,爲我這種小白開發者提供了很強大完整的功能!