2024 iThome 鐵人賽2024 iThome 鐵人賽

這是 Django Ninja 系列教學的第 11 篇。

上一篇我們討論了,請求 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 的原始碼,你會發現:它其實是一個函式。只不過會返回相同名稱的類別物件

為了解說方便,我們統稱為Query物件。畢竟,在 Python 中,萬物皆物件

參考這個修改後的範例:

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 來進行資料驗證與反序列化,讓我們能夠靈活地處理複雜的請求資訊。