Let's Django!Let's Django!

這是 Django Tutorial 系列連載的第 4 篇。

範例程式碼可參考我的 GitHub 專案


如〈Django Tutorial:系列介紹與導讀〉所言,這個系列主要是圍繞著「Django API」教學展開的。

但無論是用 Django 建立全端網站,還是開發 API,Django 的 HttpRequest——也就是我們熟悉的 request 參數——都是必不可少的元素。

HttpRequest 封裝了來自前端的 HTTP 請求,而 Django 會將 HttpRequest 物件自動帶入 view 函式的第一位置參數(通常就叫 request),讓我們可以直接使用。

為何需要了解 HttpRequest 物件

在我剛開始寫 Django 的時候,根本不太清楚 HttpRequest 具體有哪些屬性。

通常是看教學或為了實作某個功能而 Google 的時候,看到範例程式碼使用某個屬性,然後才知道這個屬性的存在。比如:

1
2
3
4
if request.method == "GET":
do_something()
elif request.method == "POST":
do_something_else()

看到這段,才知道 request 有一個 method 屬性。

這樣學當然可以,但如果一開始就對常用的 HttpRequest 屬性有基本了解,會讓你在學習 Django 的路上更加踏實


本文主旨與目標讀者

基於上述考量,我覺得還是有必要整理一下,對於 Django 初學者而言,最常用或需要了解的 HttpRequest 屬性。

不求多深,或知悉所有屬性的具體用法。面對 HttpRequest 這樣內容豐富的物件,我們掌握 80/20 法則,了解其中最重要的幾個,往往就能很有收獲。

至少,如果讓我重來,我會希望自己一開始就對這些屬性有一定認識。

目標讀者

本文的目標讀者,是 Django 初學者

無論想寫 Django 全端網站(也就是會用到 Django FormTemplate),還是開發 API。這些屬性都相對重要,或至少要知道它有何「替代品」。

因此,我還會適時補充,使用框架(DRF、Django Ninja)開發 API 時,這些屬性是否被框架提供的新屬性取代。

開始前的小提醒

本文對 HttpRequest 屬性與介紹內容,主要整理自官方文件我會適度引用原文,並加上我的經驗、看法。

提醒你:

  1. 看完之後,如果依舊覺得不熟悉這些屬性,那也無妨,有基本的認識就很好了!
  2. 所有的例示以 FBV(function-based view)為主,這是 Django 初學者最常接觸的 view 寫法。

以下依各個屬性在文件中出現的順序,一一介紹。

一、HttpRequest.method

A string representing the HTTP method used in the request. This is guaranteed to be uppercase.

表示請求所使用的 HTTP 方法,有兩個重點:

  1. 型別為字串。
  2. 而且字串值一定是全大寫。比如 "GET""POST" 等。

這或許是新手一開始最常呼叫的屬性XD。當然,前提是你寫的是 FBV(function-based view)。

在 view 函式中的用法前面已經提過:

1
2
3
4
if request.method == "GET":
do_something()
elif request.method == "POST":
do_something_else()

二、HttpRequest.GET

A dictionary-like object containing all given HTTP GET parameters. See the QueryDict documentation below.

用來獲得 HTTP request 的「URL 參數」內容,比如你透過 GET 方法存取下列網址:

1
https://www.books.com/search?category=novel&author=Asimov

這個屬性可取得 ? 後面的「category=novel&author=Asimov」這段資訊。

而且屬性值的資料型別,是一個 QueryDict

關於 QueryDict,你目前只需要知道,它是一個類似字典的物件,而主要是字串,或元素為字串的 Python list——如果一個 key 有多個值。

以下是用法:

1
2
3
4
def search_books(request):
category = request.GET.get('category')
author = request.GET.get('author')
...

不限於 GET 方法

雖然叫做 GET,但實際上這個屬性不限於 GET 方法,只要是 URL 參數,都可以透過這個屬性取得。

畢竟除了 GET 方法,還有 DELETE、PATCH 等方法,也都可以透過 URL 參數傳遞資訊。只是因為 GET 方法最常見,所以這個屬性叫做 GET

我覺得這個命名多少有點誤導性,但不算是大問題。

使用 DRF 框架

在 DRF(Django REST framework)的 request 中,官方推薦你使用 query_params 屬性代替 GET 屬性:

For clarity inside your code, we recommend using request.query_params instead of the Django’s standard request.GET.

如前所述,GET 屬性名稱有點誤導性,而 DRF 的 query_params 確實比較合理。

至於是不是要遵守這個建議,我覺得最大的重點是「整個專案的一致性」——不要有人寫 query_params、有人寫 GET

Django Ninja 則沒有這個問題(不需要用到 GET 屬性),以後介紹 Django Ninja 再詳談。


三、HttpRequest.POST

A dictionary-like object containing all given HTTP POST parameters, providing that the request contains form data.

See the QueryDict documentation below.

If you need to access raw or non-form data posted in the request, access this through the HttpRequest.body attribute instead.

POST does not include file-upload information. See FILES.

我故意把一段原文分 4 段引用,因為它有下列重點:

  1. 這個 POST 屬性,必須你的 HTTP 請求是 POST 方法。(對應第一段)
  2. 內容是 POST 請求中附帶的「表單資訊(form data)」。(對應第一段,這是最重要的一點!)
  3. 值的型別也是 QueryDict。如前所述,值必為字串。(對應第二段)
  4. 如果想取得 form data 以外的內容,要使用 HttpRequest.body。(對應第三段)
  5. 不包含上傳的檔案。因為已經有 FILES 屬性。(對應第四段)

只限 form data

簡單來說,這個屬性值對應 POST 請求中的 body,但 header 的 Content-Type 必須為 application/x-www-form-urlencoded 才行。

即 body 必須是 form data,否則 POST 屬性不會有值。

不得不說,這屬性名稱也一樣有點誤導,因為它只限於 form data。

Content-Typeapplication/json 或其他非表單的類型時,HttpRequest.POST 將會是空的,因為 Django 不會自動解析這些類型的資訊到 POST 屬性中。

只限 POST 方法

GET 屬性不同,POST 屬性「只限」使用 POST 方法,這點要特別注意。

可是,其餘帶有 body 的方法,比如 PUT、PATCH、DELETE,也可能有 form data,但 Django 也不會自動解析到 POST 屬性中。此時該怎麼辦?

答案是:使用 request.body 屬性,自行解析。

HttpRequest.body

上述的 request.POST 有一定的限制,而 body 屬性更加全面。它也是代表 HTTP 請求 body 資料,不限於 POST 方法,且不限於 form data。

不過,它的屬性值型別是 bytestring:

The raw HTTP request body as a bytestring.

你需要手動處理這個 bytestring,比如使用 json.loads() 解析 JSON body:

1
2
3
4
5
6
7
import json

def submit_data(request):
if request.method == 'POST':
body = request.body
request_data = json.loads(body)
...

有點麻煩

因此,實際上我們比較少用到 request.body,而是用 DRF 的 request.data 屬性,或 Django Ninja 的 Schema

它們會自動解析 body 資料,並轉換成 Python 物件。

程式碼實例

想像一個申請表單:

1
2
3
4
def submit_form(request):
if request.method == 'POST':
name = request.POST.get('name')
email = request.POST.get('email')

其中要注意的是,HTTP POST 方法才會有 request.POST 屬性值,所以一定要先檢查方法:if request.method == 'POST'

並且如上所述,即使是 POST 方法,如果請求 body 不是 form data,request.POST 依舊是空的。

實務使用建議

上述細節可能會新手有點眼花撩亂,不過別擔心,我們從一個更 high level 的角度看。

如果你寫的是「全端」Django,也就是包括了 Django Form 和模板,那這個 POST 屬性應該會很常用。因為前端(也就是 Django 模板)會傳來大量的 form data。

但如果你是寫 API,則這個屬性基本上不會用到。因為 API 的前端請求,往往是 JSON 格式,即 Content-Typeapplication/json,而不會是 form data。

簡言之:

  • 寫全端 Django 用 request.POST 屬性。
  • 寫 API 則是用別的屬性,比如 DRF 的 request.data

四、HttpRequest.FILES

A dictionary-like object containing all uploaded files. Each key in FILES is the name from the <input type="file" name="">. Each value in FILES is an UploadedFile.

上傳檔案必備!

注意,這個 FILES 只是一個類字典,字典的每一個 key 的才是你上傳的檔案。

FILES will only contain data if the request method was POST and the <form> that posted to the request had enctype="multipart/form-data". Otherwise, FILES will be a blank dictionary-like object.

重點:

  1. 必須使用 POST 方法。
  2. 而且 Content-Typemultipart/form-data

至於在 view 函式中的操作與使用,較為複雜(牽涉到 FileField),可自行參考文件,在此先略不介紹。


五、關於 Headers

HttpRequest.META

A dictionary containing all available HTTP headers.

HttpRequest.headers

A case insensitive, dict-like object that provides access to all HTTP-prefixed headers (plus Content-Length and Content-Type) from the request.

這部分我們已經有過專文介紹,可直接參考:

六、Attributes set by middleware

從 Django 的 middleware 整合而獲得的屬性,這些屬性的值「不屬於」HTTP 請求中的資料,而是 Django 基於方便開發,自動幫你加上去的。

比較重要且常用的是這兩個:

  • HttpRequest.session
  • HttpRequest.user

這些可以等用到相關 Django 模組時,再深入了解即可。