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

做者:HelloGitHub-追夢人物前端

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

分類列表、標籤列表實現比較簡單,咱們這裏給出接口的設計規範,你們可使用前幾篇教程中學到的知識點輕鬆實現(具體實現可參考 GtiHub 上的源代碼)。github

分類列表接口: /categories/web

標籤列表接口:/tags/django

歸檔日期列表的接口實現稍微複雜一點,由於咱們須要從已有文章中概括文章發表日期。事實上,咱們在上一部教程 HelloDjango - Django博客教程(第二版)的 頁面側邊欄:使用自定義模板標籤 已經講解了如何獲取歸檔日期列表,只是當時返回的歸檔日期列表直接用於模板的渲染,而這裏咱們須要將歸檔日期列表序列化後經過 API 接口返回。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/,就能夠看到返回的文章歸檔日期列表。

注意到紅框圈出部分,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 的標籤下的所有文章。

經過不一樣的查詢參數組合,就能夠獲得不一樣的文章資源列表了。


關注公衆號加入交流羣
相關文章
相關標籤/搜索