一、request 請求?
?
先理解:Request
?是啥?
用戶訪問你的網站時,會發一個?“請求包”?📦 ,里面裝著:
- 想訪問啥路徑?用啥方法(GET/POST 等)?
- 帶了啥頭信息(比如 Cookies)?
- 有沒有傳表單、文件?
- 客戶端 IP 是啥?
- 用戶登錄狀態是啥?
Django 把這些信息封裝成?HttpRequest
?對象(一般叫?request
?),視圖函數里拿到它,就能 “拆包” 提取信息啦~
Request 請求的分類角度 🌐
這張圖是 “總綱”,告訴你可以從?三個維度?分析?request
?:
角度 | 關注啥內容? | 類比(超通俗) |
---|---|---|
HTTP 協議角度 | 請求行(方法、路徑)、請求頭、請求正文 | 快遞單上的 “地址、電話、包裹內容” |
TCP 協議角度 | 客戶端 IP(用戶的網絡地址) | 快遞單上的 “發件人 IP 地址” |
網站功能角度 | 用戶身份、Session(會話數據) | 快遞單上的 “收件人信息、專屬標記” |
?
HTTP 協議角度 👉 拆 “請求的核心內容”
代碼里的?echo
?視圖,把?HTTP 請求的關鍵部分?提取出來,返回給用戶看~
def echo(request: HttpRequest):# 用 f-string 拼接 HTML 內容,把 request 的信息嵌入進去html = f"""請求行 請求方法:{request.method} # 比如 GET/POST/PUT 等,告訴服務器“想干啥”請求路徑:{request.path} # 比如 /hello123 ,告訴服務器“訪問哪個頁面”查詢字符串:{request.GET} # 比如 ?name=beifan ,是 URL 里帶的參數請求頭 請求頭:{request.headers} # 裝著瀏覽器信息、Cookies 等,像“附加說明”請求正文 {request.body} # POST 請求時,表單、JSON 數據會存在這里"""return HttpResponse(html)
超通俗解釋:
- 你訪問網站時,瀏覽器會發一個 “超級詳細的快遞” 給服務器~
request.method
?是 “快遞單上的操作類型”(比如 “我要查詢”“我要寄件”)request.path
?是 “快遞單上的地址”(比如 “北京市朝陽區 xxx 路”)request.headers
?是 “快遞單上的備注”(比如 “ fragile 易碎”“需要冷藏”)request.body
?是 “包裹里的東西”(如果是 POST 請求,表單、文件會存在這里)
?
TCP 協議角度 👉 拿 “客戶端 IP”
代碼里的?echo
?視圖,專門提取?用戶的 IP 地址?:
def echo(request: HttpRequest):# 提取客戶端 IP ,這里處理了“代理轉發”的情況(很多網站會用反向代理,比如 Nginx)ip = request.META.get("HTTP_X_FORWARDED_FOR", request.META['REMOTE_ADDR'])html = f"""客戶端IP:{ip} # 用戶的網絡地址,比如 192.168.1.100"""return HttpResponse(html)
超通俗解釋:
- 每個設備上網都有 “網絡身份證”(IP 地址),服務器需要知道 “誰在訪問我”~
- 有時候用戶是通過 “代理服務器” 訪問的(比如公司網絡、CDN),
HTTP_X_FORWARDED_FOR
?就是 “真實身份證”,REMOTE_ADDR
?是 “代理的身份證”~ - 這段代碼會優先拿 “真實身份證”,拿不到就用 “代理的身份證”~
?
網站功能角度 👉 拿 “用戶身份 & Session”
代碼里的?echo
?視圖,提取?和用戶身份、會話相關的信息?:
def echo(request: HttpRequest):html = f"""當前用戶:{request.user} # 登錄后顯示用戶名,沒登錄可能是匿名用戶Session數據:{request.session.get("name", "beifan")} # 從 Session 里取數據,沒有就默認 "beifan""""return HttpResponse(html)
超通俗解釋:
request.user
:告訴你 “當前是誰在訪問”~ 登錄后是用戶名,沒登錄就是 “匿名用戶”(像 “快遞單上的收件人姓名”)request.session
:是服務器給用戶的 “專屬小本本”📒 ,用戶每次訪問都帶著,能存登錄狀態、偏好設置等~- 比如你登錄后,
session
?里存了?{"name": "beifan"}
?,下次訪問就能直接拿到!
- 比如你登錄后,
?
核心邏輯 🌟
所有代碼都是圍繞?“如何從?request
?里提取信息”?展開:
- 從?HTTP 協議角度:拆請求行、頭、正文 → 看 “請求的原始內容”
- 從?TCP 協議角度:拆客戶端 IP → 看 “誰在訪問”
- 從?網站功能角度:拆用戶身份、Session → 看 “用戶是誰,有啥專屬數據”
最終目的:讓你明白?request
?里 “藏著這么多有用的信息”,以后寫視圖時,想拿啥就從對應的地方取~
?
二、認識幾個格式轉換的函數?
json.dumps
是 Python 標準庫json
中的一個函數 ,它的主要作用是將 Python 對象(如字典、列表等)轉換為 JSON 格式的字符串。
import json# 示例1:轉換字典
data_dict = {"name": "Alice", "age": 25}
json_str = json.dumps(data_dict)
print(json_str)
# 輸出: {"name": "Alice", "age": 25}# 示例2:轉換列表
data_list = [1, 2, 3, "four"]
json_str = json.dumps(data_list)
print(json_str)
# 輸出: [1, 2, 3, "four"]
ensure_ascii
:默認為True
,在這種情況下,非 ASCII 字符會被轉義成 Unicode 編碼形式(如\uXXXX
)。如果設置為False
,則會以原始的非 ASCII 字符形式輸出。
data = {"名字": "張三"}
print(json.dumps(data))
# 輸出: {"\u540d\u5b57": "\u5f20\u4e09"}print(json.dumps(data, ensure_ascii=False))
# 輸出: {"名字": "張三"}
indent
:用于設置縮進,使輸出的 JSON 字符串更具可讀性,常用于調試或格式化輸出。它可以是一個整數,表示縮進的空格數,也可以是字符串(如\t
表示制表符)。
data = {"name": "Bob", "hobbies": ["reading", "swimming"]}
print(json.dumps(data, indent=4))
# 輸出:
# {
# "name": "Bob",
# "hobbies": [
# "reading",
# "swimming"
# ]
# }
sort_keys
:默認為False
,如果設置為True
,在轉換字典時,會按照鍵的字母順序對鍵值對進行排序后輸出。
data = {"c": 3, "a": 1, "b": 2}
print(json.dumps(data))
# 輸出: {"c": 3, "a": 1, "b": 2}print(json.dumps(data, sort_keys=True))
# 輸出: {"a": 1, "b": 2, "c": 3}
?
json.loads
?是 Python 的?json
?模塊里的一個函數,作用是?把 JSON 格式的字符串,轉換成 Python 能直接用的數據類型(比如字典、列表)
?
data = json.loads(request.body)
request.body
:Django 里?request
?對象的?body
?屬性,存的是?客戶端發過來的原始數據(二進制字符串),比如客戶端 POST 一個 JSON 數據?{"c": 3}
,request.body
?拿到的是?b'{"c": 3}'
(二進制格式的 JSON 字符串 )。json.loads(...)
:把這個二進制字符串(轉成普通字符串后),解析成 Python 的字典?{"c": 3}
,這樣?data
?就可以像普通字典一樣用(比如?data['c']
?取到?3
?)。
?
json.loads
?的核心作用:“JSON → Python”
import json# 這是一個 JSON 格式的字符串(字符串里的內容符合 JSON 語法)
json_str = '{"c": 3}'# 用 json.loads 解析,轉成 Python 字典
python_dict = json.loads(json_str)print(python_dict) # 輸出: {'c': 3}
print(type(python_dict)) # 輸出: <class 'dict'>
- 輸入:JSON 格式的字符串(必須用雙引號,符合 JSON 規范 )。
- 輸出:Python 的字典(或列表、數字等,取決于 JSON 內容 )。
?
三、為什么需要?json.loads
?
因為客戶端和服務器通信時,只能傳 “字符串”(HTTP 協議的限制 )。
如果客戶端要傳復雜數據(比如字典、列表),必須先轉成 JSON 字符串(json.dumps
?干的事 ),服務器收到后,再用?json.loads
?轉成 Python 能處理的數據類型。
就像你給外國朋友寫信,得用 “國際通用語言(JSON 字符串)”,朋友收到后,用?json.loads
?翻譯成自己能懂的語言(Python 字典 )。
?
和?json.dumps
?的關系(“反向操作”)
json.dumps
:把 Python 數據(字典、列表等)轉成 JSON 字符串(Python → JSON
?)。json.loads
:把 JSON 字符串轉成 Python 數據(JSON → Python
?)。
這倆是 “一對”,經常一起用在前后端數據交互場景:
- 前端用?
JSON.stringify
(類似?json.dumps
?)把數據轉成 JSON 字符串,發給后端。 - 后端用?
json.loads
?把 JSON 字符串轉成 Python 數據,處理完后,再用?json.dumps
?轉回 JSON 字符串,返回給前端。
?
三、認識Querydict類型?
QueryDict
?本質是 Python 對象,但它又和普通?dict
?不太一樣,這得從 Django 的設計邏輯說起
?
? 先明確:所有能在 Python 里操作的東西,都是 “Python 對象”
Python 里幾乎一切皆對象:
- 普通字典?
{"a": 1}
?是對象(dict
?類型) - 字符串?
'abc'
?是對象(str
?類型) - 甚至數字?
123
?也是對象(int
?類型)
所以?QueryDict
?作為 Django 定義的自定義類,自然也是 Python 對象,只不過它是 Django 為了處理 HTTP 請求參數,專門設計的 “特殊字典”~
?
? QueryDict
?是 “特殊定制的 Python 對象”
Django 里的?QueryDict
?繼承自 Python 的?dict
,但增強了處理 HTTP 請求參數的能力,核心是為了應對兩個場景:
1. 支持 “一個鍵對應多個值”(HTTP 查詢參數的特性)
HTTP 的查詢參數(如??a=1&a=2
)允許同一個鍵(a
)出現多次,普通?dict
?會直接覆蓋值(只能存?{'a': 2}
?),但?QueryDict
?可以存成列表({'a': ['1', '2']}
?)。
比如你的請求 URL 是??a=1&a=2
,Django 會把參數解析成:
request.GET # QueryDict: {'a': ['1', '2']}
?
這樣就能正確處理 “同一個鍵傳多個值” 的場景(比如復選框多選)。
?
2. 提供更貼合 HTTP 請求的方法
QueryDict
?還加了很多實用方法,比如:
getlist('a')
:直接獲取鍵?a
?對應的所有值(返回?['1', '2']
?)urlencode()
:把?QueryDict
?轉成 URL 編碼的字符串(a=1&a=2
?)
這些方法讓處理 HTTP 請求參數更方便,是普通?dict
?做不到的~
?
四、為什么字典是單引號 json是雙引號?
這個問題涉及 Python 語法和 JSON 規范的差異,本質是兩種 “數據格式” 的設計約定,咱們用 “方言 vs 國際語” 的思路理解~ 🗣?
Python 字典用單引號:語法靈活性
Python 里的字典,鍵值對的字符串可以用?單引號?或?雙引號,甚至混用,這是 Python 語法的靈活性:
# 合法的 Python 字典
my_dict = {'name': "beifan", # 單引號、雙引號混用"age": 18
}
Python 這么設計,是為了讓開發者寫代碼時更自由(比如字符串里本身有雙引號,就可以用單引號包起來,避免轉義 )。
?
JSON 用雙引號:國際標準規范
JSON 是?“跨語言的數據交換格式”,它的語法有嚴格規范,其中一條就是:字符串必須用雙引號包裹。
這是因為 JSON 的設計目標是 “讓所有語言都能統一解析”,而不同語言對字符串引號的支持不同:
- 比如 JavaScript 里,對象的鍵必須用雙引號(
{"name": "beifan"}
?是合法的,{'name': 'beifan'}
?會報錯 )。 - 為了兼容所有語言,JSON 強制規定用雙引號,這樣不管是 Python、JavaScript、Java 還是其他語言,都能一致解析。
?
json.dumps
?的作用:自動轉換引號
當你用?json.dumps
?把 Python 字典轉成 JSON 字符串時,Python 會自動做兩件事:
- 把字典里的單引號,替換成雙引號(符合 JSON 規范)。
- 處理其他 Python 特有的語法(比如?
None
?轉成?null
,True
?轉成?true
?)。
my_dict = {'name': 'beifan'}
json_str = json.dumps(my_dict)
# 輸出: '{"name": "beifan"}'(雙引號)
?
總結:兩種格式的設計目標不同
- Python 字典:是 Python 語言內部使用的數據結構,語法靈活,方便開發者寫代碼。
- JSON:是跨語言的 “數據交換協議”,語法嚴格,確保所有語言都能統一解析。
所以 Python 字典用單引號(或雙引號)都行,但轉成 JSON 后必須用雙引號 —— 這是為了讓其他語言能看懂,實現 “跨語言交流”~
簡單說:單引號是 Python 的 “方言”,雙引號是 JSON 的 “國際語”,json.dumps
?就是 “翻譯官”,把 Python 方言翻譯成國際通用的 JSON 語~ 😊
?
五、測試用例實操?
項目結構
Django 服務端(views.py
):
寫了 3 個視圖函數(echo
/submit
/result
),負責接收請求、處理數據、返回響應
測試客戶端(test_api.py
):
用?requests
?庫模擬客戶端,給 Django 服務發?POST 請求(帶 JSON 數據)
驗證服務端返回的響應是否符合預期
?
🐇 echo
?視圖函數
def echo(request: HttpRequest):# 將 request.GET(QueryDict 類型)轉成 JSON 字符串返回html = f"{json.dumps(request.GET)}" return HttpResponse(html)
(request:HttpRequest)類型注釋:別人看到?request: HttpRequest
,不用猜就知道:這個參數是 Django 封裝的 “請求對象”,里面有?method
、body
?這些屬性
request.GET
:Django 中專門用來接收?URL 查詢參數(如??a=1
)的對象,類型是?QueryDict
- 作用:訪問該視圖時,會把 URL 里的查詢參數轉成 JSON 字符串返回給客戶端。(JSON 是前后端都能 “看懂” 的 “通用語言”,把數據轉成 JSON 再返回,能讓客戶端(瀏覽器、App 等)更方便地處理數據~)
?
在這個?echo
?視圖函數中,HttpResponse(html)
?會將經過處理后的?html
(這里是包含 URL 查詢參數的 JSON 字符串 )作為響應體,加上默認的響應頭(如?Content-Type: text/html; charset=utf-8
?)和狀態碼 200,一起返回給客戶端。
客戶端(通常是瀏覽器)接收到這個 HTTP 響應后,會根據響應頭中的?Content-Type
?來決定如何處理響應體內容。如果是?text/html
,就會渲染展示 HTML 頁面;如果是?application/json
,則可能會將其解析為 JavaScript 對象進行后續操作。
HttpResponse(html)
?是 Django 視圖函數將處理結果返回給客戶端的關鍵步驟,它按照 HTTP 協議的規范,將數據包裝成包含狀態行、響應頭和響應體的完整 HTTP 響應,從而實現服務器與客戶端之間的數據交互和信息傳遞。
?
🐇 submit
?視圖函數
def submit(request):print(request.body) # 打印請求體(一般 POST 請求會用到,這里可能無實際數據)# 讀取本地 submit.html 文件內容并返回html = open('submit.html', encoding="utf-8").read() return HttpResponse(html)
- 作用:訪問該視圖時,返回?
submit.html
?的內容,常用來展示前端頁面。
🐇 test_api.py
?測試邏輯
import json
import requests # 發送 GET 請求(帶查詢參數 ?a=1)
resp = requests.get("http://127.0.0.1:8000/beifan/echo?a=1")
print(resp.status_code) # 打印響應狀態碼(如 200 表示成功)
print(resp.text) # 打印響應內容(echo 視圖返回的 JSON 字符串)# 斷言:期望響應內容是 {"a": 1} 的 JSON 字符串
assert resp.text == json.dumps({"a": "1"})
?
為什么表單里面沒有數據類型({"a":"1"}而不是{"a":1}) 都按照字符串進行處理??
表單是 “傳遞文本的載體”,類型由后端決定
表單的作用就像 “快遞單”:前端只負責把用戶輸入的內容以 “字符串” 形式傳給服務器,不關心內容的實際類型。而服務器(后端)拿到字符串后,再根據業務場景轉成需要的類型(數字、布爾值、日期等)。
這種設計看似 “粗糙”,實則是 Web 開發中 “兼容性” 和 “靈活性” 的平衡 —— 既保證了所有系統都能互通,又讓開發者能按需處理數據~
想解決?QueryDict
?轉字典后,值帶列表中括號?[ ]
?的問題!
比如想把?{"b": ["2"]}
?變成?{"b": "2"}
,這確實是 Django 處理表單數據時的常見需求,咱們一步步講清楚怎么實現~
?
先明白為什么會有中括號?[ ]
?
request.POST
?是?QueryDict
?類型,它的設計是?“一個鍵可以對應多個值”(比如表單里多個同名的復選框),所以即使只有一個值,也會存成列表形式(["2"]
)。
- 表單提交?
b=2
,request.POST
?實際是?QueryDict({'b': ['2']})
- 直接轉字典會變成?
{'b': ['2']}
,JSON 序列化后就是?{"b": ["2"]}
?
- 前端表單里的字段(比如輸入框、單選框)只會傳一個值(比如?
b=2
?不可能同時傳?b=2&b=3
)。 - 你明確知道 “每個鍵只有一個值”,列表里的其他元素不存在(或者不需要)。
這時候,取列表的第一個元素?v[0]
?是安全的,比如:
# 原代碼:v 是列表 ['2'],存成 data[k] = v → 結果 {'b': ['2']}
# 改后代碼:取 v[0] → 結果 {'b': '2'}
for k, v in request.POST.items():data[k] = v[0] # 只取列表第一個元素,自然去掉了中括號
?
六、基于 Django Session 的 “訪問計數” 功能?
整體流程:“客戶端請求 → 服務端計數 → 測試驗證”
- Django 服務端:
echo
?視圖通過?request.session
?記錄用戶的 “訪問次數”,每次訪問計數 +1,再返回當前計數。 - 測試客戶端:用?
requests.Session()
?模擬同一個客戶端,多次發送 POST 請求到?echo
?視圖,驗證每次返回的計數是否符合預期(第一次?1
、第二次?2
、第三次?3
…)。
?
Django 服務端:echo
?視圖(核心計數邏輯)
from django.http import HttpRequest, HttpResponse
import jsondef echo(request: HttpRequest):# 1. 從 Session 中獲取 'num',默認值為 0(第一次訪問時,Session 中無 'num')num = request.session.get("num", 0) # 2. 計數 +1(每次訪問,計數自增)num = num + 1 # 3. 把新的計數存回 Session(下次訪問時,能拿到更新后的值)request.session['num'] = num # 4. 轉成 JSON 字符串,返回給客戶端html = f"{json.dumps({'num': num})}" return HttpResponse(html)
num
?在這里是一個?“計數器變量”,專門用來記錄?“同一個用戶訪問當前視圖的次數”
?
??request.session
:用戶的 “專屬儲物柜”
request.session
?是 Django 為每個訪問網站的用戶(客戶端)準備的?“專屬儲物柜”,用來存這個用戶的臨時數據(比如登錄狀態、訪問次數等)。
- 每個用戶的?
session
?是獨立的(就像每個儲物柜有不同的鑰匙),A 用戶的?session
?里的數據,B 用戶看不到。 - 這個 “儲物柜” 里的數據以?鍵值對?形式存儲(類似字典),比如可以存?
{"num": 3, "username": "張三"}
。
?
??get("num", 0)
:安全地取數據
這是字典(或類字典對象)的常用方法,作用是:
- 嘗試從?
session
?里找?鍵為?num
?的值(比如之前存過?num=2
,就會取到?2
)。 - 如果找不到這個鍵(比如用戶第一次訪問,
session
?里還沒有?num
),就返回?默認值?0
。
?
? 訪問計數
假設這是用戶第一次訪問網站:
request.session
?里還沒有?num
?這個鍵,所以?request.session.get("num", 0)
?會返回?0
?→?num = 0
。
用戶第二次訪問時:
- 因為第一次訪問后,代碼里已經把?
num
?存回了?session
(比如?request.session['num'] = 1
),所以這次會取到?1
?→?num = 1
。
以此類推,每次訪問都會拿到上一次存的計數,實現 “累加” 效果。
測試客戶端:requests.Session()
?模擬用戶請求
測試代碼用?requests
?庫模擬客戶端,重點是用?requests.Session()
?保持 Session(Cookie),讓服務端認為是 “同一個用戶的多次請求”。
import json
import requests# 1. 創建 Session 對象(自動管理 Cookie,模擬同一個客戶端)
session = requests.Session() # 2. 第一次請求:模擬客戶端訪問 echo 視圖
resp = session.post("http://127.0.0.1:8000/beifan/echo?a=1",cookies={"d": "4"} # 可選:手動設置 Cookie(實際 Django 會自動處理 Session Cookie)
)
print(resp.status_code) # 打印狀態碼(預期 200)
print(resp.text) # 打印響應內容(預期 {"num": 1})
# 驗證:第一次訪問,計數應為 1
assert resp.text == json.dumps({"num": 1}) # 3. 第二次請求:同一個 Session,再次訪問 echo 視圖
resp = session.post("http://127.0.0.1:8000/beifan/echo?a=1",cookies={"d": "4"}
)
print(resp.status_code)
print(resp.text) # 預期 {"num": 2}
# 驗證:第二次訪問,計數應為 2
assert resp.text == json.dumps({"num": 2}) # 4. 第三次請求:繼續驗證計數
resp = session.post("http://127.0.0.1:8000/beifan/echo?a=1",cookies={"d": "4"}
)
print(resp.status_code)
print(resp.text) # 預期 {"num": 3}
# 驗證:第三次訪問,計數應為 3
assert resp.text == json.dumps({"num": 3})
?
客戶端代碼里的?session.post("http://127.0.0.1:8000/beifan/echo?a=1")
,通過?URL 路徑?/beifan/echo
,借助 Django 的路由系統,精準 “命中” 了?echo
?視圖。
?
關鍵邏輯:requests.Session()
?保持 Session
為什么用?
requests.Session()
?requests.Session()
?會自動保存服務器返回的 Cookie,并在后續請求中自動帶上這些 Cookie。這樣,服務端通過 Cookie 識別到 “同一個用戶”,request.session
?就能正確關聯到之前的會話,計數才會持續 +1。如果不用?
Session()
:
每次用?requests.post()
?發請求,都是 “新的客戶端”,服務端會認為是不同用戶,num
?每次都會從?0
?開始(第一次請求?num=1
,第二次請求又會從?0
?開始,導致計數錯誤)。
?
完整執行流程(以第一次、第二次請求為例)
第一次請求(客戶端→服務端→客戶端)
- 測試代碼執行?
session.post(...)
,發送第一次請求。 - 服務端?
echo
?視圖:request.session.get("num", 0)
?→ 取到?0
(第一次訪問,Session 中無?num
)。num = 0 + 1 = 1
?→ 存回?session["num"] = 1
。- 返回?
{"num": 1}
。
- 測試客戶端收到響應,
assert
?驗證?resp.text == '{"num": 1}'
?→ 通過。
第二次請求(同一個客戶端再次訪問)
- 測試代碼執行?
session.post(...)
,Session 對象自動帶上第一次請求的 Cookie。 - 服務端?
echo
?視圖:request.session.get("num", 0)
?→ 取到?1
(Session 中已存?num=1
)。num = 1 + 1 = 2
?→ 存回?session["num"] = 2
。- 返回?
{"num": 2}
。
- 測試客戶端收到響應,
assert
?驗證?resp.text == '{"num": 2}'
?→ 通過。
?
代碼的核心目的
- 服務端:通過?
Session
?實現 “用戶訪問計數”,每次訪問計數 +1,體現?“狀態保持”(知道是同一個用戶的多次請求)。 - 測試端:用?
requests.Session()
?模擬同一個客戶端,驗證每次訪問的計數是否正確(第一次?1
、第二次?2
、第三次?3
…),體現?“接口自動化測試”。