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 模組時,再深入了解即可。