昨天我們已經將 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 欄位,設定幾個參數
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 大家可以搭配服用