iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0

前言

昨天我們已經將 Tag 與 Task 關聯起來,並且讓我們可以透過 API 存放兩者的關係,但是這樣對前端來說不是太友善,讓我們調整一下序列化讓前端開心點吧!

分析回傳內容的問題

首先讓我們看看現在 /api/todo/tasks 的回傳內容

[
    {
        "id": 5,
        "title": "測試任務一",
        "description": "這是一個測試任務",
        "is_finish": false,
        "tags": [
            1
        ]
    },
    {
        "id": 6,
        "title": "測試任務與標籤",
        "description": "這是一個測試任務",
        "is_finish": false,
        "tags": [
            1
        ]
    }
]

從上方的回應我們可以發現 tags 這個欄位回傳的是一個 JSON Array 裡面是有關係的標籤的 ID。

但是大家想像一下,使用這個 API 的程式(例如:前端網頁或是手機 APP)顯示給使用者看的時候,不可能顯示 ID 對吧?那如果依照現在的設計要讓使用的程式能顯示標籤的名稱,他們就需要再拿 Tag 的 ID 再呼叫 Tag 相關的 API 這樣才能得到 Tag 的名稱。

這樣前端同事可能會因為要打很多次 API 而生氣,使用者也可能因為等待時間很久而不喜歡用我們的程式,所以讓我們修改一下回傳內容吧,下方是我希望修改後的回傳內容

[
    {
        "id": 5,
        "title": "測試任務一",
        "description": "這是一個測試任務",
        "is_finish": false,
        "tags": [
            {
                "id": 1,
                "name": "T01"
            }
        ]
    }
]

然後希望前端再建立 Task 的時候可以透過下方這個格式來新增任務

{
    "title": "測試任務一",
    "description": "這是一個測試任務",
    "is_finish": false,
    "tag_ids": [
        1
    ]
}

修改序列化的回傳格式

現在讓我們先來修改回傳內容,等等再來處理建立的部分,首先讓我們修改 server/app/todo/serializers.py 的檔案,我們先把 TaskSerializer 移動到 TagSerializer 下方

from rest_framework import serializers

from server.app.todo import models as todo_models


-class TaskSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = todo_models.Task
-        fields = "__all__"


class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = todo_models.Tag
        fields = "__all__"


+class TaskSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = todo_models.Task
+        fields = "__all__"

接著讓我們調整 TaskSerializer 回傳的 tags 格式

# ...... 以上省略 ......

class TaskSerializer(serializers.ModelSerializer):
+   tags = TagSerializer(many=True)
+
    class Meta:
        model = todo_models.Task
        fields = "__all__"

上方的調整目的是希望調整 tags 這個欄位,原本是回傳一個 Array 裡面放 ID,那我們現在希望他用 TagSerializer 格式來回傳,然後我們設定 many=True 代表等等的回傳會有多筆(也就是會使用 Array 這個型態)

現在讓我們呼叫 GET http://127.0.0.1:8000/api/todo/tasks 來看看回傳結果,應該會看起來像下方這個樣子

[
    {
        "id": 5,
        "tags": [
            {
                "id": 1,
                "name": "T01"
            }
        ],
        "title": "測試任務一",
        "description": "這是一個測試任務",
        "is_finish": false
    },
    {
        "id": 6,
        "tags": [
            {
                "id": 1,
                "name": "T01"
            }
        ],
        "title": "測試任務與標籤",
        "description": "這是一個測試任務",
        "is_finish": false
    }
]

這樣就達到我們的目標了!

修改序列化的建立格式

現在讓我們呼叫 POST http://127.0.0.1:8000/api/todo/tasks 並使用下方的 HTTP Body(跟昨天的一樣)

{
    "title": "測試任務與標籤",
    "description": "這是一個測試任務",
    "is_finish": false,
    "tags": [
        1
    ]
}

會發現會回傳錯誤如下

{
    "tags": [
        {
            "non_field_errors": [
                "Invalid data. Expected a dictionary, but got int."
            ]
        }
    ]
}

原因是因為 tags 這個欄位被我們修改成 TagSerializer 格式了,所以沒辦法傳 Array 裡面放 ID 給後端。

所以我們要來定義一個全新的欄位叫做 tag_ids 他會收一個 Array 裡面放 ID 讓前端傳入要關聯的 Tag 這個欄位只會用於寫入,而 tags 的欄位只用於回傳,所以讓我們修改一下 server/app/todo/serializers.py 的內容

# ...... 以上省略 ......


class TaskSerializer(serializers.ModelSerializer):
-   tags = TagSerializer(many=True)
+   tags = TagSerializer(many=True, read_only=True)
+   tag_ids = serializers.PrimaryKeyRelatedField(
+       allow_empty=False,
+       write_only=True,
+       many=True,
+       queryset=todo_models.Tag.objects.all(),
+       source="tags",
+   )

    class Meta:
        model = todo_models.Task
        fields = "__all__"

上面我們將 tags 修改成 read_only 代表只供回傳使用,再來我們新增 tag_ids 欄位,設定幾個參數

  • allow_empty: 設定為 False 代表不能傳入空的陣列
  • write_only: 設定為 True 代表只供寫入使用
  • many: 設定為 True 代表會傳入多筆
  • queryset: 代表傳入的 ID 的只能從這些 Tag 中來
  • source: 代表要將序列化 tag_ids 收到的陣列內容存到 Model 的 tags 欄位中

接著我們試試呼叫 POST http://127.0.0.1:8000/api/todo/tasks 並使用下方的 HTTP Body

{
    "title": "測試任務與標籤",
    "description": "這是一個測試任務",
    "is_finish": false,
    "tag_ids": [
        1
    ]
}

就可以新增成功了。

總結

今天我們修改了 Task 的序列化,讓前端比較好做事。結束前別忘了檢查一下今天的程式碼有沒有問題,並排版好喔。

ruff check --fix .
black .
pyright .

明天讓我們繼續將任務清單做得更完整吧!

P.S. 今天的檔案更新可以參考我的 Git Commit 大家可以搭配服用


上一篇
Day09 - 資料庫關聯與 API
下一篇
Day11 - 客製化 ViewSet 使用不同的格式
系列文
Django REST 大冒險:探索精彩紛呈的 API 開發世界30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言