1. 程式人生 > >第 7 篇:文章詳情的 API 介面

第 7 篇:文章詳情的 API 介面

![](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) **關注公眾號加入交流群**