iT邦幫忙

2024 iThome 鐵人賽

DAY 11
2
Python

Django 忍法帖——Django Ninja 入門指南系列 第 11

卷 11:請求(三)查詢參數 - Query Parameters

  • 分享至 

  • xImage
  •  

上一篇我們討論了,請求 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 中使用 Query Parameters

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

@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 的原始碼,你會發現:它其實是一個函式。只不過會返回相同名稱的類別物件。為了解說方便,我們暫且當它是類別吧!

參考這個修改後的範例:

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_lengthmax_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_lengthmax_lengthQuery還提供了許多實用的參數,供你限制查詢條件、為 API 文件補充額外資訊,常見的有:

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

這部分我們就不示範了。


小結與下一步

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

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

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

本文同步發表於我的部落格——Code and Me


上一篇
卷 10:請求(二)路徑參數 - Path Parameters
下一篇
卷 12:請求(四)Request Body 與 Schema 介紹
系列文
Django 忍法帖——Django Ninja 入門指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
gbaian10
iT邦新手 5 級 ‧ 2024-09-24 03:26:30

我注意到一個超級不重要的東西! 我好少見到 type hints 會把 None 寫再前面的耶 (title: None | str = None),雖然不影響運行結果,但就感覺很少見?(ruff 自動升級語法也是會寫在後面),有什特別考量嗎? 例如我記得這個順序也會影響 pydantic 判斷的順序?

也有可能是我看的太少了,畢竟用 | 來代替 Union是3.10以後的功能,而多數套件為了兼容舊版都是使用 Union 或 Optional,但我自己只用新版,用 | 和 None 不用額外 import 方便且個人認為也比較好閱讀!

Kyo Huang iT邦新手 4 級 ‧ 2024-09-24 22:25:48 檢舉

哈哈哈,並沒有特別的考量,而且我也認同 str | None = None 比較好看、直觀

我後來有發現這樣寫比較醜!之所以沒有改,是因為我發現的時候,專案的程式碼已經 commit 並 push、merge 了,如果文章改了,這一篇的文章範例和程式碼內容會存在細微的不一致(而且不是省略內容那種),所以我一直都沒動

我要留言

立即登入留言