在深入探討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的部分!