第 7 篇:文章詳情的 API 介面
阿新 • • 發佈:2020-05-29
![](https://img2020.cnblogs.com/blog/759200/202004/759200-20200415161158343-1662112908.jpg)
作者:[HelloGitHub-追夢人物](https://www.zmrenwu.com)
一旦我們使用了檢視集,並實現了 HTTP 請求對應的 action 方法(對應規則的說明見 [使用檢視集簡化程式碼](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/95/)),將其在路由器中註冊後,django-restframework 自動會自動為我們生成對應的 API 介面。
目前為止,我們只實現了 GET 請求對應的 action——list 方法,因此路由器只為我們生成了一個 API,這個 API 返回文章資源列表。GET 請求還可以用於獲取單個資源,對應的 action 為 retrieve,因此,只要我們在檢視集中實現 retrieve 方法的邏輯,就可以直接生成獲取單篇文章資源的 API 介面。
貼心的是,django-rest-framework 已經幫我們把 retrieve 的邏輯在 `mixins.RetrieveModelMixin` 裡寫好了,直接混入檢視集即可:
```python
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` 為例,我們來看看它的原始碼:
```python
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` 方法通常情況下依據以下兩點來篩選出單個資源物件:
1. `get_queryset` 方法(或者 `queryset` 屬性,`get_queryset` 方法返回的值優先)返回的資源列表物件。
2. `lookup_field` 屬性指定的資源篩選欄位(預設為 pk)。django-rest-framework 以該欄位的值從 `get_queryset` 返回的資源列表中篩選出單個資源物件。`lookup_field` 欄位的值將從請求的 URL 中捕獲,所以你看到文章介面的 url 模式為 /posts/:pk/,假設將 `lookup_field` 指定為 title,則 url 模式為 /posts/:title/,此時將根據文章標題獲取單篇文章資源。
## 文章詳情 Serializer
現在,假設我們要獲取 id 為 1 的文章資源,訪問獲取單篇文章資源的 API 介面 http://127.0.0.1:10000/api/posts/1/,得到如下的返回結果:
![](https://img2020.cnblogs.com/blog/759200/202005/759200-20200528220212071-1566437546.png)
可以看到很多我們需要在詳情頁中展示的欄位值並沒有返回,比如文章正文(body)。原因是檢視集中指定的文章序列化器為 PostListSerializer,這個序列化器被用於序列化文章列表。因為展示文章列表資料時,有些欄位用不上,所以出於效能考慮,只序列化了部分欄位。
顯然,我們需要給文章詳情寫一個新的序列化器了:
```python
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` 方法,寫入我們自己的邏輯,就可以根據不同請求,分別獲取相應的序列化器了:
```python
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 應該使用的序列化器。
```python
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 介面,可以看到返回了更加詳細的部落格文章資料了:
![](https://img2020.cnblogs.com/blog/759200/202005/759200-20200528220145682-806764524.png)
---
![](https://img2018.cnblogs.com/blog/759200/202002/759200-20200213201956024-782757549.png)
**關注公眾號加入交流群**