iT邦幫忙

2024 iThome 鐵人賽

DAY 14
1
Software Development

Django 2024: 從入門到SaaS實戰系列 第 16

Django REST framework: 深入探討視圖類之前,不可不知道的序列化器原理

  • 分享至 

  • xImage
  •  

在深入探討Class Based View(CBV)之前,我先補上昨天在序列化器沒有提及的觀念

我們透過最像Functional Based View(FBV)的APIView來帶入CBV的同時,補充一些觀念

今日重點:

  • 初探CBV:APIView
  • 序列化器原理詳解
  • 懶加載

初探CBV:APIView

我們將昨天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拿到請求後,根據請求方法來判斷要給哪個函式進行處理

  • get方法中,先產生Workspace的queryset對象,我們將其丟入序列化器中,進行序列化,最終透過Response返回JSON資料
  • post方法中,透將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字符串

  • 序列化器的實際角色:
    • 序列化器並不直接將 JSON 轉換為 Python 對象,或反之
    • 序列化器的主要任務是:
      • 驗證數據
      • 轉換數據結構(例如從字典轉換到model實例)
      • 應用業務邏輯(例如在 createupdate 方法中)
  • 真正的數據流程:
    • JSON → Python 對象:這是由 DRF 的請求解析器完成的
    • Python 對象 → 模型實例:這是序列化器的 createupdate 方法處理的
    • 模型實例 → Python 對象:這是序列化器的序列化過程
    • Python 對象 → JSON:這是由 DRF 的渲染器(通常是 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,怎麼知道是序列化還是反序列化?

  • 參數填入時,序列化器檢查是不是model實例,如果是就將其轉成可序列化的數據
  • 而我們使用data關鍵字傳參時,序列化器就知道需要驗證後建立
  • 而傳入模型實例與資料傳入時,就知道是一個更新操作

那這時候可能又又有新的問題,我們在序列化器不論是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的領域,看到使用最基礎的APIView使用方式
  • 完整理解序列化器的運作原理,更清楚資料格式轉換的過程
  • 了解到querySey懶加載的特性,這點在我們使用序列化器自定義方法時是特別需要知道的觀念

明天我們就繼續介紹CBV的部分!

參考資料

  • DRF parser:https://www.django-rest-framework.org/api-guide/parsers/#jsonparser
  • DRF render:https://www.django-rest-framework.org/api-guide/renderers/#jsonrenderer
  • Django select_related:https://docs.djangoproject.com/en/4.2/ref/models/querysets/#select-related

上一篇
Django REST framework: 序列化器與視圖函式 開啟API之旅
下一篇
Django REST framework: 視圖的進化之旅 - GenericAPI 到 ViewSet,從通用基礎到高層抽象
系列文
Django 2024: 從入門到SaaS實戰31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言