第 7 篇:文章詳情的 API 介面
- 2020 年 5 月 29 日
- 筆記
- HelloGitHub
一旦我們使用了視圖集,並實現了 HTTP 請求對應的 action 方法(對應規則的說明見 使用視圖集簡化程式碼),將其在路由器中註冊後,django-restframework 自動會自動為我們生成對應的 API 介面。
目前為止,我們只實現了 GET 請求對應的 action——list 方法,因此路由器只為我們生成了一個 API,這個 API 返迴文章資源列表。GET 請求還可以用於獲取單個資源,對應的 action 為 retrieve,因此,只要我們在視圖集中實現 retrieve 方法的邏輯,就可以直接生成獲取單篇文章資源的 API 介面。
貼心的是,django-rest-framework 已經幫我們把 retrieve 的邏輯在 mixins.RetrieveModelMixin
里寫好了,直接混入視圖集即可:
class PostViewSet(
mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
serializer_class = PostListSerializer
queryset = Post.objects.all()
permission_classes = [AllowAny]
現在,路由會自動增加一個 /posts/:pk/ 的 URL 模式,其中 pk 為文章的 id。訪問此 API 介面可以獲得指定文章 id 的資源。
實際上,實現各個 action 邏輯的混入類都非常簡單,以 RetrieveModelMixin
為例,我們來看看它的源碼:
class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
retrieve 方法首先調用 get_object
方法獲取需序列化的對象。get_object
方法通常情況下依據以下兩點來篩選出單個資源對象:
get_queryset
方法(或者queryset
屬性,get_queryset
方法返回的值優先)返回的資源列表對象。lookup_field
屬性指定的資源篩選欄位(默認為 pk)。django-rest-framework 以該欄位的值從get_queryset
返回的資源列表中篩選出單個資源對象。lookup_field
欄位的值將從請求的 URL 中捕獲,所以你看到文章介面的 url 模式為 /posts/:pk/,假設將lookup_field
指定為 title,則 url 模式為 /posts/:title/,此時將根據文章標題獲取單篇文章資源。
文章詳情 Serializer
現在,假設我們要獲取 id 為 1 的文章資源,訪問獲取單篇文章資源的 API 介面 //127.0.0.1:10000/api/posts/1/,得到如下的返回結果:
可以看到很多我們需要在詳情頁中展示的欄位值並沒有返回,比如文章正文(body)。原因是視圖集中指定的文章序列化器為 PostListSerializer,這個序列化器被用於序列化文章列表。因為展示文章列表數據時,有些欄位用不上,所以出於性能考慮,只序列化了部分欄位。
顯然,我們需要給文章詳情寫一個新的序列化器了:
from .models import Category, Post, Tag
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = [
"id",
"name",
]
class PostRetrieveSerializer(serializers.ModelSerializer):
category = CategorySerializer()
author = UserSerializer()
tags = TagSerializer(many=True)
class Meta:
model = Post
fields = [
"id",
"title",
"body",
"created_time",
"modified_time",
"excerpt",
"views",
"category",
"author",
"tags",
]
詳情序列化器和列表序列化器幾乎一樣,只是在 fields 中指定了更多需要序列化的欄位。
同時注意,為了序列化文章的標籤 tags,我們新增了一個 TagSerializer
,由於文章可能有多個標籤,因為 tags 是一個列表,要序列化一個列表資源,需要將序列化器參數 many
的值指定為 True
。
動態 Serializer
現在新的序列化器寫好了,可是在哪裡指定呢?視圖集中 serializer_class
屬性已經被指定為了 PostListSerializer
,那 PostRetrieveSerializer
應該指定在哪呢?
類似於視圖集類的 queryset
屬性和 get_queryset
方法的關係, serializer_class
屬性的值也可以通過 get_serializer_class
方法返回的值覆蓋,因此我們可以根據不同的 action 動作來動態指定對應的序列化器。
那麼如何在視圖集中區分不同的 action 動作呢?視圖集有一個 action 屬性,專門用來記錄當前請求對應的動作。對應關係如下:
HTTP 請求 | 對應 action 屬性的值 |
---|---|
GET | list(資源列表)/ retrieve(單個資源) |
PUT | update |
PATCH | partial_update |
DELETE | destory |
因此,我們在視圖集中重寫 get_serializer_class
方法,寫入我們自己的邏輯,就可以根據不同請求,分別獲取相應的序列化器了:
class PostViewSet(
mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
# ... 省略其他屬性和方法
def get_serializer_class():
if self.action == 'list':
return PostListSerializer
elif self.action == 'retrieve':
return PostRetrieveSerializer
else:
return super().get_serializer_class()
後續對於其他動作,可以再加 elif 判斷,不過如果動作變多了,就會有很多的 if 判斷。更好的做好是,給視圖集加一個屬性,用於配置 action 和 serializer_class 的對應關係,通過查表法查找 action 應該使用的序列化器。
class PostDetailViewSet(viewsets.GenericViewSet):
# ... 省略其他屬性和方法
serializer_class_table = {
'list': PostListSerializer,
'retrieve': PostRetrieveSerializer,
}
def get_serializer_class():
return self.serializer_class_table.get(
self.action, super().get_serializer_class()
)
現在,再次訪問單篇文章 API 介面,可以看到返回了更加詳細的部落格文章數據了:
關注公眾號加入交流群