1. 程式人生 > >DRF檢視和路由

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 = BookSerializer
view.py

  這樣我們的檢視只要寫兩行就可以了

  其實我們所寫的所有檢視, 框架都幫我們封裝好了.

  注意 :

    應框架封裝的檢視, url上的那個關鍵字引數要用pk, 系統預設的

 


 

繼承順序圖 :

 


 

DRF路由 :

# 最終版
# 幫助我們生成帶引數的路由
from rest_framework.routers import DefaultRouter
# 例項化DefaultRouter物件
router = DefaultRouter()
# 註冊我們的路由以及檢視
router.register(r"book", views.BookModelView)


urlpatterns = [

]

urlpatterns += router.urls

可以看到, 通過框架可以把路由檢視都變得非常簡單, 但是需要自定製的時候還是需要自己用APIView寫, 當不需要那麼多路由時候, 也不需藥使用這種路由註冊.