在深入探討Class Based View(CBV)之前,我先補上昨天在序列化器沒有提及的觀念
我們透過最像Functional Based View(FBV)的APIView來帶入CBV的同時,補充一些觀念
今日重點:
我們將昨天WorkspaceList轉換成APIView的作法
from rest_framework.views import APIView
class WorkspaceList(APIView):
    def get(self, request):
        workspaces = Workspace.objects.all()
        serializer = WorkspaceSerializer(workspaces, many=True)
        return Response(serializer.data)
    def post(self, request):
        serializer = WorkspaceSerializer(data=request.data, many=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
APIView拿到請求後,根據請求方法來判斷要給哪個函式進行處理
Workspace的queryset對象,我們將其丟入序列化器中,進行序列化,最終透過Response返回JSON資料
request.data傳遞給序列化器,讓序列化器驗證數據,驗證成功後調用save方法儲存,最後Response返回JSON資料與狀態碼
而在url的部分,記得如果是CBV就需要as_view來調用
urlpatterns = [
    # path("workspaces/", workspace_list, name="workspace-list"),
    path("workspaces/", WorkspaceList.as_view(), name="workspace-list"),
    ...
]
可以看到其實架構跟FBV真的沒有太大的區別,所以大部分時候都不會是我們的選擇
其實我們昨天說DRF中序列化器的序列化,是將python資料轉換成JSON,而反序列化則是相反。這件事情沒有說得很精確
看看以下的資料
serializer = WorkspaceSerializer(workspaces, many=True)
print(type(serializer.data)) # <class 'rest_framework.serializers.ListSerializer'>
print(type(request.data))  # <class 'list'>
首先request.data的type去印出來是<class 'list'>,而不是<class 'str'>,因為DRF的JSONParser已經將JSON字符串轉換成python的列表
再來我們從WorkspaceSerializer拿到的 <class 'rest_framework.serializers.ListSerializer'>也不是什麼JSON字符串
create 或 update 方法中)create 或 update 方法處理的JSONRenderer)完成的Response 的角色:
Response 對象接收序列化器的 .data,然後使用適當的渲染器(如 JSONRenderer)將其轉換為 JSON現在我們知道序列化器的確具備將資料格式轉換的功能,只是不是完整的從JSON直接轉成python對象
我們再看一次程式碼
    def get(self, request):
        workspaces = Workspace.objects.all()
        serializer = WorkspaceSerializer(workspaces, many=True)
        return Response(serializer.data)
    def post(self, request):
        serializer = WorkspaceSerializer(data=request.data, many=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
可能會有新的問題:都是直接調用WorkspaceSerializer,怎麼知道是序列化還是反序列化?
那這時候可能又又有新的問題,我們在序列化器不論是serializers.Serializer還是serializers.ModelSerializer都沒有調用save方法,那他是怎麼運作的?
serializers.Serializer與serializers.ModelSerializer最初始都繼承了BaseSerializer
其中這是他的save方法:
    def save(self, **kwargs):
        assert hasattr(self, '_errors'), (
            'You must call `.is_valid()` before calling `.save()`.'
        )
        assert not self.errors, (
            'You cannot call `.save()` on a serializer with invalid data.'
        )
        # Guard against incorrect use of `serializer.save(commit=False)`
        assert 'commit' not in kwargs, (
            "'commit' is not a valid keyword argument to the 'save()' method. "
            "If you need to access data before committing to the database then "
            "inspect 'serializer.validated_data' instead. "
            "You can also pass additional keyword arguments to 'save()' if you "
            "need to set extra attributes on the saved model instance. "
            "For example: 'serializer.save(owner=request.user)'.'"
        )
        assert not hasattr(self, '_data'), (
            "You cannot call `.save()` after accessing `serializer.data`."
            "If you need to access data before committing to the database then "
            "inspect 'serializer.validated_data' instead. "
        )
        validated_data = {**self.validated_data, **kwargs}
        if self.instance is not None:
            self.instance = self.update(self.instance, validated_data)
            assert self.instance is not None, (
                '`update()` did not return an object instance.'
            )
        else:
            self.instance = self.create(validated_data)
            assert self.instance is not None, (
                '`create()` did not return an object instance.'
            )
        return self.instance
可以看到最後的部分,也就是依照self.instance的有無,來調用update或是create方法,這也是我們昨天在serializers.Serializer 所定義的
最後在提及一下serializers.ModelSerializer ,沒有特殊需求的話可以不用再覆寫create與save方法,所以這也是使用ModelSerializer的好處之一
class WorkspaceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Workspace
        fields = [
            "id",
            "name",
            "owner",
            "members",
            "created_at",
        ]
        read_only_fields = ["id", "created_at"]
到目前為止,我們已經透過源碼跟做一些print來更了解序列化器的原理,但是但是我必須要還要再說一件事情XD
class WorkspaceList(APIView):
    def get(self, request):
        workspaces = Workspace.objects.all()
        serializer = WorkspaceSerializer(workspaces, many=True)
        return Response(serializer.data)
其中 workspaces = Workspace.objects.all()產生的querySet,顧名思義就是queries的集合對象,因此這時候其實沒有去資料庫要資料,只有當調用如count等方法時才會真的進行查詢
因此實際過程如下:
def get(self, request):
    workspaces = Workspace.objects.all()  # 不執行查詢
    serializer = WorkspaceSerializer(workspaces, many=True)  # 不執行查詢
    print("Before accessing data")  # 這裡還沒有執行查詢
    data = serializer.data  # 這裡觸發查詢和序列化
    print("After accessing data")  # 查詢已經執行
    return Response(data)
了解這個特性會十分重要,尤其是性能優化的部分
workspaces = Workspace.objects.all()
for workspace in workspaces:
    print(workspace.owner.username)  # 每次訪問 owner 都會觸發新的查詢
# 需要N+1查詢
workspaces = Workspace.objects.all().select_related('owner') # 只需查詢一次
for workspace in workspaces:
    print(workspace.owner.username)  # 不會觸發額外查詢
# select_related會回傳針對外鍵的querySet
但是也不要過度使用,可能會造成初始查詢壓力過大
明天我們就繼續介紹CBV的部分!