DRF檢視和路由
APIView :
在django中寫CBV的時候是繼承View, rest_framework繼承的是APIView, 這兩種具體有什麼不同呢?
urlpatterns = [ url(r'^book$', BookView.as_view()), url(r'^book/(?P<id>\d+)$', BookEditView.as_view()), ]
無論是View還是APIView最開始呼叫的都是as_view()方法, 看原始碼:
可以看到, APIView繼承了View, 並且執行了View中的as_view()方法, 最後把view返回, 用csrf_exempt()方法包裹後去掉了csrf的認證.
而在View中的as_view()方法如下:
在View中的as_view方法返回了view函式, 而view函式執行了self.dispatch()方法, 但是這裡的dispatch方法應該是APIView中的.
再去initialize_request中看下把什麼賦值給了request, 並且賦值給了self.request, 也就是在檢視中用的request.xxx到底是什麼?
可以看到, 這個方法返回的是Request這個類的例項物件, 而這個Request類中的第一個引數request, 使我們在django中使用的request.
可以看到, 這個Request類把原來的request賦值給了self._request, 也就是說_request就是我們原先的request, 新的request使我們這個Request類.
那繼承APIView之後請求來的資料都在哪呢?
當我們使用了rest_framework框架之後, 我們的request是重新封裝的Request類.
request.query_params 存放的是我們get請求的引數.
request.data 存放的是我們所有的資料, 包括post請求的以及put, patch請求.
相比原來的django的request, 我們現在的request更加精簡, 清晰.
封裝程式碼 :
原始碼(封裝之前) :
from django.conf.urls import url from SerDemo import views urlpatterns = [ # 第一二版本 # url(r'^book/$', views.BookView.as_view()), # url(r'^book/(?P<edit_id>\d+)', views.BookEditView.as_view()), ]url
from rest_framework import serializers from app01 import models class PublisherSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) # 一個判斷某一個欄位是否含有敏感資訊的函式, 直接呼叫即可 def my_validate(value): if "敏感資訊" in value.lower(): raise serializers.ValidationError("存在敏感詞彙!!!") class BookSerializer(serializers.Serializer): id = serializers.IntegerField(required=False) # required 為False時, 反序列化不做校驗 title = serializers.CharField(max_length=32, validators=[my_validate]) pub_time = serializers.DateField() category = serializers.CharField(source="get_category_display", read_only=True) # 自定義一個欄位只用來反序列化接收使用 post_category = serializers.IntegerField(write_only=True) publisher = PublisherSerializer(read_only=True) publisher_id = serializers.IntegerField(write_only=True) # 多對多有many引數 author = AuthorSerializer(many=True, read_only=True) author_list = serializers.ListField(write_only=True) # 新增資料要重寫的create方法 def create(self, validated_data): # validated_data是驗證通過的資料 # 通過ORM操作給Book表增加資料 # 新增除多對多欄位的所有欄位 book_obj = models.Book.objects.create( title=validated_data["title"], pub_time=validated_data["pub_time"], category=validated_data["post_category"], publisher_id=validated_data["publisher_id"], ) # 新增多對多欄位 book_obj.author.add(*validated_data["author_list"]) return book_obj # 更新資料要重寫update方法 def update(self, instance, validated_data): # instance 是要更新的物件 # 對除多對多欄位以外的欄位進行更新, 並設定當前已存在的資料為預設值 instance.title = validated_data.get("title", instance.title) instance.pub_time = validated_data.get("pub_time", instance.pub_time) instance.category = validated_data.get("post_category", instance.category) instance.publisher_id = validated_data.get("publisher_id", instance.publisher_id) # 判斷前端傳過來的資料是否含有author_list欄位, 如果有則更新, 沒有就不變動 if validated_data.get("author_list"): instance.author.set(validated_data["author_list"]) instance.save() return instance # 對前端傳過來的資料進行條件控制 def validate(self, attrs): # 相當於鉤子函式 # attrs是一個字典, 含有傳過來的所有欄位 if "python" in attrs["title"].lower() and attrs["post_category"] == 1: return attrs else: raise serializers.ValidationError("分類或標題不匹配")序列化器
from rest_framework.views import APIView from app01 import models from .serializers import BookSerializer from rest_framework.response import Response from rest_framework.viewsets import ViewSetMixin # Create your views here. # 版本一 class BookView(APIView): def get(self, request): book_queryset = models.Book.objects.all() # 用序列化器進行序列化 ser_obj = BookSerializer(book_queryset, many=True) return Response(ser_obj.data) def post(self, request): # 接收前端傳過來的資料 book_obj = request.data # 對前端傳過來的資料使用自定義序列化方法進行校驗(是否合法等) ser_obj = BookSerializer(data=book_obj) # 如果校驗通過做些什麼 if ser_obj.is_valid(): ser_obj.save() # validated_data是校驗通過之後的資料 return Response(ser_obj.validated_data) # 驗證不通過返回錯誤資訊 return Response(ser_obj.errors) class BookEditView(APIView): def get(self, request, edit_id): book_obj = models.Book.objects.filter(id=edit_id).first() ser_obj = BookSerializer(book_obj) return Response(ser_obj.data) def put(self, request, edit_id): book_obj = models.Book.objects.filter(id=edit_id).first() ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True) if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.validated_data) return Response(ser_obj.errors) def delete(self, request, edit_id): book_obj = models.Book.objects.filter(id=edit_id).first() if not book_obj: return Response("刪除物件不存在!") book_obj.delete() return Response("刪除成功了呢!")view.py
第一次封裝 :
from django.conf.urls import url from SerDemo import views urlpatterns = [ # 第一二版本 # url(r'^book/$', views.BookView.as_view()), # url(r'^book/(?P<edit_id>\d+)', views.BookEditView.as_view()), ]url
class GenericAPIView(APIView): """ 定義一個公共的類, 用來獲取需要的資源 """ # 設定預設操作的資料為空 queryset = None # 設定需要使用的序列化器為空 serializer_class = None def get_queryset(self): """ 定義一個獲取需要操作的資料的函式 子類函式直接呼叫即可 子類中呼叫此方法, 返回值中的self指的是呼叫此方法的子類的實力化物件 queryset屬性在本類中是None, 每一個繼承此類的子類中都會重寫queryset和serializer_class方法 然後子類中執行此類中的方法是去執行子類對應的屬性. :return: """ return self.queryset.all() def get_serializer_class(self, *args, **kwargs): return self.serializer_class(*args, **kwargs) class ListModelMixin: """ 定義一個展示類,將展示方法統一寫成一個方法 """ def list(self, request): queryset = self.get_queryset() ser_obj = self.get_serializer_class(queryset, many=True) return Response(ser_obj.data) class CreateModeMixin: """ 新增的檢視類 """ def create(self, request): ser_obj = self.get_serializer_class(data=request.data) if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.validated_data) return Response(ser_obj.errors) class EditModeMixin: """ 編輯的檢視類 """ def retrieve(self, request, id): obj = self.get_queryset().filter(id=id).first() ser_obj = self.get_serializer_class(obj) return Response(ser_obj.data) class UpdateModeMixin: """ 更新的檢視類 """ def update(self, request, id): obj = self.get_queryset().filter(id=id).first() ser_obj = self.get_serializer_class(instance=obj, data=request.data, partial=True) if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.validated_data) return Response(ser_obj.errors) class DeleteModeMixin: """ 刪除的檢視類 """ def destroy(self, request, id): obj = self.get_queryset().filter(id=id).first() if not obj: return Response("刪除的物件不存在!") obj.delete() return Response("刪除了呢") class ListCreateAPIview(GenericAPIView, ListModelMixin, CreateModeMixin): pass class OperationAPIview(GenericAPIView, EditModeMixin, UpdateModeMixin, DeleteModeMixin): pass class BookView(ListCreateAPIview): # 定義queryset屬性時, DRF方法內部和關鍵字重名, 內部會識別並將此屬性做快取 # 換個名字DRF不識別屬性, 不做快取, 也就不需要.all() queryset = models.Book.objects.all() serializer_class = BookSerializer def get(self, request): return self.list(request) def post(self, request): return self.create(request) class BookEditView(OperationAPIview): queryset = models.Book.objects.all() serializer_class = BookSerializer def get(self, request, edit_id): return self.retrieve(request, edit_id) def put(self, request, edit_id): return self.update(request, edit_id) def delete(self, request, edit_id): return self.destroy(request, edit_id)view.py
我們封裝的GenericAPIView, , 包括封裝的每個方法的類, 其實框架都幫我們封裝好了,
我們可以繼承這個二類, 來實現上面的檢視.
其中框架還給我們提供了一個路由傳參的方法:
actioon這個預設引數其實就是接收路由引數的引數.
再次封裝 :
from SerDemo import views urlpatterns = [ # 第三版 # url(r'^book/$', views.BookModelView.as_view({"get": "list", "post": "create"})), # url(r'^book/(?P<pk>\d+)', views.BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})), ]url
from rest_framework.viewsets import ViewSetMixin # 重寫了原始碼中的as_view()方法, 是as_view方法可以傳引數 # 在執行dispatch()方法之前 class ModelViewSet(ViewSetMixin, ListCreateAPIview, OperationAPIview): pass class BookModelView(viewsets.ModelViewSet): queryset = models.Book.objects.all() serializer_class = BookSerializerview.py
這樣我們的檢視只要寫兩行就可以了
其實我們所寫的所有檢視, 框架都幫我們封裝好了.
注意 :
應框架封裝的檢視, url上的那個關鍵字引數要用pk, 系統預設的
繼承順序圖 :
DRF路由 :
# 最終版 # 幫助我們生成帶引數的路由 from rest_framework.routers import DefaultRouter # 例項化DefaultRouter物件 router = DefaultRouter() # 註冊我們的路由以及檢視 router.register(r"book", views.BookModelView) urlpatterns = [ ] urlpatterns += router.urls
可以看到, 通過框架可以把路由檢視都變得非常簡單, 但是需要自定製的時候還是需要自己用APIView寫, 當不需要那麼多路由時候, 也不需藥使用這種路由註冊.