做者: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_class
、pagination_class
、permission_classes
等,用於覆蓋類視圖中設置的屬性值。
以上是 action 用法的一個基本介紹,如今來分析一下 list_archive_dates
這個 action 來加深理解。
methods
參數指定接口須要經過 GET 方法訪問,detail 爲 False
,url_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
方法)。
咱們經過列表推導式生成一個序列化後的歸檔日期列表,這個列表是可被序列化的。接着咱們在接口返回一個 Response
, Response
將序列化後的結果包裝返回(保存在 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_backends
和 filterset_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 很是相似。
category
,tags
兩個過濾字段由於是 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 的標籤下的所有文章。
經過不一樣的查詢參數組合,就能夠獲得不一樣的文章資源列表了。