第 9 篇:實現分類、標簽、歸檔日期接口


作者:HelloGitHub-追夢人物

我們的博客有一個側邊欄功能,分別列出博客文章的分類列表、標簽列表、歸檔時間列表,通過點擊側邊欄對應的條目,還可以進入相應的頁面。例如點擊某個分類,博客將跳轉到該分類下全部文章列表頁面。這些數據的展示都需要開發對應的接口,以便前端調用獲取數據。

分類列表、標簽列表實現比較簡單,我們這里給出接口的設計規范,大家可以使用前幾篇教程中學到的知識點輕松實現(具體實現可參考 GtiHub 上的源代碼)。

分類列表接口: /categories/

標簽列表接口:/tags/

歸檔日期列表的接口實現稍微復雜一點,因為我們需要從已有文章中歸納文章發表日期。事實上,我們在上一部教程 HelloDjango - Django博客教程(第二版)的 頁面側邊欄:使用自定義模板標簽 已經講解了如何獲取歸檔日期列表,只是當時返回的歸檔日期列表直接用於模板的渲染,而這里我們需要將歸檔日期列表序列化后通過 API 接口返回。

具體來說,獲取博客文章發表時間歸檔列表的方法是調用查詢集(QuerySet)的 dates 方法,提取記錄中的日期。核心代碼就一句:

Post.objects.dates('created_time', 'month', order='DESC')

這里 Post.objects.dates 方法會返回一個列表,列表中的元素為每一篇文章(Post)的創建日期(已去重),日期都是 Python 的 date 對象,精確到月份,降序排列。

有了返回的歸檔日期列表,接下來就實現相應的 API 接口視圖函數:

blog/views.py

from rest_framework import mixins, status, viewsets
from rest_framework.decorators import action
from rest_framework.serializers import DateField

class PostViewSet(
    mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
	# ...

    @action(
        methods=["GET"], detail=False, url_path="archive/dates", url_name="archive-date"
    )
    def list_archive_dates(self, request, *args, **kwargs):
        dates = Post.objects.dates("created_time", "month", order="DESC")
        date_field = DateField()
        data = [date_field.to_representation(date) for date in dates]
        return Response(data=data, status=status.HTTP_200_OK)

注意這里我們涉及到了幾個以前沒有詳細講解過的用法。

一是 action 裝飾器,它用來裝飾一個視圖集中的方法,被裝飾的方法會被 django-rest-framework 的路由自動注冊為一個 API 接口。

回顧一下我們之前在使用視圖集 viewset 時提到過 action(動作)的概念,django-rest-framework 預定義了幾個標准的動作,分別為 list 獲取資源列表,retrieve 獲取單個資源、update 和 partial_update 更新資源、destroy 刪除資源,這些 action 具體的實現方法,分別由 mixins 模塊中的混入類提供。例如 用類視圖實現首頁 API 中我們介紹過 mixins.ListModelMixin,這個混入類提供了 list 動作對應的標准實現,即 list 方法。視圖集中所有以上提及的以標准動作命名的方法,都會被 django-rest-framework 的路由自動注冊為標准的 API 接口。

django-rest-framework 默認只能識別標准命名的視圖集方法並將其注冊為 API,但我們可以添加更多非標准的 action,而為了讓 django-rest-framework 能夠識別這些方法,就需要使用 action 裝飾器進行裝飾。

其實我們可以簡單地將 action 裝飾的方法看作是一個視圖函數的實現,因此可以看到方法傳入的第一個參數為 request 請求對象,函數體就是這個視圖函數需要執行的邏輯,顯然,方法最終必須要返回一個 HTTP 響應對象。

action 裝飾器通常用於在視圖集中添加額外的接口實現。例如這里我們已有了 PostViewSet 視圖集,標准的 list 實現了獲取文章資源列表的邏輯。我們想添加一個獲取文章歸檔日期列表的接口,因此添加了一個 list_archive_dates 方法,並使用 action 進行裝飾。通常如果要在視圖集中添加額外的接口實現,可以使用如下的模板代碼:

@action(
    methods=["allowed http method name"], 
    detail=False or True, 
    url_path="url/path", 
    url_name="url name"
)
def method_name(self, request, *args, **kwargs):
    # 接口邏輯的具體實現,返回一個 Response

通常 action 裝飾器以下 4 個參數都會設置:

methods:一個列表,指定訪問這個接口時允許的 HTTP 方法(GET、POST、PUT、PATCH、DELETE)

detail:True 或者 False。設置為 True,自動注冊的接口 URL 中會添加一個 pk 路徑參數(請看下面的示例),否則不會。

url_path:自動注冊的接口 URL。

url_name:接口名,主要用於通過接口名字反解對應的 URL。

當然,我們還可以在 action 中設置所有 ViewSet 類所支持的類屬性,例如 serializer_classpagination_classpermission_classes 等,用於覆蓋類視圖中設置的屬性值。

以上是 action 用法的一個基本介紹,現在來分析一下 list_archive_dates 這個 action 來加深理解。

methods 參數指定接口需要通過 GET 方法訪問,detail 為 Falseurl_path 設置為 archive/dates,因此最終自動生成的接口路由就是 /posts/archive/dates/。如果我們設置 detail 為 True,那么生成的接口路由就是 /posts/<int:pk>/archive/dates/,生成的 URL 中就會多一個 pk 路徑參數。

list_archive_dates 具體的實現邏輯中,以下幾點需要注意:

一是獨立使用序列化字段(Field)。之前序列化字段都是在序列化器(Serializer)里面使用的,因為通常來說接口需要序列化一個對象的多個字段。而這個接口中只需要序列化一個時間字段(類型為 Python 標准庫中的 datetime.date),所以沒必要單獨定義一個序列化器了,直接拿 django-rest-framework 提供的用於序列化時間類型的 DateField 就可以了。用法也很簡單,實例化序列化字段,調用其 to_representation 方法,將需要序列化的值傳入即可(其實序列化器在序列對象的多個字段時,內部也是分別調用對應序列化字段的 to_representation 方法)。

我們通過列表推導式生成一個序列化后的歸檔日期列表,這個列表是可被序列化的。接着我們在接口返回一個 ResponseResponse 將序列化后的結果包裝返回(保存在 data 屬性中),django-rest-framework 會進一步幫我們把這個 Response 中包含的數據解析為合適的格式(例如 JSON)。

status=status.HTTP_200_OK 指定這個接口返回的狀態碼,HTTP_200_OK 是一個預定義的常數,即 200。django-rest-framework 將常用 HTTP 請求的狀態碼常數預定義 status 模塊里,使用預定義的變量而不是直接使用數字的好處一是增強代碼可讀性,二是減少硬編碼。

由於 PostViewSet 視圖集已經通過 django-rest-framework 的路由進行了注冊,因此 list_archive_dates 也會被連帶着自動注冊為一個接口。啟動開發服務器,訪問 /posts/archive/dates/,就可以看到返回的文章歸檔日期列表。

![文章歸檔日期返回結果](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/

.png)

注意到紅框圈出部分,django-rest-framework API 交互后台會識別到額外定義的 action 並將它們展示出來,點擊就可以進入到相應的 API 頁面。

現在,側邊欄所需要的數據接口就開發完成了,接下來實現返回某一分類、標簽或者歸檔日期下的文章列表接口。

使用視圖集簡化代碼 我們開發了獲取全部文章的接口。事實上,分類、標簽或者歸檔日期文章列表的 API,本質上還是返回一個文章列表資源,只不過比首頁 API 返回的文章列表資源多了個“過濾”,只過濾出了指定的部分文章而已。對於這樣的場景,我們可以在請求 API 時加上查詢參數,django-rest-framework 解析查詢參數,然后從全部文章列表中過濾出查詢所指定的文章列表再返回。

這在 RESTful API 的設計中肯定是會遇到的,因此第三方庫 django-filter 幫我們實現了上述所說的查詢過濾功能,而且和 django-rest-framework 有很好的集成,我們可以在 django-rest-framework 中非常方便地使用 django-filter。

既然要使用它,當然是先安裝它(已安裝跳過):pipenv install django-filter

接着我們來配置 PostViewSet,為其設置用於過濾返回結果集的一些屬性,代碼如下:

from django_filters.rest_framework import DjangoFilterBackend
from .filters import PostFilter

class PostViewSet(
    mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
    # ...
    filter_backends = [DjangoFilterBackend]
    filterset_class = PostFilter

非常的簡單,僅僅設置了 filter_backendsfilterset_class 兩個屬性。其中 filter_backends 設置為 DjangoFilterBackend,這樣 API 在返回結果時, django-rest-framework 會調用設置的 backend(這里是 DjangoFilterBackend) 的 filter 方法對 get_queryset 方法返回的結果進行進一步的過濾,而 DjangoFilterBackend 會依據 filterset_class(這里是 PostFilter)中定義的過濾規則來過濾查詢結果集。

當然 PostFilter 還沒有定義,我們來定義它。首先在 blog 應用下創建一個 filters.py 文件,用於存放自定義 filter 的代碼,PostFilter 代碼如下:

from django_filters import rest_framework as drf_filters

from .models import Post


class PostFilter(drf_filters.FilterSet):
    created_year = drf_filters.NumberFilter(
        field_name="created_time", lookup_expr="year"
    )
    created_month = drf_filters.NumberFilter(
        field_name="created_time", lookup_expr="month"
    )

    class Meta:
        model = Post
        fields = ["category", "tags", "created_year", "created_month"]

PostFilter 的定義和序列化器 Serializer 非常類似。

categorytags 兩個過濾字段因為是 Post 模型中定義的字段,因此 django-filter 可以自動推斷其過濾規則,只需要在 Meta.fields 中聲明即可。

歸檔日期下的文章列表,我們設計的接口傳遞 2 個查詢參數:年份和月份。由於這兩個字段在 Post 中沒有定義,Post 記錄時間的字段為 created_time,因此我們需要顯示地定義查詢規則,定義的規則是:

查詢參數名 = 查詢參數值的類型(查詢的模型字段,查詢表達式)

例如示例中定義的 created_year 查詢參數,查詢參數值的類型為 number,即數字,查詢的模型字段為 created_time,查詢表達式是 year。當用戶傳遞 created_year 查詢參數時,django-filter 實際上會將以上定義的規則翻譯為如下的 ORM 查詢語句:

Post.objects.filter(created_time__year=created_year傳遞的值)

現在回到 API 交互后台,先進到 /post/ 接口下,默認返回了全部文章列表。可以看到右上角多了個過濾器(紅框圈出部分)。

點擊會彈出過濾參數輸入的交互面板,在這里可以交互式地輸入查詢過濾參數的值。

例如選擇如下的過濾參數,得到查詢的 URL 為:

http://127.0.0.1:10000/api/posts/?category=1&tags=1&created_year=2020&created_month=1

這條查詢返回創建於 2020 年 1 月,id 為 1 的分類下,id 為 1 的標簽下的全部文章。

通過不同的查詢參數組合,就可以得到不同的文章資源列表了。


關注公眾號加入交流群


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM