第 10 篇 評論介面

削微寒發表於2020-06-19

作者:HelloGitHub-追夢人物

此前我們一直在操作部落格文章(Post)資源,並藉此介紹了序列化器(Serializer)、檢視集(Viewset)、路由器(Router)等 django-rest-framework 提供的便利工具,藉助這些工具,就可以非常快速地完成 RESTful API 的開發。

評論(Comment)是另一種資源,我們同樣藉助以上工具來完成對評論資源的介面開發。

首先是設計評論 API 的 URL,根據 RESTful API 的設計規範,評論資源的 URL 設計為:/comments/

對評論資源的操作有獲取某篇文章下的評論列表和建立評論兩種操作,因此相應的 HTTP 請求和動作(action)對應如下:

HTTP請求 Action URL
GET list_comments /posts/:id/comments/
POST create /comments/

文章評論列表 API 使用自定義的 action,放在 /post/ 介面的檢視集下;發表評論介面使用標準的 create action,需要定義單獨的檢視集。

然後需要一個序列化器,用於評論資源的序列化(獲取評論時),反序列化(建立評論時)。有了編寫文章序列化器的基礎,評論序列化器就是依葫蘆畫瓢的事。

comments/serializers.py

from rest_framework import serializers
from .models import Comment


class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = [
            "name",
            "email",
            "url",
            "text",
            "created_time",
            "post",
        ]
        read_only_fields = [
            "created_time",
        ]
        extra_kwargs = {"post": {"write_only": True}}

注意這裡我們在 Meta 中增加了 read_only_fieldsextra_kwargs 的宣告。

read_only_fields 用於指定只讀欄位的列表,由於 created_time 是自動生成的,用於記錄評論釋出時間,因此宣告為只讀的,不允許通過介面進行修改。

extra_kwargs 指定傳入每個序列化欄位的額外引數,這裡給 post 序列化欄位傳入了 write_only 關鍵字引數,這樣就將 post 宣告為只寫的欄位,這樣 post 欄位的值僅在建立評論時需要。而在返回的資源中,post 欄位就不會出現。

首先來實現建立評論的介面,先為評論建立一個檢視集:

comments/views.py

from rest_framework import mixins, viewsets
from .models import Comment
from .serializers import CommentSerializer

class CommentViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
    serializer_class = CommentSerializer

    def get_queryset(self):
        return Comment.objects.all()

檢視集非常的簡單,混入 CreateModelMixin 後,檢視集就實現了標準的 create action。其實 create action 方法的實現也非常簡單,我們來學習一下 CreateModelMixin 的原始碼實現。

class CreateModelMixin:
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

核心邏輯在 create 方法:首先取到繫結了使用者提交資料的序列化器,用於反序列化。接著呼叫 is_valid 方法校驗資料合法性,如果不合法,會直接丟擲異常(raise_exception=True)。否則就執行序列化的 save 邏輯將評論資料存入資料庫,最後返回響應。

接著在 router 裡註冊 CommentViewSet 檢視集:

router.register(r"comments", comments.views.CommentViewSet, basename="comment")

進入 API 互動後臺,可以看到首頁列出了 comments 介面的 URL,點選進入 /comments/ 後可以看到一個評論表單,在這裡可以提交評論資料與建立評論的介面進行互動。

接下來實現獲取評論列表的介面。通常情況下,我們都是隻獲取某篇部落格文章下的評論列表,因此我們的 API 設計成了 /posts/:id/comments/。這個介面具有很強的語義,非常符合 RESTful API 的設計規範。

由於介面位於 /posts/ 空間下,因此我們在 PostViewSet 新增自定義 action 來實現,先來看程式碼:

blog/views.py

class PostViewSet(
    mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
    # ...
    
    @action(
            methods=["GET"],
            detail=True,
            url_path="comments",
            url_name="comment",
            pagination_class=LimitOffsetPagination,
            serializer_class=CommentSerializer,
    )
    def list_comments(self, request, *args, **kwargs):
        # 根據 URL 傳入的引數值(文章 id)獲取到部落格文章記錄
        post = self.get_object()
        # 獲取文章下關聯的全部評論
        queryset = post.comment_set.all().order_by("-created_time")
        # 對評論列表進行分頁,根據 URL 傳入的引數獲取指定頁的評論
        page = self.paginate_queryset(queryset)
        # 序列化評論
        serializer = self.get_serializer(page, many=True)
        # 返回分頁後的評論列表
        return self.get_paginated_response(serializer.data)

action 裝飾器我們在上一篇教程中進行了詳細說明,這裡我們再一次接觸到 action 裝飾器更為深入的用法,可以看到我們除了設定 methodsdetailurl_path 這些引數外,還通過設定 pagination_classserializer_class 來覆蓋原本在 PostViewSet 中設定的這些類屬性的值(例如對於分頁,PostViewSet 預設為我們之前設定的 PageNumberPagination,而這裡我們替換為 LimitOffsetPagination)。

list_comments 方法邏輯非常清晰,註釋中給出了詳細的說明。另外還可以看到我們呼叫了一些輔助方法,例如 paginate_queryset 對查詢集進行分頁;get_paginated_response 返回分頁後的 HTTP 響應,這些方法其實都是 GenericViewSet 提供的通用輔助方法,原始碼也並不複雜,如果不用這些方法,我們自己也可以輕鬆實現,但既然 django-rest-framework 已經為我們寫好了,直接複用就行,具體的實現請大家通過閱讀原始碼進行學習。

現在進入 API 互動後臺,進入某篇文章的詳細介面,例如訪問 /api/posts/5/,Extra Actions 下拉框中可以看到 List comments 的選項:

點選 List comments 即可進入這篇文章下的評論列表介面,獲取這篇文章的評論列表資源了:


關注公眾號加入交流群

相關文章