上一篇我們討論了,請求 URL 中關於路徑參數的處理方式。
本文將介紹查詢參數(query parameters),這是 RESTful API 中用來傳遞過濾條件等額外資訊的重要部分。
處理查詢參數在 Django Ninja 中非常簡單直觀,我們可以透過多種方式來達成。
本文所有的程式碼變動,可參考這個 PR。
查詢參數是 URL 中的可選參數,通常位於 path 的後方,以?key=value
的形式出現,用來傳遞額外的資訊。
例如,當我們需要過濾某位作者的文章時,URL 的 path 可能會這樣寫:
/posts/?author=john
URL 傳遞了一個查詢參數author=john
,表示我們希望過濾出由 John 撰寫的文章。
為了更真實地介紹查詢參數,我們需要修改原先的「取得所有文章」API,加入簡單的「過濾」功能。
附帶一提,複雜的過濾功能,我們會在〈卷 23:過濾(Filtering)〉進行介紹。
修改後,當請求帶有查詢參數時,API 就能透過這些參數來限制查詢結果。如下:
@router.get(path='/posts/')
def get_posts(request: HttpRequest, title: None | str = None):
posts = Post.objects.all()
if title:
posts = posts.filter(title__icontains=title) # 實現過濾邏輯
return posts
這裡我們以「文章標題」來進行過濾。
小提醒:專案 API 目前還無法使用,一來 db 沒有資料,二來我們還沒有撰寫相關的 Schema。現階段僅作為閱讀理解上的參考。不過別擔心,我們很快會讓它 work ☺️
好,改完程式碼,接下來進行講解。
在 Django Ninja 中,處理查詢參數的最簡單方式,是直接將它們作為 view 函式的可選參數——透過參數預設值None
:
@router.get(path='/posts/')
def get_posts(request: HttpRequest, title: None | str = None):
在這個例子中,title
參數被定義為一個可選的字串(None | str = None
)。
title
查詢參數,Django Ninja 會自動將其值作為引數,並傳遞給get_posts
函式。title
在函式中的值將會是None
——因為它有預設值。關於這個例子,我們還需要留意以下這些地方:
router
裝飾器的path
參數路徑中。None
。如果缺少預設值,當查詢參數不存在時,Django Ninja 會返回 422 回應。None
時,需留意 type hints 的寫法:None | str = None
。(相當於Optional[str] = None
)str
——因為 URL 本質都是字串。以上寫法簡單直接,適用於大多數情況。
然而,當我們需要對查詢參數進行更複雜的驗證或限制時,就需要使用進階的技巧——Query
。
當我們需要進行更詳細的控制,例如限制查詢參數的長度、範圍,或為 API 文件加上額外資訊時,可以使用Query
來設定、處理查詢參數。
必須承認,我之前開發其實也很少用到Query
,但了解它 20% 最重要的特性,肯定會很有幫助。
Query
類別介紹透過Query
物件,我們可以對查詢參數進行更精細的定義與驗證。
事實上,如果你看過 Django Ninja 的原始碼,你會發現:它其實是一個函式。只不過會返回相同名稱的類別物件。為了解說方便,我們暫且當它是類別吧!
參考這個修改後的範例:
from ninja import Query, Router
...
@router.get(path='/posts/')
def get_posts(request: HttpRequest, title: None | str = Query(None)):
...
它和原來的這個寫法幾乎是等價的:(仍有細微不同,但可以先忽略)
def get_posts(request: HttpRequest, title: None | str = None):
你可能會覺得奇怪,那我沒事幹嘛要換一個更複雜的寫法,卻沒有額外的好處?
這當然是因為,更複雜的寫法,能做的事情也更多。
比如我們想要限制title
這個查詢字串,不可以太長也不可以太短。
假設要求長度在 2 到 10 個字元好了。
此時你就可以這樣寫:
def get_posts(
request: HttpRequest,
title: None | str = Query(None, min_length=2, max_length=10),
):
這個範例中,我們使用了Query
來定義title
查詢參數,並額外給予了min_length
和max_length
這兩個初始化Query
的參數設定。
這樣做可以確保title
查詢參數的長度在 2 到 10 個字元之間。
如果用戶輸入的title
不符合這個長度要求,如上一篇所述,Django Ninja 會自動返回一個狀態碼為 422 的回應,無需我們手動處理這些驗證邏輯與相關回應。
// 422 Unprocessable Entity
{
"detail": [
{
"type": "string_too_short", // 查詢參數過短
"loc": [
"title",
"title"
],
"msg": "String should have at least 2 characters",
"ctx": {
"min_length": 2
}
}
]
}
Query
的其他常用參數除了min_length
和max_length
,Query
還提供了許多實用的參數,供你限制查詢條件、為 API 文件補充額外資訊,常見的有:
gt
、ge
:查詢參數的值必須大於或大於等於某個數字。lt
、le
:查詢參數的值必須小於或小於等於某個數字。example
、examples
:為 API 文件提供查詢參數的範例值,讓用戶更容易理解參數用法。這部分我們就不示範了。
查詢參數是 RESTful API 常見且重要的組成部分。Django Ninja 中,我們可以透過簡單的方式來處理查詢參數,也可以使用Query
進行更高級的驗證和控制。
了解了 Django Ninja 如何處理 URL 的相關參數後,接下來則是重頭戲。
下一步,我們將探討如何在 Django Ninja 中處理 HTTP request body,介紹如何使用 Schema 來進行資料驗證與反序列化,讓我們能夠靈活地處理複雜的請求資訊。
本文同步發表於我的部落格——Code and Me
我注意到一個超級不重要的東西! 我好少見到 type hints 會把 None 寫再前面的耶 (title: None | str = None)
,雖然不影響運行結果,但就感覺很少見?(ruff 自動升級語法也是會寫在後面),有什特別考量嗎? 例如我記得這個順序也會影響 pydantic 判斷的順序?
也有可能是我看的太少了,畢竟用 | 來代替 Union是3.10以後的功能,而多數套件為了兼容舊版都是使用 Union 或 Optional,但我自己只用新版,用 | 和 None 不用額外 import 方便且個人認為也比較好閱讀!
哈哈哈,並沒有特別的考量,而且我也認同 str | None = None
比較好看、直觀
我後來有發現這樣寫比較醜!之所以沒有改,是因為我發現的時候,專案的程式碼已經 commit 並 push、merge 了,如果文章改了,這一篇的文章範例和程式碼內容會存在細微的不一致(而且不是省略內容那種),所以我一直都沒動