2024 iThome 鐵人賽2024 iThome 鐵人賽

上一篇我們討論了,請求 URL 中關於路徑參數的處理方式。

本文將介紹查詢參數(query parameters),這是 RESTful API 中用來傳遞過濾條件額外資訊的重要部分。

處理查詢參數在 Django Ninja 中非常簡單直觀,我們可以透過多種方式來達成。

本文所有的程式碼變動,可參考這個 PR


一、什麼是查詢參數?

查詢參數是 URL 中的可選參數,通常位於 path 的後方,以?key=value的形式出現,用來傳遞額外的資訊

例如,當我們需要過濾某位作者的文章時,URL 的 path 可能會這樣寫:

1
/posts/?author=john

URL 傳遞了一個查詢參數author=john,表示我們希望過濾出由 John 撰寫的文章。

二、範例專案改動

為了更真實地介紹查詢參數,我們需要修改原先的「取得所有文章」API,加入簡單的「過濾」功能。

附帶一提,複雜的過濾功能,我們會在〈卷 23:過濾(Filtering)〉進行介紹。

修改後,當請求帶有查詢參數時,API 就能透過這些參數來限制查詢結果。如下:

1
2
3
4
5
6
@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 中使用 Query Parameters

在 Django Ninja 中,處理查詢參數的最簡單方式,是直接將它們作為 view 函式的可選參數——透過參數預設值None

1
2
@router.get(path='/posts/')
def get_posts(request: HttpRequest, title: None | str = None):

在這個例子中,title參數被定義為一個可選的字串None | str = None)。

  • 如果 URL 中包含title查詢參數,Django Ninja 會自動將其值作為引數,並傳遞給get_posts函式。
  • 如果 URL 中沒有這個查詢參數,則函式不會收到引數,此時title在函式中的值將會是None——因為它有預設值

關於這個例子,我們還需要留意以下這些地方:

  1. 查詢參數不需要寫在router裝飾器的path參數路徑中。
  2. 查詢參數通常有預設值,無論是具體的值還是上述的None。如果缺少預設值,當查詢參數不存在時,Django Ninja 會返回 422 回應。
  3. 當預設值為None時,需留意 type hints 的寫法:None | str = None。(相當於Optional[str] = None
  4. 查詢參數和路徑參數一樣,都會依照函式的 type hints 進行型別轉換。如果沒有標記型別,那兩者的預設型別皆為str——因為 URL 本質都是字串

以上寫法簡單直接,適用於大多數情況。

然而,當我們需要對查詢參數進行更複雜的驗證或限制時,就需要使用進階的技巧——Query


四、使用 Query 物件

當我們需要進行更詳細的控制,例如限制查詢參數的長度、範圍,或為 API 文件加上額外資訊時,可以使用Query來設定、處理查詢參數。

必須承認,我之前開發其實也很少用到Query,但了解它 20% 最重要的特性,肯定會很有幫助。

Query類別介紹

透過Query物件,我們可以對查詢參數進行更精細的定義與驗證。

事實上,如果你看過 Django Ninja 的原始碼,你會發現:它其實是一個函式。只不過會返回相同名稱的類別物件。為了解說方便,我們暫且當它是類別吧!

參考這個修改後的範例:

1
2
3
4
5
6
from ninja import Query, Router
...

@router.get(path='/posts/')
def get_posts(request: HttpRequest, title: None | str = Query(None)):
...

它和原來的這個寫法幾乎是等價的:(仍有細微不同,但可以先忽略)

1
def get_posts(request: HttpRequest, title: None | str = None):

你可能會覺得奇怪,那我沒事幹嘛要換一個更複雜的寫法,卻沒有額外的好處?

這當然是因為,更複雜的寫法,能做的事情也更多。

限制查詢字串的長度

比如我們想要限制title這個查詢字串,不可以太長也不可以太短。

假設要求長度在 2 到 10 個字元好了。

此時你就可以這樣寫:

1
2
3
4
def get_posts(
request: HttpRequest,
title: None | str = Query(None, min_length=2, max_length=10),
):

這個範例中,我們使用了Query來定義title查詢參數,並額外給予了min_lengthmax_length這兩個初始化Query的參數設定。

這樣做可以確保title查詢參數的長度在 2 到 10 個字元之間。

如果用戶輸入的title不符合這個長度要求,如上一篇所述,Django Ninja 會自動返回一個狀態碼為 422 的回應,無需我們手動處理這些驗證邏輯與相關回應。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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_lengthmax_lengthQuery還提供了許多實用的參數,供你限制查詢條件、為 API 文件補充額外資訊,常見的有:

  • gtge:查詢參數的值必須大於或大於等於某個數字。
  • ltle:查詢參數的值必須小於或小於等於某個數字。
  • exampleexamples:為 API 文件提供查詢參數的範例值,讓用戶更容易理解參數用法。

這部分我們就不示範了。


小結與下一步

查詢參數是 RESTful API 常見且重要的組成部分。Django Ninja 中,我們可以透過簡單的方式來處理查詢參數,也可以使用Query進行更高級的驗證和控制。

了解了 Django Ninja 如何處理 URL 的相關參數後,接下來則是重頭戲

下一步,我們將探討如何在 Django Ninja 中處理 HTTP request body,介紹如何使用 Schema 來進行資料驗證與反序列化,讓我們能夠靈活地處理複雜的請求資訊。