1. 程式人生 > WINDOWS開發 >第 3 篇:實現部落格首頁文章列表 API

第 3 篇:實現部落格首頁文章列表 API

技術分享圖片

作者:HelloGitHub-追夢人物

文中所涉及的示例程式碼,已同步更新到 HelloGitHub-Team 倉庫

此前在討論基於模板引擎的開發方式和 django-rest-framework 開發的異同時說過,django-rest-framework 開發和傳統的開發方式沒有什麼不同,區別僅在於返回的資料格式不同而已。

在基於模板引擎的開發方式中,部落格首頁文章列表的檢視函式可能是這樣的:

from django.shortcuts import render
from .models import Post

def index(request):
    post_list = Post.objects.all().order_by(‘-created_time‘)
    return render(request,‘blog/index.html‘,context={‘post_list‘: post_list})

在 django-rest-framework,程式碼邏輯是一樣的,只是在最後返回結果時,返回資源序列化後的結果。

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status

from .models import Post
from .serializers import PostListSerializer

@api_view(http_method_names=["GET"])
def index(request):
    post_list = Post.objects.all().order_by(‘-created_time‘)
    serializer = PostListSerializer(post_list,many=True)
    return Response(serializer.data,status=status.200)

暫且忽略掉資源序列化器 PostListSerializer,我們接下來會實現它,先把注意力放在主體邏輯上。

首先,我們從 rest_framework.decorators 中匯入了 api_view 裝飾器,並用它裝飾了 index 檢視函式,使其成為一個 RESTful API 檢視函式。

為什麼需要這個檢視函式裝飾器呢?之前說過,django-rest-framework 為 API 的開發提供了豐富的功能,包括內容協商、認證和鑑權、限流等等。這些過程 django 預設的檢視函式在處理 HTTP 請求時是沒有提供的,而經過 api_view 裝飾後的檢視,則提供了上述全部功能。

不過我們這裡並沒有看到任何內容協商、認證和鑑權、限流程式碼邏輯和配置,這是為什麼呢?原因隱藏在 Python 的裝飾器魔法裡,django-rest-framework 對於上述功能有一套預設的處理邏輯,因此我們不需要進行任何配置,僅需使用 api_view

裝飾一個 django 檢視函式,所有功能全部自動開啟。

檢視函式裡我們先從資料庫獲取文章列表資源,然後使用序列化器對其進行序列化,序列化後的資料存在 data 屬性裡,我們把它傳遞給 HTTP 響應類 Response,並將這個響應返回。

注意這個 Response 是從 rest_framework.response 中匯入的,它類似於 django 的 HTTPResponse 響應類。實際上,這個類是 django-rest-framework 對 django 的模板響應類(SimpleTemplateResponse)的拓展(具體的細節可以不用瞭解,只要知道 django 使用它來渲染模板並構造 HTTP 響應即可),通常在 RESTful API 的檢視函式中我們都會返回這個類,而不是 django 的 HTTP 響應類。此外,通過傳入 status 引數,指定 HTTP 響應的狀態碼。

小貼士

請了解常用的 HTTP 狀態碼。在 RESTful 架構中,客戶端通過 HTTP 請求動詞表徵對資源的操作意圖,而服務端則使用 HTTP 狀態碼錶示資源操作的結果。常用狀態碼及其含義如下:

200:通常表示請求成功。

201:表示資源建立成功。

400:表示客戶端請求錯誤。

401:沒有提供身份認證資訊

403:沒有操作許可權

404 :訪問的資源不存在

405:不支援的 HTTP 請求方法

500:伺服器內部錯誤

HTTP 請求和響應過程,django-rest-framework 已經幫我們處理。但是資源的序列化,框架是無法自動化完成的,框架提供了基本的序列化器,我們需要自定義序列化邏輯。所以,讓我們來定義 PostListSerializer 序列化器,用它來序列化文章列表。

序列化器由一系列的序列化欄位(Field)組成,序列化欄位的作用是,在序列化資源時,將 Python 資料型別轉為原始資料型別(通常為字元型別或者二進位制型別),以便在客戶端和服務端之間傳遞;反序列化時,將原始資料型別轉為 Python 資料型別。在轉換過程中,還會進行資料合法性的校驗。

先來看一個簡單的例子(摘自 django-rest-framework 官網示例),理解序列化器的工作原理和功能。假設我們有一個 Python 類 Comment

from datetime import datetime

class Comment(object):
    def __init__(self,email,content,created=None):
        self.email = email
        self.content = content
        self.created = created or datetime.now()

comment = Comment(email=‘[email protected]‘,content=‘foo bar‘)

根據 Comment 類 3 個屬性的型別,定義一個序列化器,用於資料序列化和反序列化。我們在上一步教程的 交流的橋樑:評論功能 中介紹過表單(Form)的定義。實際上,django-rest-framework 序列化器的設計參考了 django 表單的設計。序列化器和表單也有很多相似功能,比如對輸入資料進行校驗等。序列化器的程式碼如下:

from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

自定義的序列化器都要繼承 serializers.Serializer 基類,基類提供了資料序列化和反序列化的邏輯。根據被序列化物件的屬性的資料型別,需要指定相應的序列化欄位(Serializer Field)。django-rest-framework 提供了很多常用的序列化欄位,例如本例中用於序列化 email 資料格式的 EmailField,用於序列化字元型資料格式的 CharField,用於序列化日期格式的 DateTimeField。在實際專案中,應該根據資料型別,選擇合適的序列化欄位。全部序列化欄位,可以參考官方文件 Serializer fields

有了序列化器,就可以將 Comment 物件序列化了,序列化器用法如下:

>>> serializer = CommentSerializer(comment)
>>> serializer.data
# 輸出:
{‘email‘: ‘[email protected]‘,‘content‘: ‘foo bar‘,‘created‘: ‘2016-01-27T15:17:10.375877‘}

首先將需要序列化的物件(comment)傳入序列化器(CommentSerializer),構造一個序列化器物件(serializer),訪問序列化器物件的 data 屬性,就可以得到序列化後的資料。

被序列化物件序列化後的資料是一個扁平的 Python 字典,字典中的資料描述了這個物件資源。有了序列化生成的 Python 字典,我們就可以將字典資料進一步格式化為 JSON 字串或者 XML 文件字串,在客戶端和服務端之間傳輸。試想,客戶端服務端通常都通過 HTTP 協議傳輸資料,傳輸的資料只能是字串或者二進位制資料,不可能將一個 Python 的物件直接傳遞,這就是為什麼要序列化的原因。一端接收到序列化的資料後,如果有需要,可以對資料進行反序列化,重新恢復為 Python 物件。

以上就是一個標準序列化器的定義。其關鍵點在於,根據被序列化物件屬性的資料型別,選擇合適的序列化欄位。回顧我們在上一步教程的 交流的橋樑:評論功能 中對評論表單的定義,我們通過繼承 ModelForm 定義了表單,而並沒有顯示地指定表單欄位的型別。原因在於,對於 django 中的模型(Model),已經有了定義其資料型別的模型欄位,因此 django 表單可以根據關聯的模型,自動推測需要使用的表單欄位,在背後幫我們完成表單欄位的選擇,簡化了表單的定義。

和表單類似,django-rest-framework 的序列化器也可以根據關聯的模型,自動檢測被序列化模型各個屬性的資料型別,推測需要使用的序列化欄位,無需我們顯示定義。此時,自定義的序列化器不再繼承標準的 Serializer,而是繼承其子類,ModelSerializer

我們來編寫文章(Post)模型的序列化器程式碼。按照習慣,序列化器的程式碼位於相應應用的 serializers.py 模組中,因此在 blog 應用下新建一個 serializers.py 檔案,寫上如下程式碼:

from rest_framework import serializers
from .models import Post

class PostListSerializer(serializers.ModelSerializer):
    category = CategorySerializer()
    author = UserSerializer()
    
    class Meta:
        model = Post
        fields = [
            ‘id‘,‘title‘,‘created_time‘,‘excerpt‘,‘category‘,‘author‘,‘views‘,]

使用 ModelSerializer 時,只需要在序列化器的內部類 Meta 中指定關聯的模型,以及需要序列化的模型屬性,django-rest-framework 就會根據各個屬性的資料型別,自動推測需要使用的系列化欄位,從而生成標準的序列化器。事實上,我們可以來看一下 django-rest-framework 最終生成的序列化器長什麼樣子:

class PostListSerializer():
    id = IntegerField(label=‘ID‘,read_only=True)
    title = CharField(label=‘標題‘,max_length=70)
    created_time = DateTimeField(label=‘建立時間‘,required=False)
    excerpt = CharField(allow_blank=True,label=‘摘要‘,max_length=200,required=False)
    category = CategorySerializer()
    author = UserSerializer()

還需要注意一點,titlecreated_timeviews 這些屬性都是原始的資料型別(字元型、日期型、整數型別)。而對於文章關聯的 categoryauthor,它們本身也是一個物件,django-rest-framework 就無法推測該使用什麼型別的系列化欄位來序列化它們了。所以這裡我們按照標準序列化器的定義方式,將這兩個屬性的系列化欄位分別定義為 CategorySerializerUserSerializer,意思是告訴 django-rest-framework,請使用 CategorySerializerUserSerializer 來序列化關聯的 categoryauthor。實際上,序列化器本身也是一個序列化欄位。當然,CategorySerializerUserSerializer 目前還不存在,我們來定義他們:

from django.contrib.auth.models import User
from .models import Category,Post

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = [
            ‘id‘,‘name‘,]
        
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = [
            ‘id‘,‘username‘,]
        
class PostListSerializer(serializers.ModelSerializer):
	# ...

再來回顧一下我們的 API 檢視函式程式碼:

@api_view(http_method_names=["GET"])
def index(request):
    post_list = Post.objects.all().order_by(‘-created_time‘)
    serializer = PostListSerializer(post_list,status=status.HTTP_200_OK)

注意這裡 PostListSerializer 的用法,構造序列化器時可以傳入單個物件,序列化器會將其序列化為一個字典;也可以傳入包含多個物件的可迭代型別(這裡的 post_list 是一個 django 的 QuerySet),此時需要設定 many 引數為 True 序列化器會依次序列化每一項,返回一個列表。

api_view 裝飾器傳入 http_method_names 引數指定允許訪問該 API 檢視的 HTTP 方法。

現在我們已經有了檢視函式,最後,我們需要給這個檢視函式繫結 URL,在 blog 應用下的 urls.py 中加入繫結的程式碼:

path(‘api/index/‘,views.index)

啟動開發伺服器,開啟瀏覽器訪問 http://127.0.0.1:8000/api/index/ ,可以看到介面返回了文章列表 JSON 格式的資料(預設為 JSON)。

技術分享圖片

目前來說,這個介面其實作用不大。不過在後續的教程中,我們學習前端框架 Vue,那個時候,RESTful API 就有了它的用武之地了。

回顧一下 index API 檢視函式的基本邏輯:

  1. 從資料庫取資料
  2. 構造序列化器並將取出的資料序列化
  3. 返回響應

這其實是訪問序列型的資源比較常見的邏輯,我們知道,django 專門為這種在 Web 開發中常用的邏輯提供了一系列基於類的通用檢視,以提高程式碼的複用性和減少程式碼量。只是 django 的通用檢視適用於基於模板引擎的開發方式,同樣的,django-rest-framework 也提供了專門針對 RESTful API 開發過程中常用邏輯的類檢視通用函式。接下來,讓我們使用 django-rest-framework 提供的通用類檢視,將首頁 API 的檢視函式改為類檢視。


技術分享圖片

關注公眾號加入交流群