一文讀懂django-restframeword(DRF)
轉(zhuǎn)載請(qǐng)注明 來(lái)源:http://www.eword.name/
Author:eword
Email:eword@eword.name
django-restframeword(DRF)解讀
- django-restframeword(DRF)解讀
- 一、面相對(duì)象編程中的類(lèi)繼承和反射基礎(chǔ)
- 二、CBV和FBV的區(qū)別
- 三、View源碼解析
- 四、APIView源碼解析
- 五、序列化與反序列化
- 六、GenericAPIView源碼解析
- 七、Mixin工具類(lèi)源碼解析
- 八、ViewSet源碼解析
- 九、路由組件
- 十、過(guò)濾、查詢(xún)、排序與分頁(yè)
- 十一、限流
Throttling - 十二、認(rèn)證
- 十三、權(quán)限
- 附錄、D4Dic數(shù)據(jù)模型
- [附錄、settings設(shè)置指南](#附錄、settings設(shè)置指南-http-www-noobyard-comarticlep-uhubgbve-cu-html)
- 附錄、
Simple JWT的默認(rèn)設(shè)置
一、面相對(duì)象編程中的類(lèi)繼承和反射基礎(chǔ)
要讀懂DRF原理,學(xué)通
類(lèi)繼承和反射原理很重要。
1.1、類(lèi)繼承
- 在Python中,類(lèi)的繼承是面向?qū)ο缶幊痰囊粋€(gè)核心概念。通過(guò)繼承,一個(gè)類(lèi)(稱(chēng)為子類(lèi)或派生類(lèi))可以獲取另一個(gè)類(lèi)(稱(chēng)為父類(lèi)或基類(lèi))的屬性和方法。這使得代碼的重用和擴(kuò)展變得容易。
- Python支持多重繼承,這意味著一個(gè)類(lèi)可以繼承自多個(gè)父類(lèi)。繼承關(guān)系通過(guò)類(lèi)定義時(shí)使用的class關(guān)鍵字和括號(hào)內(nèi)的基類(lèi)名來(lái)建立。
# 定義一個(gè)基類(lèi)(父類(lèi))
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} makes a sound")
# 定義一個(gè)子類(lèi),繼承自Animal
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 調(diào)用父類(lèi)的__init__方法
self.breed = breed
def bark(self):
print(f"{self.name} barks!")
# 創(chuàng)建一個(gè)Dog類(lèi)的實(shí)例
my_dog = Dog("Buddy", "Labrador")
# 調(diào)用繼承自Animal的方法
my_dog.speak() # 輸出: Buddy makes a sound
# 調(diào)用Dog類(lèi)特有的方法
my_dog.bark() # 輸出: Buddy barks!
多重繼承
# 定義第一個(gè)父類(lèi)
class FlyingAnimal:
def fly(self):
print("I can fly!")
# 定義第二個(gè)父類(lèi)
class Mammal:
def give_birth(self):
print("I give birth to live young.")
# 定義子類(lèi),繼承自FlyingAnimal和Mammal
class Bat(FlyingAnimal, Mammal):
pass
# 創(chuàng)建一個(gè)Bat類(lèi)的實(shí)例
my_bat = Bat()
# 調(diào)用繼承自FlyingAnimal的方法
my_bat.fly() # 輸出: I can fly!
# 調(diào)用繼承自Mammal的方法
my_bat.give_birth() # 輸出: I give birth to live young.
1.2、反射
在Python中,反射(Reflection)是一種能力,它允許程序在運(yùn)行時(shí)檢查對(duì)象、模塊、函數(shù)、類(lèi)等的信息,并且可以動(dòng)態(tài)地調(diào)用其方法和屬性。Python的內(nèi)置函數(shù)和模塊提供了實(shí)現(xiàn)反射功能所需的工具。反射在Python中通常涉及到以下幾個(gè)內(nèi)置函數(shù):
- dir():返回一個(gè)對(duì)象的屬性列表,包括方法、變量等。
- getattr():獲取對(duì)象屬性的值。
- setattr():設(shè)置對(duì)象屬性的值。
- hasattr():檢查對(duì)象是否具有某個(gè)屬性。
- callable():檢查對(duì)象是否可以被調(diào)用(例如函數(shù)或方法)。
1.2.1、使用舉例
使用 dir() 查看對(duì)象的屬性
class MyClass:
def my_method(self):
print("This is a method.")
my_variable = "This is a variable."
obj = MyClass()
print(dir(obj)) # 列出對(duì)象的所有屬性
使用 getattr() 獲取屬性的值
value = getattr(obj, 'my_variable') # 獲取my_variable屬性的值
print(value) # 輸出: This is a variable.
使用 setattr() 設(shè)置屬性的值
setattr(obj, 'my_variable', 'New value') # 設(shè)置my_variable屬性的新值
print(getattr(obj, 'my_variable')) # 輸出: New value
使用 hasattr() 檢查屬性是否存在
exists = hasattr(obj, 'my_method') # 檢查my_method方法是否存在
print(exists) # 輸出: True
使用 callable() 檢查對(duì)象是否可調(diào)用
is_callable = callable(obj.my_method) # 檢查my_method方法是否可以被調(diào)用
print(is_callable) # 輸出: True
1.2.2、通過(guò)反射創(chuàng)建對(duì)象
在Python中,你可以使用反射來(lái)動(dòng)態(tài)地創(chuàng)建對(duì)象。
- 使用globals()或locals()函數(shù)來(lái)獲取當(dāng)前全局或局部命名空間。
- 使用getattr()來(lái)獲取類(lèi)或函數(shù)的引用。
- 調(diào)用該類(lèi)或函數(shù)來(lái)創(chuàng)建對(duì)象。
ps: 更常見(jiàn)的方法是直接使用globals()或locals()結(jié)合類(lèi)名或函數(shù)名來(lái)動(dòng)態(tài)地獲取類(lèi)或函數(shù),并調(diào)用它們。
class MyClass:
def __init__(self, name):
self.name = name
def say_hello(self):
print(f"Hello, my name is {self.name}!")
# 動(dòng)態(tài)創(chuàng)建對(duì)象
class_name = "MyClass" # 類(lèi)名作為字符串
obj = globals()[class_name](name="Dynamic Object") # 使用globals()獲取類(lèi)引用并創(chuàng)建對(duì)象
# 調(diào)用對(duì)象的方法
obj.say_hello() # 輸出: Hello, my name is Dynamic Object!
在這個(gè)例子中,
- 首先定義了一個(gè)名為MyClass的類(lèi)。
- 然后,將類(lèi)名作為字符串存儲(chǔ)在變量class_name中。
- 接著,使用globals()函數(shù)來(lái)獲取當(dāng)前全局命名空間,并使用類(lèi)名字符串作為鍵來(lái)獲取類(lèi)的引用。
- 最后,我們調(diào)用這個(gè)類(lèi)來(lái)創(chuàng)建一個(gè)新的對(duì)象obj,并傳遞所需的參數(shù)。
如果是在一個(gè)函數(shù)或方法內(nèi)部,并且類(lèi)是在那個(gè)局部作用域中定義的,可以使用locals()代替globals()。但是,通常情況下,類(lèi)定義在模塊級(jí)別,所以globals()更為常用。
如果你的類(lèi)定義在另一個(gè)模塊中,需要先導(dǎo)入那個(gè)模塊,然后從模塊的命名空間中使用反射來(lái)獲取類(lèi)的引用。
import my_module # 假設(shè)my_module中包含MyClass的定義
class_name = "MyClass"
obj = my_module.__dict__[class_name](name="Dynamic Object from Module")
obj.say_hello()
1.2.3、通過(guò)反射實(shí)現(xiàn)事件注冊(cè)機(jī)制
1.2.3.1、父類(lèi)調(diào)用子類(lèi)函數(shù)
實(shí)現(xiàn)事件注冊(cè)機(jī)制前,要先了解如何通過(guò)父類(lèi)調(diào)用子類(lèi)的函數(shù)或?qū)傩浴?/p>
- 在父類(lèi)中定義一個(gè)方法,該方法使用反射來(lái)調(diào)用子類(lèi)的方法。
- 在子類(lèi)中實(shí)現(xiàn)這個(gè)方法。
- 當(dāng)父類(lèi)需要調(diào)用這個(gè)方法時(shí),它使用反射來(lái)找到并調(diào)用子類(lèi)中的實(shí)現(xiàn)。
這通常是通過(guò)使用getattr()函數(shù)來(lái)實(shí)現(xiàn)的,該函數(shù)可以從一個(gè)對(duì)象中獲取一個(gè)屬性的值,如果該屬性是一個(gè)方法,則可以調(diào)用它。
class Parent:
def callback_subclass_method(self):
# 使用反射調(diào)用子類(lèi)的方法
method_name = "subclass_specific_method"
if hasattr(self, method_name):
method = getattr(self, method_name)
method()
else:
print("No subclass-specific method found.")
class Child(Parent):
def subclass_specific_method(self):
print("This is a method specific to the subclass.")
# 創(chuàng)建子類(lèi)實(shí)例并調(diào)用父類(lèi)方法
child_instance = Child()
child_instance.callback_subclass_method()
在這個(gè)例子中,Parent類(lèi)有一個(gè)callback_subclass_method方法,它試圖調(diào)用一個(gè)名為subclass_specific_method的方法。這個(gè)方法在Child類(lèi)中被定義。當(dāng)Child類(lèi)的一個(gè)實(shí)例調(diào)用callback_subclass_method時(shí),它實(shí)際上會(huì)調(diào)用Child類(lèi)中定義的subclass_specific_method。
注意:這個(gè)例子假設(shè)子類(lèi)確實(shí)有一個(gè)名為subclass_specific_method的方法。在實(shí)際應(yīng)用中,可能需要更復(fù)雜的邏輯來(lái)處理不同子類(lèi)可能有不同方法的情況。
此外,如果設(shè)計(jì)允許子類(lèi)有不同的方法名稱(chēng)或簽名,可能需要使用更高級(jí)的反射技術(shù),或者重新考慮設(shè)計(jì),以便父類(lèi)可以使用更通用的接口來(lái)與子類(lèi)交互。這通常可以通過(guò)接口、抽象基類(lèi)或協(xié)議來(lái)實(shí)現(xiàn)。
最后,過(guò)度使用反射可能會(huì)使代碼難以理解和維護(hù),因?yàn)樗试S在運(yùn)行時(shí)動(dòng)態(tài)地改變行為。因此,在使用反射時(shí)應(yīng)該謹(jǐn)慎,并確保代碼的可讀性和可維護(hù)性。
1.2.3.1、事件注冊(cè)機(jī)制
在Python中,通過(guò)反射實(shí)現(xiàn)事件注冊(cè)機(jī)制可以涉及到創(chuàng)建一個(gè)事件分發(fā)器,它允許不同的對(duì)象(通常是類(lèi)的實(shí)例)注冊(cè)和觸發(fā)事件。每個(gè)對(duì)象可以定義自己的事件處理函數(shù),并在事件發(fā)生時(shí)通過(guò)反射機(jī)制被調(diào)用。
class EventDispatcher:
def __init__(self):
self.event_handlers = {}
def register_event(self, event_name, handler):
"""注冊(cè)事件處理函數(shù)"""
if event_name not in self.event_handlers:
self.event_handlers[event_name] = []
self.event_handlers[event_name].append(handler)
def trigger_event(self, event_name, *args, **kwargs):
"""觸發(fā)事件并調(diào)用所有注冊(cè)的處理函數(shù)"""
if event_name in self.event_handlers:
for handler in self.event_handlers[event_name]:
handler(*args, **kwargs)
class Listener:
def on_event(self, *args, **kwargs):
"""事件處理函數(shù)"""
print(f"Event received: {args}, {kwargs}")
# 創(chuàng)建事件分發(fā)器和監(jiān)聽(tīng)器
dispatcher = EventDispatcher()
listener = Listener()
# 注冊(cè)事件處理函數(shù)
dispatcher.register_event("my_event", listener.on_event)
# 觸發(fā)事件
dispatcher.trigger_event("my_event", "Hello", x=10)
在這個(gè)例子中:
- EventDispatcher類(lèi)維護(hù)了一個(gè)字典event_handlers,它映射事件名稱(chēng)到處理函數(shù)列表。
- register_event方法允許其他對(duì)象將其事件處理函數(shù)注冊(cè)到特定的事件上。
- trigger_event方法則負(fù)責(zé)調(diào)用所有注冊(cè)到特定事件的處理函數(shù)。
Listener類(lèi)有一個(gè)on_event方法,它是事件處理函數(shù)。當(dāng)事件被觸發(fā)時(shí),這個(gè)方法將被調(diào)用。
然后,創(chuàng)建了一個(gè)EventDispatcher實(shí)例和一個(gè)Listener實(shí)例,將Listener的on_event方法注冊(cè)到名為"my_event"的事件上,并觸發(fā)這個(gè)事件。當(dāng)事件被觸發(fā)時(shí),所有注冊(cè)到該事件的處理函數(shù)(在這個(gè)例子中是on_event方法)都會(huì)被調(diào)用。
這個(gè)例子并沒(méi)有直接使用getattr或setattr等反射函數(shù),因?yàn)榉瓷渫ǔS糜趧?dòng)態(tài)地獲取或設(shè)置對(duì)象的屬性或方法。
在這個(gè)例子中,事件處理函數(shù)的注冊(cè)和觸發(fā)是顯式進(jìn)行的,而不是通過(guò)字符串名稱(chēng)動(dòng)態(tài)查找的。
可以擴(kuò)展這個(gè)機(jī)制,允許通過(guò)字符串名稱(chēng)來(lái)注冊(cè)和觸發(fā)事件,這樣就涉及到了反射的使用。
如果需要通過(guò)字符串名稱(chēng)來(lái)動(dòng)態(tài)地注冊(cè)和觸發(fā)事件,可以修改register_event和trigger_event方法,讓它們接受事件名稱(chēng)作為字符串,并使用getattr來(lái)動(dòng)態(tài)地獲取和處理事件處理函數(shù)。但是請(qǐng)注意,這會(huì)增加代碼的復(fù)雜性,并可能引入更多的錯(cuò)誤和安全問(wèn)題,因此應(yīng)該謹(jǐn)慎使用。
二、CBV和FBV的區(qū)別
FBV(function base views) 基于函數(shù)的視圖,就是在視圖里使用函數(shù)處理請(qǐng)求。
CBV(class base views) 基于類(lèi)的視圖,就是在視圖里使用類(lèi)處理請(qǐng)求。
2.1、FBV結(jié)構(gòu)示例
# urls.py 文件
urlpatterns = [
path("login/", views.login),
]
# views.py文件
from django.shortcuts import render,HttpResponse
def login(request):
if request.method == "GET":
return HttpResponse("GET 方法")
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")
if user == "eword" and pwd == "123456":
return HttpResponse("POST 方法")
else:
return HttpResponse("POST 方法1")
2.2、CBV 結(jié)構(gòu)示例
# urls.py 文件
urlpatterns = [
path("login/", views.Login.as_view()),
]
# views.py文件
from django.shortcuts import render,HttpResponse
from django.views import View
class Login(View):
def get(self,request):
return HttpResponse("GET 方法")
def post(self,request):
user = request.POST.get("user")
pwd = request.POST.get("pwd")
if user == "eword" and pwd == "123456":
return HttpResponse("POST 方法")
else:
return HttpResponse("POST 方法 1")
2.3、FBV下API 示例
2.3.1、模式一:一個(gè)方法操作所有
通過(guò)body來(lái)攜帶id等pk參數(shù)。
# serializers.d4_serializers.py
from rest_framework import serializers, status
from d4.models.d4_models import D4Dic
class D4DicSerializer(serializers.Serializer):
# 編號(hào)
id = serializers.CharField(read_only=False)
# 父級(jí)ID 1級(jí)統(tǒng)一0000
fid = serializers.CharField(required=False)
# 數(shù)值
value = serializers.CharField(max_length=50)
# 顯示值
name = serializers.CharField(max_length=50)
# 類(lèi)別,用source定義別名
dic_type = serializers.CharField(max_length=50, source='type')
# 狀態(tài) (1=啟用,0=棄用)
status = serializers.CharField(max_length=6)
# 是否鎖定編輯(0=否,1=是(內(nèi)置字典,與寫(xiě)死代碼對(duì)應(yīng)))
is_locked = serializers.CharField(max_length=2)
# 簡(jiǎn)碼 一般為 name 內(nèi)容的大寫(xiě)首字母
code = serializers.CharField(max_length=255)
# 描述
description = serializers.CharField(max_length=255)
# 排序(升序)范圍 0-50000
sort = serializers.IntegerField()
# 是否可以刪除 1是 0否
is_delete = serializers.CharField(max_length=2)
# 創(chuàng)建人id
created_by = serializers.CharField(max_length=32)
# 創(chuàng)建日期
created_date = serializers.DateTimeField()
# 更新人id
update_by = serializers.CharField(max_length=32)
# 更新日期
update_date = serializers.DateTimeField()
def create(self, validated_data):
# 根據(jù)提供的驗(yàn)證過(guò)的數(shù)據(jù)創(chuàng)建并返回一個(gè)新的`Dic`實(shí)例。
return D4Dic.objects.create(**validated_data)
def update(self, instance, validated_data):
# # 根據(jù)提供的驗(yàn)證過(guò)的數(shù)據(jù)更新和返回一個(gè)已經(jīng)存在的`Snippet`實(shí)例。
# instance.fid=validated_data.get('fid',instance.fid)
# instance.value=validated_data.get('value',instance.value)
# instance.name=validated_data.get('name',instance.name)
# instance.type=validated_data.get('type',instance.type)
# instance.status=validated_data.get('status',instance.status)
# instance.locked=validated_data.get('locked',instance.locked)
# instance.easy_code=validated_data.get('easy_code',instance.easy_code)
# instance.description=validated_data.get('description',instance.description)
# instance.sort=validated_data.get('sort',instance.sort)
# instance.dict_type=validated_data.get('dict_type',instance.dict_type)
# instance.is_del=validated_data.get('is_del',instance.is_del)
# return instance
# # 根據(jù)查詢(xún)出來(lái)的示例更新驗(yàn)證過(guò)的數(shù)據(jù)
# # 通過(guò)以下方式請(qǐng)求
# # 請(qǐng)求類(lèi)中:dic = D4Dic.objects.get(id=pk)
# # 請(qǐng)求類(lèi)中:serializer = D4DicSerializer(dic, data=data_json)
# D4Dic.objects.filter(pk=instance.id).update(**validated_data)
# updated_d4dic = D4Dic.objects.get(pk=instance.id)
# return updated_d4dic
# 根據(jù)請(qǐng)求的數(shù)據(jù)查詢(xún)實(shí)例更新驗(yàn)證過(guò)的數(shù)據(jù),這樣可以減少一次查詢(xún)
# 通過(guò)以下方式請(qǐng)求
# 請(qǐng)求類(lèi)中:data = request.body.decode('utf-8') # 將請(qǐng)求的body轉(zhuǎn)為字符串
# 請(qǐng)求類(lèi)中:data_json = json.loads(data) # 解析為json
# 請(qǐng)求類(lèi)中:serializer = D4DicSerializer(instance=data_json, data=data_json)
if 'id' in instance and instance.get("id"):
id = instance.get("id")
else:
raise Exception("id不存在")
dic = D4Dic.objects.filter(pk=id)
if not dic.exists():
raise Exception(status.HTTP_404_NOT_FOUND)
dic.update(**validated_data)
return validated_data
# urls.py
urlpatterns = [
path('dic', dic_view.Dic),
# dic_view.py 一個(gè)方法解決一個(gè)資源的所有請(qǐng)求。
from django.shortcuts import HttpResponse, render
from rest_framework import status
from d4.models.d4_dic_model import D4Dic
from d4.serializers.d4_serializers import D4DicSerializer
import json
def Dic(request):
'''
FBV下的系統(tǒng)字典管理API
'''
if request.method == "GET":
print("GET 被執(zhí)行")
# GET請(qǐng)求
request.encoding = 'utf-8'
if 'pk' in request.GET and request.GET['pk']:
# 單條記錄查詢(xún)
pk = request.GET['pk']
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化數(shù)據(jù)
# return Response(serializer.data)
# 返回給 html 模板視圖,自定義字典
# return render(request,"dic.html",{"dic":"內(nèi)容"})
# 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
return render(request, "dic.html", serializer.data)
else:
# 多條記錄查詢(xún)
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 轉(zhuǎn)換成 json
# 可以使用 from rest_framework.renderers import JSONRenderer
# data = JSONRenderer().render(serializer.data)
# 可以使用 json.dumps(serializer.data)
data = json.dumps(serializer.data)
return HttpResponse(data)
elif request.method == "POST":
print("POST 被執(zhí)行了")
data = request.body.decode('utf-8') # 將請(qǐng)求的body轉(zhuǎn)為字符串
data_json = json.loads(data) # 解析為json
# 添加數(shù)據(jù)請(qǐng)求
serializer = D4DicSerializer(data=data_json)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)已經(jīng)被保存")
print(data_json)
return HttpResponse(content=json.dumps(data_json), status=status.HTTP_201_CREATED)
return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == "PUT":
print("PUT 被執(zhí)行")
data = request.body.decode('utf-8') # 將請(qǐng)求的body轉(zhuǎn)為字符串
data_json = json.loads(data) # 解析為json
# 修改數(shù)據(jù)請(qǐng)
serializer = D4DicSerializer(instance=data_json, data=data_json)
if serializer.is_valid():
serializer.save()
return HttpResponse(json.dumps(serializer.data))
return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == "DELETE":
print("DELETE被執(zhí)行")
# 刪除數(shù)據(jù)請(qǐng)求
if 'pk' in request.GET and request.GET['pk']:
# 單條記錄查詢(xún)
pk = request.GET['pk']
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到數(shù)據(jù)")
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
dic.delete()
return HttpResponse(status=status.HTTP_204_NO_CONTENT)
else:
return HttpResponse("pk不存在")
# 默認(rèn)返回
return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
方別使用 GET、POST、PUT、DELETE的形式訪問(wèn)一下
http://xxx.xxx.xxx.xxx:8000/dic和http://xxx.xxx.xxx.xxx:8000/dic/xxx試一下效果吧。
2.3.2、模式二:兩個(gè)方法操作分開(kāi)List和Detail
通過(guò)Url來(lái)攜帶id等pk參數(shù)。
# serializers.d4_serializers.py
from rest_framework import serializers, status
from d4.models.d4_models import D4Dic
class D4DicSerializer(serializers.Serializer):
# 編號(hào)
id = serializers.CharField(read_only=False)
# 父級(jí)ID 1級(jí)統(tǒng)一0000
fid = serializers.CharField(required=False)
# 數(shù)值
value = serializers.CharField(max_length=50)
# 顯示值
name = serializers.CharField(max_length=50)
# 類(lèi)別,用source定義別名
dic_type = serializers.CharField(max_length=50, source='type')
# 狀態(tài) (1=啟用,0=棄用)
status = serializers.CharField(max_length=6)
# 是否鎖定編輯(0=否,1=是(內(nèi)置字典,與寫(xiě)死代碼對(duì)應(yīng)))
is_locked = serializers.CharField(max_length=2)
# 簡(jiǎn)碼 一般為 name 內(nèi)容的大寫(xiě)首字母
code = serializers.CharField(max_length=255)
# 描述
description = serializers.CharField(max_length=255)
# 排序(升序)范圍 0-50000
sort = serializers.IntegerField()
# 是否可以刪除 1是 0否
is_delete = serializers.CharField(max_length=2)
# 創(chuàng)建人id
created_by = serializers.CharField(max_length=32)
# 創(chuàng)建日期
created_date = serializers.DateTimeField()
# 更新人id
update_by = serializers.CharField(max_length=32)
# 更新日期
update_date = serializers.DateTimeField()
def create(self, validated_data):
# 根據(jù)提供的驗(yàn)證過(guò)的數(shù)據(jù)創(chuàng)建并返回一個(gè)新的`Dic`實(shí)例。
return D4Dic.objects.create(**validated_data)
def update(self, instance, validated_data):
# 這里和模式一不一樣,這里直接將 pk 賦值給 instance
dic = D4Dic.objects.filter(pk=instance)
if not dic.exists():
raise Exception(status.HTTP_404_NOT_FOUND)
dic.update(**validated_data)
return validated_data
# urls.py
from django.urls import path, include, re_path
from d4.views import dic_view
urlpatterns = [
# 這里要注意path 和re_path的區(qū)別。
re_path(r'^dic/$', dic_view.DicList),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', dic_view.DicDetail),
# dic_view.py 兩個(gè)方法操作分開(kāi)List和Detail。
from django.shortcuts import HttpResponse, render
from rest_framework import status
from d4.models.d4_models import D4Dic
from d4.serializers.d4_serializers import D4DicSerializer
import json
def DicList(request):
'''
FBV下的系統(tǒng)字典管理API
'''
if request.method == "GET":
print("GET 被執(zhí)行")
# GET請(qǐng)求
# 多條記錄查詢(xún)
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 轉(zhuǎn)換成 json
# 可以使用 from rest_framework.renderers import JSONRenderer
# data = JSONRenderer().render(serializer.data)
# 可以使用 json.dumps(serializer.data)
data = json.dumps(serializer.data)
return HttpResponse(data)
elif request.method == "POST":
print("POST 被執(zhí)行了")
data = request.body.decode('utf-8') # 將請(qǐng)求的body轉(zhuǎn)為字符串
data_json = json.loads(data) # 解析為json
# 添加數(shù)據(jù)請(qǐng)求
serializer = D4DicSerializer(data=data_json)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)已經(jīng)被保存")
print(data_json)
return HttpResponse(content=json.dumps(data_json), status=status.HTTP_201_CREATED)
return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 默認(rèn)返回
return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def DicDetail(request, pk):
'''
FBV下的系統(tǒng)字典管理API
'''
if request.method == "GET":
print("GET 被執(zhí)行")
# GET請(qǐng)求
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化數(shù)據(jù)
return HttpResponse(json.dumps(serializer.data))
# 返回給 html 模板視圖,自定義字典
# return render(request,"dic.html",{"dic":"內(nèi)容"})
# 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
# return render(request, "dic.html", serializer.data)
elif request.method == "PUT":
print("PUT 被執(zhí)行")
data = request.body.decode('utf-8') # 將請(qǐng)求的body轉(zhuǎn)為字符串
data_json = json.loads(data) # 解析為json
# 修改數(shù)據(jù)請(qǐng)
serializer = D4DicSerializer(instance=pk, data=data_json)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)被更新了。")
return HttpResponse(json.dumps(serializer.data))
return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == "DELETE":
print("DELETE被執(zhí)行")
# 刪除數(shù)據(jù)請(qǐng)求
# 單條記錄查詢(xún)
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到數(shù)據(jù)")
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
dic.delete()
return HttpResponse(status=status.HTTP_204_NO_CONTENT)
# 默認(rèn)返回
return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
2.3.3、使用@api_view裝飾器
@api_view裝飾器函數(shù)簽名:@api_view(http_method_names=['GET'], exclude_from_schema=False)
- 用來(lái)包裝你的視圖函數(shù),以確保視圖函數(shù)會(huì)收到
Request(而不是Django一般的HttpRequest)對(duì)象,并且返回Response(而不是Django的HttpResponse)對(duì)象,同時(shí)允許你設(shè)置這個(gè)請(qǐng)求的處理方式。- @api_view裝飾器還提供了諸如在適當(dāng)時(shí)候返回
405 Method Not Allowed響應(yīng),并處理在使用格式錯(cuò)誤的輸入來(lái)訪問(wèn)request.data時(shí)發(fā)生的任何ParseError異常。- 簡(jiǎn)單來(lái)說(shuō)使用
@api_view裝飾器的的效果類(lèi)似在CBV下繼承APIView的效果
from rest_framework.decorators import api_view
from rest_framework import status
from rest_framework.response import Response
from d4.models.d4_models import D4Dic
from d4.serializers.d4_serializers import D4DicSerializer
import json
@api_view(['GET', "POST"])
def DicList(request):
'''
FBV下的系統(tǒng)字典管理API
'''
if request.method == "GET":
print("GET 被執(zhí)行")
# GET請(qǐng)求
# 多條記錄查詢(xún)
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 轉(zhuǎn)換成 json
# 可以使用 from rest_framework.renderers import JSONRenderer
# data = JSONRenderer().render(serializer.data)
# 可以使用 json.dumps(serializer.data)
data = json.dumps(serializer.data)
return Response(data)
elif request.method == "POST":
print("POST 被執(zhí)行了")
# data = request.body.decode('utf-8') # 將請(qǐng)求的body轉(zhuǎn)為字符串
# data_json = json.loads(data) # 解析為json
# 添加數(shù)據(jù)請(qǐng)求
serializer = D4DicSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)已經(jīng)被保存")
print(request.data)
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 默認(rèn)返回
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@api_view(['GET', "PUT", "DELETE"])
def DicDetail(request, pk):
'''
FBV下的系統(tǒng)字典管理API
'''
if request.method == "GET":
print("GET 被執(zhí)行")
# GET請(qǐng)求
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化數(shù)據(jù)
return Response(json.dumps(serializer.data))
# 返回給 html 模板視圖,自定義字典
# return render(request,"dic.html",{"dic":"內(nèi)容"})
# 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
# return render(request, "dic.html", serializer.data)
elif request.method == "PUT":
print("PUT 被執(zhí)行")
data = request.body.decode('utf-8') # 將請(qǐng)求的body轉(zhuǎn)為字符串
data_json = json.loads(data) # 解析為json
# 修改數(shù)據(jù)請(qǐng)
serializer = D4DicSerializer(instance=pk, data=data_json)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)被更新了。")
return Response(json.dumps(serializer.data))
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == "DELETE":
print("DELETE被執(zhí)行")
# 刪除數(shù)據(jù)請(qǐng)求
# 單條記錄查詢(xún)
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到數(shù)據(jù)")
return Response(status=status.HTTP_404_NOT_FOUND)
dic.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# 默認(rèn)返回
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
三、View源碼解析

3.1、as_view()方法
用來(lái)識(shí)別請(qǐng)求類(lèi)型,并調(diào)用
dispatch函數(shù)分發(fā)到對(duì)應(yīng)的處理函數(shù)
# as_view()方法
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError(
"The method name %s is not accepted as a keyword argument "
"to %s()." % (key, cls.__name__)
)
if not hasattr(cls, key):
raise TypeError(
"%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key)
)
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
if not hasattr(self, "request"):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
## 調(diào)用dispatch分發(fā)任務(wù)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# __name__ and __qualname__ are intentionally left unchanged as
# view_class should be used to robustly determine the name of the view
# instead.
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.__annotations__ = cls.dispatch.__annotations__
# Copy possible attributes set by decorators, e.g. @csrf_exempt, from
# the dispatch method.
view.__dict__.update(cls.dispatch.__dict__)
# Mark the callback if the view class is async.
if cls.view_is_async:
view._is_coroutine = asyncio.coroutines._is_coroutine
return view
3.2、dispatch()方法
用來(lái)根據(jù)請(qǐng)求類(lèi)型分發(fā)到對(duì)應(yīng)的處理函數(shù),請(qǐng)求類(lèi)型包括:
- get
- post
- put
- patch
- delete
- head
- options
- trace
# dispatch()方法
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(
self, request.method.lower(), self.http_method_not_allowed
)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
3.3、請(qǐng)求順序
- 舉例eg.
- get請(qǐng)求
-view()==>return dispatch()==>return get()
-post請(qǐng)求
-view()==>return dispatch()==>return post()

3.4、CBV 下繼承View的 API 示例
serializers.d4_serializers.py 文件與2.3.2一致
繼承于View類(lèi)的 DicList類(lèi)和DicDetail類(lèi)無(wú)法在類(lèi)的方法上使用@api_view 裝飾器,所以這些方法的 request 和 response 都是 HttpRequest和 HttpResponse。
# urls.py文件
from django.urls import path, include, re_path
from d4.views import dic_view
urlpatterns = [
re_path(r'^dic/$', dic_view.DicList.as_view()),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', dic_view.DicDetail.as_view()),
]
# dic_view.py
from rest_framework.views import View
from rest_framework import status
from d4.models.d4_models import D4Dic
from d4.serializers.d4_serializers import D4DicSerializer
from django.http import HttpResponse
import json
# DicList類(lèi)
class DicList(View):
'''
CBV下的系統(tǒng)字典管理API
'''
def get(self, request):
print("GET 被執(zhí)行")
# GET請(qǐng)求
# 多條記錄查詢(xún)
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 轉(zhuǎn)換成 json
# 可以使用 from rest_framework.renderers import JSONRenderer
# data = JSONRenderer().render(serializer.data)
# 可以使用 json.dumps(serializer.data)
data = json.dumps(serializer.data)
return HttpResponse(data)
def post(self, request):
print("POST 被執(zhí)行了")
data = request.body.decode('utf-8') # 將請(qǐng)求的body轉(zhuǎn)為字符串
data_json = json.loads(data) # 解析為json
# 添加數(shù)據(jù)請(qǐng)求
serializer = D4DicSerializer(data=data_json)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)已經(jīng)被保存")
print(data_json)
return HttpResponse(json.dumps(serializer.data), status=status.HTTP_201_CREATED)
return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# DicDetail類(lèi)
class DicDetail(View):
'''
CBV下的系統(tǒng)字典管理API
'''
def get(self, request, pk):
print("GET 被執(zhí)行")
# GET請(qǐng)求
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化數(shù)據(jù)
return HttpResponse(json.dumps(serializer.data))
# 返回給 html 模板視圖,自定義字典
# return render(request,"dic.html",{"dic":"內(nèi)容"})
# 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
# return render(request, "dic.html", serializer.data)
def put(self, request, pk):
print("PUT 被執(zhí)行")
data = request.body.decode('utf-8') # 將請(qǐng)求的body轉(zhuǎn)為字符串
data_json = json.loads(data) # 解析為json
# 修改數(shù)據(jù)請(qǐng)
serializer = D4DicSerializer(instance=pk, data=data_json)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)被更新了。")
return HttpResponse(json.dumps(serializer.data))
return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
print("DELETE被執(zhí)行")
# 刪除數(shù)據(jù)請(qǐng)求
# 單條記錄查詢(xún)
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到數(shù)據(jù)")
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
dic.delete()
return HttpResponse(status=status.HTTP_204_NO_CONTENT)
四、APIView源碼解析
APIView繼承于View,擴(kuò)展了以下功能
- 重寫(xiě)了as_view()方法和dispatch()方法用來(lái)構(gòu)建新的request對(duì)象,統(tǒng)一請(qǐng)求數(shù)據(jù)格式。
- eg.
- 將
a=1&b=2格式轉(zhuǎn)換成統(tǒng)一的 json 格式{ a:1, b:2 }- 重寫(xiě)了as_view()方法并在dispatch()方法進(jìn)行路由分發(fā)前,會(huì)對(duì)請(qǐng)求的客戶(hù)端進(jìn)行身份認(rèn)證、權(quán)限檢查、流浪控制。
4.1、as_view()方法
# as_view()方法
@classmethod
def as_view(cls, **initkwargs):
"""
Store the original class on the view function.
This allows us to discover information about the view when we do URL
reverse lookups. Used for breadcrumb generation.
"""
if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
def force_evaluation():
raise RuntimeError(
'Do not evaluate the `.queryset` attribute directly, '
'as the result will be cached and reused between requests. '
'Use `.all()` or call `.get_queryset()` instead.'
)
cls.queryset._fetch_all = force_evaluation
view = super().as_view(**initkwargs) ## <---- 這里
view.cls = cls
view.initkwargs = initkwargs
# Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt.
return csrf_exempt(view) ## <---- 這里
4.2、dispatch()方法
# dispatch()方法
# Note: Views are made CSRF exempt from within `as_view` as to prevent
# accidental removal of this exemption in cases where `dispatch` needs to
# be overridden.
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs) ## <---- 這里構(gòu)建新的request對(duì)象,統(tǒng)一請(qǐng)求數(shù)據(jù)格式
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names: ## <--分發(fā)邏輯幾乎和View一樣。
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
4.3、請(qǐng)求順序
和View類(lèi)幾乎一樣
- 舉例eg.
- get請(qǐng)求
-view()==>return dispatch()==>return get()
-post請(qǐng)求
-view()==>return dispatch()==>return post()

4.4、CBV 下繼承APIView的API 示例
D4DicSerializer 序列化類(lèi)與 2.3.2的 serializers.d4_serializers.py 文件一致。
# urls.py文件
re_path(r'^dic/$', dic_view.DicList.as_view()),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', dic_view.DicDetail.as_view()),
# dic_view.py
from rest_framework.response import Response
from rest_framework import status
from d4.models.d4_models import D4Dic
from d4.serializers.d4_serializers import D4DicSerializer
from rest_framework.views import APIView
import json
class DicList(APIView):
'''
CBV下的系統(tǒng)字典管理API
'''
def get(self, request):
print("GET 被執(zhí)行")
# GET請(qǐng)求
# 多條記錄查詢(xún)
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 轉(zhuǎn)換成 json
# 可以使用 from rest_framework.renderers import JSONRenderer
# data = JSONRenderer().render(serializer.data)
# 可以使用 json.dumps(serializer.data)
data = json.dumps(serializer.data)
return Response(json.loads(data))
def post(self, request):
print("POST 被執(zhí)行了")
# 添加數(shù)據(jù)請(qǐng)求
serializer = D4DicSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)已經(jīng)被保存")
print(request.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class DicDetail(APIView):
'''
CBV下的系統(tǒng)字典管理API
'''
def get(self, request, pk):
print("GET 被執(zhí)行")
# GET請(qǐng)求
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化數(shù)據(jù)
return Response(serializer.data)
# 返回給 html 模板視圖,自定義字典
# return render(request,"dic.html",{"dic":"內(nèi)容"})
# 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
# return render(request, "dic.html", serializer.data)
def put(self, request, pk):
print("PUT 被執(zhí)行")
# 修改數(shù)據(jù)請(qǐng)
serializer = D4DicSerializer(instance=pk, data=request.data)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)被更新了。")
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
print("DELETE被執(zhí)行")
# 刪除數(shù)據(jù)請(qǐng)求
# 單條記錄查詢(xún)
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到數(shù)據(jù)")
return Response(status=status.HTTP_404_NOT_FOUND)
dic.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
五、序列化與反序列化
Serializer序列化器,把像查詢(xún)集和模型實(shí)例這樣的復(fù)雜數(shù)據(jù)轉(zhuǎn)換為可以輕松渲染成JSON,XML或其他內(nèi)容類(lèi)型的原生Python類(lèi)型。序列化器還提供反序列化,在驗(yàn)證傳入的數(shù)據(jù)之后允許解析數(shù)據(jù)轉(zhuǎn)換回復(fù)雜類(lèi)型。
序列化器構(gòu)造函數(shù):Serializer(instance=None, data=empty, **kwarg)
- 說(shuō)明:
- 用于序列化時(shí),將模型類(lèi)對(duì)象傳入
instance參數(shù)
- 用于反序列化時(shí),將要被反序列化的數(shù)據(jù)傳入
data參數(shù)
- 除了
instance和data參數(shù)外,在構(gòu)造Serializer對(duì)象時(shí),還可通過(guò)context參數(shù)額外添加數(shù)據(jù),如serializer = AccountSerializer(account, context={'request': request})
通過(guò)context參數(shù)附加的數(shù)據(jù),可以通過(guò)Serializer對(duì)象的context屬性獲取。- 注意
- 序列化器聲明了以后,在視圖中調(diào)用 后才會(huì)執(zhí)行。
- 不管是序列化還是反序列化,都需要通過(guò)構(gòu)造函數(shù)把數(shù)據(jù)傳遞到序列化器。
- 序列化器的字段聲明類(lèi)似
Model。
django drf框架在使用序列化器進(jìn)行序列化時(shí)會(huì)把模型數(shù)據(jù)轉(zhuǎn)換成字典。
- django drf 框架的視圖在返回
Response時(shí)會(huì)把字典轉(zhuǎn)換成json。或者把客戶(hù)端發(fā)送過(guò)來(lái)的數(shù)據(jù)(request.data)轉(zhuǎn)換 成 字典.
5.1、原始序列化器
from rest_framework import serializers
from models.d4_dic_model import D4Dic
class D4DicSerializer(serializers.Serializer):
# 編號(hào)
id = serializers.CharField(read_only=True)
# 父級(jí)ID 1級(jí)統(tǒng)一0000
fid = serializers.CharField(required=False)
# 數(shù)值
value = serializers.CharField(max_length=50)
# 顯示值
name = serializers.CharField(max_length=50)
# 類(lèi)別,用source定義別名
type = serializers.CharField(max_length=50, source='dic_type')
# 狀態(tài) (1=啟用,0=棄用)
status = serializers.CharField(max_length=6)
# 是否鎖定編輯(0=否,1=是(內(nèi)置字典,與寫(xiě)死代碼對(duì)應(yīng)))
is_locked = serializers.CharField(max_length=2)
# 簡(jiǎn)碼 一般為 name 內(nèi)容的大寫(xiě)首字母
code = serializers.CharField(max_length=255)
# 描述
description = serializers.CharField(max_length=255)
# 排序(升序)范圍 0-50000
sort = serializers.IntegerField()
# 是否可以刪除 1是 0否
is_delete = serializers.CharField(max_length=2)
# 創(chuàng)建人id
created_by = serializers.CharField(max_length=32, null=False)
# 創(chuàng)建日期
created_date = serializers.DateTimeField(null=False)
# 更新人id
update_by = serializers.CharField(max_length=32, blank=True, null=True)
# 更新日期
update_date = serializers.DateTimeField(blank=True, null=True)
5.2、save()方法下的序列化器
Serializers的父類(lèi)BaseSerializer中.save()方法如下:
def save(self, **kwargs):
assert hasattr(self, '_errors'), (
'You must call `.is_valid()` before calling `.save()`.'
)
assert not self.errors, (
'You cannot call `.save()` on a serializer with invalid data.'
)
# Guard against incorrect use of `serializer.save(commit=False)`
assert 'commit' not in kwargs, (
"'commit' is not a valid keyword argument to the 'save()' method. "
"If you need to access data before committing to the database then "
"inspect 'serializer.validated_data' instead. "
"You can also pass additional keyword arguments to 'save()' if you "
"need to set extra attributes on the saved model instance. "
"For example: 'serializer.save(owner=request.user)'.'"
)
assert not hasattr(self, '_data'), (
"You cannot call `.save()` after accessing `serializer.data`."
"If you need to access data before committing to the database then "
"inspect 'serializer.validated_data' instead. "
)
validated_data = {**self.validated_data, **kwargs}
if self.instance is not None:
# 注意這里
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
# 注意這里
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
return self.instance
BaseSerializer中.save()方法判斷了序列化器是否傳了instance參數(shù)。
- 如果傳了就調(diào)用
def update(self, instance, validated_data)方法。- 如果沒(méi)有就調(diào)用
def create(self, validated_data)方法
BaseSerializer中定義的update和create方法如下:
def update(self, instance, validated_data):
raise NotImplementedError('`update()` must be implemented.')
def create(self, validated_data):
raise NotImplementedError('`create()` must be implemented.')
BaseSerializer中定義的update和create方法,是需要用戶(hù)在子類(lèi)中重載,實(shí)現(xiàn)業(yè)務(wù)邏輯的。這樣用戶(hù)在調(diào)用序列化器的.save()方法時(shí),會(huì)根據(jù)是否傳了instance參數(shù),將通過(guò)校驗(yàn)的validated_data數(shù)據(jù)傳遞給重載的update或create方法。
- 注意:
- 在調(diào)用
.save()方法前要先調(diào)用.is_valid()方法,對(duì)傳入的數(shù)據(jù)進(jìn)行校驗(yàn)。
5.2.1、 在子類(lèi)中重載update 和create方法
from rest_framework import serializers, status
from d4.models.d4_models import D4Dic
class D4DicSerializer(serializers.Serializer):
# 編號(hào)
id = serializers.CharField(read_only=False)
# 父級(jí)ID 1級(jí)統(tǒng)一0000
fid = serializers.CharField(required=False)
# 數(shù)值
value = serializers.CharField(max_length=50)
# 顯示值
name = serializers.CharField(max_length=50)
# 類(lèi)別,用source定義別名
dic_type = serializers.CharField(max_length=50, source='type')
# 狀態(tài) (1=啟用,0=棄用)
status = serializers.CharField(max_length=6)
# 是否鎖定編輯(0=否,1=是(內(nèi)置字典,與寫(xiě)死代碼對(duì)應(yīng)))
is_locked = serializers.CharField(max_length=2)
# 簡(jiǎn)碼 一般為 name 內(nèi)容的大寫(xiě)首字母
code = serializers.CharField(max_length=255)
# 描述
description = serializers.CharField(max_length=255)
# 排序(升序)范圍 0-50000
sort = serializers.IntegerField()
# 是否可以刪除 1是 0否
is_delete = serializers.CharField(max_length=2)
# 創(chuàng)建人id
created_by = serializers.CharField(max_length=32)
# 創(chuàng)建日期
created_date = serializers.DateTimeField()
# 更新人id
update_by = serializers.CharField(max_length=32)
# 更新日期
update_date = serializers.DateTimeField()
def create(self, validated_data):
# 根據(jù)提供的驗(yàn)證過(guò)的數(shù)據(jù)創(chuàng)建并返回一個(gè)新的`Dic`實(shí)例。
return D4Dic.objects.create(**validated_data)
def update(self, instance, validated_data):
# 這里和模式一不一樣,這里直接將 pk 賦值給 instance
dic = D4Dic.objects.filter(pk=instance)
if not dic.exists():
raise Exception(status.HTTP_404_NOT_FOUND)
dic.update(**validated_data)
return validated_data
5.3、ModelSerializer
通過(guò)繼承
ModelSerializer創(chuàng)建序列化器,可以簡(jiǎn)化字段、create和update的定義。
ModelSerializer將繼承Serializer時(shí)需要重復(fù)做的字段定義、create方法和update方法進(jìn)行了封裝,簡(jiǎn)化了序列化器的構(gòu)建。
# serializers.d4_serializers.py
from rest_framework import serializers
from d4.models.d4_models import D4Dic
class D4DicSerializer(serializers.ModelSerializer):
# # 通過(guò) source為 type 字段設(shè)置別名dic_type.
# # 注意:dic_type和 type 是同一個(gè)字段,且會(huì)同時(shí)被序列化,除非把其中一個(gè)exclude
# dic_type = serializers.CharField(max_length=50, source='type')
class Meta:
model = D4Dic
# 序列化全部字段
fields = "__all__"
# # 序列化數(shù)組內(nèi)的字段
# fields = ['id', 'fid', 'name', 'value']
# # 排除數(shù)組內(nèi)的字段,注意:fields和exclude不能同時(shí)存在。
# exclude = ['type']
# 只讀字段
# read_only_fields = ['id', 'fid', 'name', 'value']
# 修改某些字段選項(xiàng)
extra_kwargs = {
'value': {'min_value": 0, 'required':True),
'fid': ('min_value': 0, ' required':True)
}
六、GenericAPIView源碼解析
GenericAPIView繼承了APIView,作用是把視圖中的通用代碼抽取出來(lái),讓視圖方法中的代碼更加通用,方便把通用代碼進(jìn)行簡(jiǎn)寫(xiě)。相較于
APIView,GenericAPIView主要增加了操作序列化器和數(shù)據(jù)庫(kù)查詢(xún)的方法,作用是為Mixin擴(kuò)展類(lèi)的執(zhí)行提供方法支持(通常在使用時(shí),可搭配一個(gè)或多個(gè)Mixin擴(kuò)展類(lèi))。
GenericAPIView中需要關(guān)注的屬性和方法如下:
get_serializer_class(self)
- 當(dāng)一個(gè)視圖類(lèi)中用到多個(gè)序列化器時(shí),可以通過(guò)
get_serializer_cass方法返回的序列化器類(lèi)名判斷是哪個(gè)序列化器。默認(rèn)返回的serializer_class,可以被重寫(xiě)。get_serializer(self, *args, **kwargs)
- 在視圖中提供獲取序列化器對(duì)象的方法(返回序列化器對(duì)象),主要用來(lái)提供給
Mixin擴(kuò)展類(lèi)使用。- 注意:該方法會(huì)在返回的的序列化器對(duì)象的
context屬性中補(bǔ)充三個(gè)數(shù)據(jù):request、format、view:
- request: 當(dāng)前視圖的請(qǐng)求對(duì)象
- view: 當(dāng)前請(qǐng)求的類(lèi)視圖對(duì)象
- format: 當(dāng)前請(qǐng)求期望返回的數(shù)據(jù)格式
get_queryset(self)
- 返回視圖使用的查詢(xún)集,主要用來(lái)提供給
Mixin擴(kuò)展類(lèi)使用,是列表視圖與詳情視圖獲取數(shù)據(jù)的基礎(chǔ),默認(rèn)返回 的queryset屬性,可以被重寫(xiě)。
def get_queryset(self) myclass=xxxx_model_class return myclass.object.all()
get_object(self)
- 執(zhí)行數(shù)據(jù)查詢(xún)過(guò)濾,返回符合查詢(xún)條件的數(shù)據(jù)詳情對(duì)象,主要用來(lái)提供給
Mixin擴(kuò)展類(lèi)使用。在試圖中可以調(diào)用該方法獲取詳情信息的模型類(lèi)對(duì)象。- 若數(shù)據(jù)不存在,會(huì)返回
404。- 該方法會(huì)默認(rèn)使用
APIView提供的check_object _permissions方法檢當(dāng)前對(duì)象是否有權(quán)限被訪問(wèn)。- 要配合路由設(shè)置中的
有名分組進(jìn)行,get_object會(huì)根據(jù)有名分組的參數(shù)進(jìn)行查詢(xún)。
6.1、CBV 下繼承GenericAPIView的API 示例
# dic_view.py
from rest_framework.response import Response
from rest_framework import status
from d4.models.d4_models import D4Dic
from d4.serializers.d4_serializers import D4DicSerializer
from rest_framework.generics import GenericAPIView
class DicList(GenericAPIView):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get(self, request):
print("GET 被執(zhí)行")
# GET請(qǐng)求
# 多條記錄查詢(xún)
# list = self.queryset()
# serializer = self.get_serializer_class()(self.queryset(), many=True)
serializer = self.get_serializer(self.get_queryset(), many=True)
return Response(serializer.data)
def post(self, request):
print("POST 被執(zhí)行了")
# 添加數(shù)據(jù)請(qǐng)求
# serializer = D4DicSerializer(data=request.data)
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)已經(jīng)被保存")
print(request.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class DicDetail(GenericAPIView):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get(self, request, pk):
print("GET 被執(zhí)行")
# GET請(qǐng)求
# self.get_object() 基于有名路由分組進(jìn)行過(guò)濾
serializer = self.get_serializer(instance=self.get_object(), many=False)
# 返回序列化數(shù)據(jù)
return Response(serializer.data)
# 返回給 html 模板視圖,自定義字典
# return render(request,"dic.html",{"dic":"內(nèi)容"})
# 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
# return render(request, "dic.html", serializer.data)
def put(self, request, pk):
print("PUT 被執(zhí)行")
# 修改數(shù)據(jù)請(qǐng)
serializer = self.get_serializer(instance=self.get_object(), data=request.data)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)被更新了。")
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
print("DELETE被執(zhí)行")
# 刪除數(shù)據(jù)請(qǐng)求
# 單條記錄查詢(xún)
self.get_object().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
七、Mixin工具類(lèi)源碼解析
- Mixin作用:
- 提供了幾種后端視圖組合,用來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)資源進(jìn)行增刪改查處理流程。即封裝了數(shù)據(jù)資源的增刪改查操作,其他視圖可以通過(guò)繼承相應(yīng)的Mixin擴(kuò)展類(lèi)來(lái)復(fù)用代碼,減少自己編寫(xiě)的代碼量。
- 這些Mixin擴(kuò)展類(lèi)需要搭配GenericAPIView通用視圖基類(lèi)使用,因?yàn)樗鼈兊膶?shí)現(xiàn)需要調(diào)用GenericAPIView提供的序列化器與數(shù)據(jù)庫(kù)查詢(xún)的方法。
7.1、Mixin 保函的視圖組合
7.1.1、CreateModelMixin
- 作用:
- 創(chuàng)建(添加)操作視圖擴(kuò)展類(lèi),提供
create(self, request, *args, **kwargs)方法快速實(shí)現(xiàn)創(chuàng)建(添加)視圖,返回201狀態(tài)碼。
class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
7.1.2、ListModelMixin
- 作用:
- 列表視圖擴(kuò)展類(lèi),提供
list(request, *args, **kwargs)方法快速實(shí)現(xiàn)列表視圖,返回200狀態(tài)碼。- 該
Mixin的list方法會(huì)對(duì)數(shù)據(jù)進(jìn)行過(guò)濾和分頁(yè)。
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
7.1.3、RetrieveModelMixin
- 作用:
- 數(shù)據(jù)詳情頁(yè)視圖擴(kuò)展類(lèi),提供
retrieve(self, request, *args, **kwargs)方法快速實(shí)現(xiàn)列表視圖,返回200狀態(tài)碼。
class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
7.1.4、UpdateModelMixin
- 作用:
- 更新操作視圖擴(kuò)展類(lèi),提供
update(self, request, *args, **kwargs)方法快速實(shí)現(xiàn)列表視圖,返回200狀態(tài)碼。
class UpdateModelMixin:
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
7.1.5、DestroyModelMixin
- 作用:
- 刪除操作視圖擴(kuò)展類(lèi),提供
destroy(self, request, *args, **kwargs)方法快速實(shí)現(xiàn)列表視圖,返回204狀態(tài)碼。
class DestroyModelMixin:
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
7.2、繼承Mixin時(shí)的API 示例
# dic_view.py
from d4.models.d4_models import D4Dic
from d4.serializers.d4_serializers import D4DicSerializer
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
class DicList(GenericAPIView, ListModelMixin, CreateModelMixin):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get(self, request):
print("GET 被執(zhí)行")
return self.list(request)
def post(self, request):
print("POST 被執(zhí)行了")
return self.create(request)
class DicDetail(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get(self, request, pk):
print("GET 被執(zhí)行")
return self.retrieve(request, pk)
def put(self, request, pk):
print("PUT 被執(zhí)行")
return self.update(request, pk)
def delete(self, request, pk):
print("DELETE被執(zhí)行")
return self.destroy(request, pk)
7.3、Mixin 再封裝
由7.2k也看出
DicList類(lèi)和DicDetail類(lèi)表示了Dic資源的5個(gè)操作(增刪改查查),而其他資源如果也只想實(shí)現(xiàn)這5個(gè)操作,那么代碼相似度是極高的。因此可以對(duì)其進(jìn)行二次封裝,封裝的思路主要是簡(jiǎn)化了繼承關(guān)系和對(duì)應(yīng)的操作。封裝的類(lèi)如下:
7.3.1、CreateAPIView
繼承了
CreateModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了post操作。
class CreateAPIView(mixins.mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for creating a model instance.
"""
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
7.3.2、ListAPIView
繼承了
ListModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了get操作。
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
7.3.3、RetrieveAPIView
繼承了
RetrieveModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了get操作。
class RetrieveAPIView(mixins.RetrieveModelMixin,
GenericAPIView):
"""
Concrete view for retrieving a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
7.3.4、DestroyAPIView
繼承了
DestroyModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了delete操作。
class DestroyAPIView(mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for deleting a model instance.
"""
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
7.3.5、UpdateAPIView
繼承了
UpdateModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了put、patch操作。
class UpdateAPIView(mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for updating a model instance.
"""
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
7.3.6、ListCreateAPIView
繼承了
ListModelMixin,CreateModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了get、post操作。
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
7.3.7、RetrieveUpdateAPIView
繼承了
RetrieveModelMixin,UpdateModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了get、put、patch操作。
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
7.3.8、RetrieveDestroyAPIView
繼承了
RetrieveModelMixin,DestroyModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了get、delete操作。
class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
7.3.9、RetrieveUpdateDestroyAPIView
繼承了
RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了get、put、patch、delete操作。
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
7.4、Mixin再封裝后的API示例
這里
DicList類(lèi)和DicDetail類(lèi)分別繼承了ListCreateAPIView和RetrieveUpdateDestroyAPIView,簡(jiǎn)化了代碼。
from d4.models.d4_models import D4Dic
from d4.serializers.d4_serializers import D4DicSerializer
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
class DicList(ListCreateAPIView):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
class DicDetail(RetrieveUpdateDestroyAPIView):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
八、ViewSet源碼解析
ViewSetMixin重構(gòu)了分發(fā)機(jī)制。
通過(guò)路由設(shè)置改變操作綁定的處理函數(shù)。
- eg.
- get操作綁定操作函數(shù)為get_object,post操作綁定操作函數(shù)為post_object。
re_path(r'^dic/$', DicList.as_view("get":"get_object", "post":"post_object"))
8.1、ViewSetMixin
class ViewSetMixin:
"""
This is the magic.
Overrides `.as_view()` so that it takes an `actions` keyword that performs
the binding of HTTP methods to actions on the Resource.
For example, to create a concrete view binding the 'GET' and 'POST' methods
to the 'list' and 'create' actions...
view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
"""
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
"""
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
# The name and description initkwargs may be explicitly overridden for
# certain route configurations. eg, names of extra actions.
cls.name = None
cls.description = None
# The suffix initkwarg is reserved for displaying the viewset type.
# This initkwarg should have no effect if the name is provided.
# eg. 'List' or 'Instance'.
cls.suffix = None
# The detail initkwarg is reserved for introspecting the viewset type.
cls.detail = None
# Setting a basename allows a view to reverse its action urls. This
# value is provided by the router through the initkwargs.
cls.basename = None
# actions must not be empty
if not actions:
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
# sanitize keyword arguments
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r" % (
cls.__name__, key))
# name and suffix are mutually exclusive
if 'name' in initkwargs and 'suffix' in initkwargs:
raise TypeError("%s() received both `name` and `suffix`, which are "
"mutually exclusive arguments." % (cls.__name__))
# 》》》》》《《《《《
# 核心代碼重構(gòu)了VIew》》》》》《《《《《
# 》》》》》《《《《《
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if 'get' in actions and 'head' not in actions:
actions['head'] = actions['get']
# We also store the mapping of request methods to actions,
# so that we can later set the action attribute.
# eg. `self.action = 'list'` on an incoming GET request.
self.action_map = actions
# Bind methods to actions
# This is the bit that's different to a standard view
# 》》》》》《《《《《
# 核心代碼,通過(guò)循環(huán)重新綁定了操作函數(shù)/方法。》》》》》《《《《《
# 》》》》》《《《《《
# actions === .as_view({"get":"get_object", "post":"post_object"})
for method, action in actions.items():
handler = getattr(self, action)
# 將 get 操作的執(zhí)行函數(shù)設(shè)置成get_object
setattr(self, method, handler)
self.request = request
self.args = args
self.kwargs = kwargs
# And continue as usual
return self.dispatch(request, *args, **kwargs)
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
# We need to set these on the view function, so that breadcrumb
# generation can pick out these bits of information from a
# resolved URL.
view.cls = cls
view.initkwargs = initkwargs
view.actions = actions
return csrf_exempt(view)
def initialize_request(self, request, *args, **kwargs):
"""
Set the `.action` attribute on the view, depending on the request method.
"""
request = super().initialize_request(request, *args, **kwargs)
method = request.method.lower()
if method == 'options':
# This is a special case as we always provide handling for the
# options method in the base `View` class.
# Unlike the other explicitly defined actions, 'metadata' is implicit.
self.action = 'metadata'
else:
self.action = self.action_map.get(method)
return request
def reverse_action(self, url_name, *args, **kwargs):
"""
Reverse the action for the given `url_name`.
"""
url_name = '%s-%s' % (self.basename, url_name)
namespace = None
if self.request and self.request.resolver_match:
namespace = self.request.resolver_match.namespace
if namespace:
url_name = namespace + ':' + url_name
kwargs.setdefault('request', self.request)
return reverse(url_name, *args, **kwargs)
@classmethod
def get_extra_actions(cls):
"""
Get the methods that are marked as an extra ViewSet `@action`.
"""
return [_check_attr_name(method, name)
for name, method
in getmembers(cls, _is_extra_action)]
def get_extra_action_url_map(self):
"""
Build a map of {names: urls} for the extra actions.
This method will noop if `detail` was not provided as a view initkwarg.
"""
action_urls = OrderedDict()
# exit early if `detail` has not been provided
if self.detail is None:
return action_urls
# filter for the relevant extra actions
actions = [
action for action in self.get_extra_actions()
if action.detail == self.detail
]
for action in actions:
try:
url_name = '%s-%s' % (self.basename, action.url_name)
namespace = self.request.resolver_match.namespace
if namespace:
url_name = '%s:%s' % (namespace, url_name)
url = reverse(url_name, self.args, self.kwargs, request=self.request)
view = self.__class__(**action.kwargs)
action_urls[view.get_view_name()] = url
except NoReverseMatch:
pass # URL requires additional arguments, ignore
return action_urls
8.2、ViewSet
ViewSet繼承了ViewSetMixin和APIView,除了有APIView的所有功能外,還支持制定路由分發(fā)。
8.2.1、ViewSet源碼
class ViewSet(ViewSetMixin, views.APIView):
"""
The base ViewSet class does not provide any actions by default.
"""
pass
8.2.2、繼承ViewSet時(shí)的 API 示例
# urls.py
from django.urls import re_path
from d4.d4_views import DicViewSet
urlpatterns = [
re_path(r'^dic/$', DicViewSet.as_view({"get": "get_list", "post": "add"})),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicViewSet.as_view({"get": "get_object", "put": "update"})),
]
# dic_view.py
from rest_framework.response import Response
from rest_framework import status
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import ViewSet
import json
class DicViewSet(ViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
def get_list(self, request):
print("GET 被執(zhí)行")
# GET請(qǐng)求
# 多條記錄查詢(xún)
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 轉(zhuǎn)換成 json
# 可以使用 from rest_framework.renderers import JSONRenderer
# data = JSONRenderer().render(serializer.data)
# 可以使用 json.dumps(serializer.data)
data = json.dumps(serializer.data)
return Response(json.loads(data))
def add(self, request):
print("POST 被執(zhí)行了")
# 添加數(shù)據(jù)請(qǐng)求
serializer = D4DicSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)已經(jīng)被保存")
print(request.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def get_object(self, request, pk):
print("GET 被執(zhí)行")
# GET請(qǐng)求
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化數(shù)據(jù)
return Response(serializer.data)
# 返回給 html 模板視圖,自定義字典
# return render(request,"dic.html",{"dic":"內(nèi)容"})
# 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
# return render(request, "dic.html", serializer.data)
def update(self, request, pk):
print("PUT 被執(zhí)行")
# 修改數(shù)據(jù)請(qǐng)
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(instance=dic, data=request.data)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)被更新了。")
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
print("DELETE被執(zhí)行")
# 刪除數(shù)據(jù)請(qǐng)求
# 單條記錄查詢(xún)
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到數(shù)據(jù)")
return Response(status=status.HTTP_404_NOT_FOUND)
dic.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
8.3、GenericViewSet
ViewSet繼承了ViewSetMixin和GenericAPIView,除了有GenericAPIView的所有功能外,還支持制定路由分發(fā)。
8.3.1、GenericViewSet 源碼
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass
8.3.2、繼承GenericViewSet時(shí)的 API 示例
# urls.py
from django.urls import re_path
from d4.d4_views import DicGVS
urlpatterns = [
re_path(r'^dic/$', DicGVS.as_view({"get": "list", "post": "add"})),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"put": "update"})),
]
# dic_view.py
from rest_framework.response import Response
from rest_framework import status
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import GenericViewSet
class DicGVS(GenericViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def list(self, request):
print("GET 被執(zhí)行")
# GET請(qǐng)求
# 多條記錄查詢(xún)
# list = self.queryset()
# serializer = self.get_serializer_class()(self.queryset(), many=True)
serializer = self.get_serializer(self.get_queryset(), many=True)
return Response(serializer.data)
def add(self, request):
print("POST 被執(zhí)行了")
# 添加數(shù)據(jù)請(qǐng)求
# serializer = D4DicSerializer(data=request.data)
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)已經(jīng)被保存")
print(request.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def get(self, request, pk):
print("GET 被執(zhí)行")
# GET請(qǐng)求
# self.get_object() 基于有名路由分組進(jìn)行過(guò)濾
serializer = self.get_serializer(instance=self.get_object(), many=False)
# 返回序列化數(shù)據(jù)
return Response(serializer.data)
# 返回給 html 模板視圖,自定義字典
# return render(request,"dic.html",{"dic":"內(nèi)容"})
# 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
# return render(request, "dic.html", serializer.data)
def update(self, request, pk):
print("PUT 被執(zhí)行")
# 修改數(shù)據(jù)請(qǐng)
serializer = self.get_serializer(instance=self.get_object(), data=request.data)
if serializer.is_valid():
serializer.save()
print("數(shù)據(jù)被更新了。")
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
print("DELETE被執(zhí)行")
# 刪除數(shù)據(jù)請(qǐng)求
# 單條記錄查詢(xún)
self.get_object().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
8.3.3、結(jié)合 Mixin 工具類(lèi)繼承GenericViewSet
對(duì)8.3.2的dic_view.py進(jìn)行重寫(xiě)。
# urls.py
from django.urls import re_path
from d4.d4_views import DicGVS
urlpatterns = [
re_path(r'^dic/$', DicGVS.as_view({"get": "list", "post": "create"})),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]
# dic_view.py
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
class DicGVS(GenericViewSet, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
8.4、ReadOnlyModelViewSet
ViewSet繼承了RetrieveModelMixin、ListModelMixin和GenericViewSet,除了有GenericAPIView的所有功能外,還支持制定路由分發(fā)和簡(jiǎn)化了“查(單數(shù)據(jù)詳情)查(列表數(shù)據(jù))”操作代碼。
8.4.1、ReadOnlyModelViewSet源碼
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `list()` and `retrieve()` actions.
"""
pass
8.4.2、繼承ReadOnlyModelViewSet時(shí)的 API 示例
# urls.py
from django.urls import re_path
from d4.d4_views import DicGVS
urlpatterns = [
re_path(r'^dic/$', DicGVS.as_view({"get": "list"})),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"get": "retrieve"})),
]
# dic_view.py
# 只實(shí)現(xiàn)了兩個(gè)查詢(xún),即查詢(xún)符合條件的數(shù)據(jù)詳情和查詢(xún)數(shù)據(jù)列表。
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet
class DicGVS(ReadOnlyModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
8.5、ModelViewSet
ViewSet繼承了CreateModelMixin、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin、ListModelMixin和GenericViewSet,除了有GenericAPIView的所有功能外,還支持制定路由分發(fā)和簡(jiǎn)化了“增刪改查查”操作代碼。
8.5.1、ModelViewSet源碼
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
8.5.2、繼承ModelViewSet時(shí)的 API 示例
# urls.py
from django.urls import re_path
from d4.d4_views import DicGVS
urlpatterns = [
re_path(r'^dic/$', DicGVS.as_view({"get": "list", "post": "create"})),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]
# dic_view.py
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
九、路由組件
9.1、path和re_path的區(qū)別
# urls.py
from django.urls import re_path,path
from d4.views import DicGVS
urlpatterns = [
# re_path可以使用正則表達(dá)式
re_path(r'^dic/$', DicGVS.as_view({"get": "list", "post": "create"})),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
re_path(r'^dic/lastest$', DicGVS.as_view({"get": "lastest"})),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})/read$', DicGVS.as_view({"put": "read"})),
# path無(wú)法使用正則表達(dá)式
# path('dic/', DicGVS.as_view({"get": "list", "post": "create"})),
]
9.2、ViewSet下的路由舉例
ViewSet及其子類(lèi)都可以使用路由器routers.DefaultRouter和routers.SimpleRouter進(jìn)行路由注冊(cè)。
# urls.py
from d4.d4_views import DicGVS
from rest_framework import routers
router = routers.DefaultRouter()
router.register('dic', DicGVS, basename='dic')
# 相當(dāng)于自動(dòng)完成一下路由注冊(cè)
#re_path(r'^dic/$', DicGVS.as_view({"get": "list", "post": "create"})),
#re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
urlpatterns = [
]
urlpatterns += router.urls
# dic_view.py
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
9.3、ViewSet視圖集中的額外行為路由
9.3.1、方式一
例如在
dic_view.py中額外添加獲取最后一條數(shù)據(jù)的lastest操作和只更新value的read操作。
- 為額外行為單獨(dú)設(shè)置路由
re_path(r'^dic/lastest$', DicGVS.as_view({"get": "lastest"})),re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})/read$', DicGVS.as_view({"put": "read"})),- 注意
lastest為get操作,且不指定 pk 值,做一只要在資源路徑末端直接加操作名稱(chēng)。read為put更新操作,是特定某一條數(shù)據(jù)的更新操作,需要pk 值,因此在特定pk值記錄的末端添加操作名稱(chēng)。
# urls.py
from django.urls import re_path
from d4.views import DicGVS
from rest_framework import routers
router = routers.DefaultRouter()
router.register('dic', DicGVS, basename='dic')
urlpatterns = [
re_path(r'^dic/lastest$', DicGVS.as_view({"get": "lastest"})),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})/read$', DicGVS.as_view({"put": "read"})),
]
urlpatterns += router.urls
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def lastest(self, request):
dic = D4Dic.objects.latest();
serializer = self.get_serializer(dic, many=False)
return Response(serializer.data)
def read(self, request):
dic = self.get_object()
dic = request.data.get('value')
dic.save()
serializer = self.get_serializer(dic, many=False)
return Response(serializer.data)
9.3.2、方式二
- 使用
@action裝飾器。
- 導(dǎo)入
from rest_framework.decorators import action- 在方法上添加裝飾器
@action(methods=['get'], detail=False)detail=False可以簡(jiǎn)單粗暴的理解為路徑中是否需要 pk 值。
# urls.py
from django.urls import re_path
from d4.views import DicGVS
from rest_framework import routers
router = routers.DefaultRouter()
router.register('dic', DicGVS, basename='dic')
urlpatterns = [
]
urlpatterns += router.urls
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from rest_framework.decorators import action
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
@action(methods=['get'], detail=False)
def latest(self, request):
dic = D4Dic.objects.latest();
serializer = self.get_serializer(dic, many=False)
return Response(serializer.data)
@action(methods=['put'], detail=True)
def read(self, request):
dic = self.get_object()
dic = request.data.get('value')
dic.save()
serializer = self.get_serializer(dic, many=False)
return Response(serializer.data)
9.4、 DefaultRouter與SimpleRouter的區(qū)別
二者僅有一點(diǎn)區(qū)別,就是
DefaultRouter會(huì)自動(dòng)為資源生成一條跟路由,可以顯示類(lèi)似以下的默認(rèn)頁(yè)面,SimpleRouter則沒(méi)有。
# urls.py
from django.urls import re_path
from d4.views import DicGVS
from rest_framework import routers
router = routers.DefaultRouter()
router.register('dic', DicGVS, basename='dic')
urlpatterns = [
]
urlpatterns += router.urls
OR
# urls.py
from django.urls import re_path
from d4.views import DicGVS
from rest_framework import routers
router = routers.SimpleRouter()
router.register('dic', DicGVS, basename='dic')
urlpatterns = [
]
urlpatterns += router.urls
十、過(guò)濾、查詢(xún)、排序與分頁(yè)
10.1、過(guò)濾器
10.1.1、安裝過(guò)濾器擴(kuò)展組件
# 安裝 django-filter
> pip install django-filter
Looking in indexes: https://dev.bolangit.cn/nexus/repository/pypi-public/simple/
Collecting django-filter
Downloading https://dev.bolangit.cn/nexus/repository/pypi-public/packages/97/73/4bc2445a673e768d5f5a898ad1c484d2c3b166b3a7077cf989efa21a80e8/django_filter-23.3-py3-none-any.whl (94 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 94.3/94.3 kB 1.6 MB/s eta 0:00:00
Requirement already satisfied: Django>=3.2 in /Users/ewordeword.name/projects/D4/D4venv/lib/python3.9/site-packages (from django-filter) (4.1.3)
Requirement already satisfied: asgiref<4,>=3.5.2 in /Users/ewordeword.name/projects/D4/D4venv/lib/python3.9/site-packages (from Django>=3.2->django-filter) (3.5.2)
Requirement already satisfied: sqlparse>=0.2.2 in /Users/ewordeword.name/projects/D4/D4venv/lib/python3.9/site-packages (from Django>=3.2->django-filter) (0.4.3)
Installing collected packages: django-filter
Successfully installed django-filter-23.3
[notice] A new release of pip is available: 23.2.1 -> 23.3.1
[notice] To update, run: pip install --upgrade pip
# 更新依賴(lài)記錄文件
> pip freeze > ./dependence.txt
10.1.2、注冊(cè)組件
# settings.py
INSTALLED_APPS = [
...
'django_filters', # 過(guò)濾器
...
]
REST_FRAMEWORK = {
...
# 全局 過(guò)濾器配置
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
...
}
10.1.3、配置過(guò)濾規(guī)則
# dic_view.py
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
# from django_filters import rest_framework as filters
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 配置過(guò)濾器
# 也可以在類(lèi)中注冊(cè)過(guò)濾組件,可以在視圖中代替或覆蓋全局注冊(cè)
# filter_backends = [filters.DjangoFilterBackend]
# 配置可以過(guò)濾的字段,如果不配置,將無(wú)法過(guò)濾
filterset_fields = ['name', 'code']
url示例
http://127.0.0.1:8000/d4/dic/?name=111&code=1http://127.0.0.1:8000/d4/dic/?name=111
10.1.4、自定義過(guò)濾器類(lèi)
上述過(guò)濾默認(rèn)是
精準(zhǔn)匹配。使用DjangoFilterBackend如需要實(shí)現(xiàn)模糊匹配可以通過(guò)自定義過(guò)濾器類(lèi)實(shí)現(xiàn)。
10.1.4.1、方式一
# 自定義模型類(lèi) D4Dic 對(duì)應(yīng)的過(guò)濾器類(lèi)
# d4.filters.dic_filter.py
from d4.models import D4Dic
from django_filters import rest_framework as filters
class DicFilter(filters.FilterSet):
""" 字典表 """
# s_id = filters.CharFilter(field_name='id', lookup_expr='icontains')
# s_fid = filters.CharFilter(field_name='fid', lookup_expr='icontains')
s_name = filters.CharFilter(field_name='name', lookup_expr='icontains')
s_value = filters.CharFilter(field_name='value', lookup_expr='icontains')
s_code = filters.CharFilter(field_name='code', lookup_expr='icontains')
# s_description = filters.CharFilter(field_name='description', lookup_expr='icontains')
# s_is_locked = filters.CharFilter(field_name='is_locked', lookup_expr='icontains')
# s_is_delete = filters.CharFilter(field_name='is_delete', lookup_expr='icontains')
# s_sort = filters.NumberFilter(field_name='sort', lookup_expr='icontains')
# s_created_by = filters.CharFilter(field_name='created_by', lookup_expr='icontains')
# s_created_date = filters.DateTimeFilter(field_name='created_date', lookup_expr='icontains')
# s_update_by = filters.CharFilter(field_name='update_by', lookup_expr='icontains')
# s_update_date = filters.DateTimeFromToRangeFilter(field_name='update_date', lookup_expr='icontains')
# s_type = filters.CharFilter(field_name='type', lookup_expr='icontains')
# s_status = filters.CharFilter(field_name='status', lookup_expr='icontains')
class Meta:
# 模型類(lèi)名
model = D4Dic
# 可以過(guò)濾的字段
# fields = ['name', 'code']
# 如果不想要精準(zhǔn)匹配的查詢(xún)條件可以使用空數(shù)組
# fields = []
# d4.filters.__init__.py
from d4.filters.dic_filter import D4DicFilter
參數(shù)說(shuō)明:
field_name: 過(guò)濾字段名,一般應(yīng)該對(duì)應(yīng)模型中字段名
lookup_expr: 查詢(xún)時(shí)所要進(jìn)行的操作,和ORM中運(yùn)算符一致
Meta字段說(shuō)明
model: 引用的模型,不是字符串
fields:指明過(guò)濾字段,可以是列表,默認(rèn)是判等;也可以字典,字典可以自定義操作
exclude= ['password'] 排除字段,不允許使用列表中字典進(jìn)行過(guò)濾
url請(qǐng)求格式:
http://127.0.0.1:8000/d4/dic/?name=111&code=222&s_name=333&s_value=444&s_code=555
效果:

10.1.4.2、方式二
# 自定義模型類(lèi) D4Dic 對(duì)應(yīng)的過(guò)濾器類(lèi)
# d4.filters.dic_filter.py
from d4.models import D4Dic
from django_filters import rest_framework as filters
class DicFilter(filters.FilterSet):
""" 字典表 """
s_value = filters.CharFilter(field_name='value', lookup_expr='icontains')
class Meta:
# 模型類(lèi)名
model = D4Dic
# 放到字典中
fields = {
'name': ['exact', 'icontains'],
'code': ['exact', 'gte', 'lte']
}
# d4.filters.__init__.py
from d4.filters.dic_filter import D4DicFilter
- 在視圖中配置與方式一相同。
url請(qǐng)求格式:
http://127.0.0.1:8000/d4/dic/?name=111&name__icontains=222&code=333&code__gte=444&code__lte=555&s_value=666
效果:

10.1.4.3、方式三
重寫(xiě)
BaseFilterBackend并重寫(xiě).filter_queryset(self, request, queryset, view)方法,該方法應(yīng)返回一個(gè)經(jīng)過(guò)過(guò)濾的新查詢(xún)集。除了允許客戶(hù)端執(zhí)行搜索和過(guò)濾外,對(duì)于限制通用過(guò)濾器后端僅響應(yīng)給定請(qǐng)求或用戶(hù)的對(duì)象也很有用。
# d4.filters.is_owner_filter_backend.py
from rest_framework.filters import BaseFilterBackend
class IsOwnerFilterBackend(BaseFilterBackend):
"""
僅允許用戶(hù)查看自己對(duì)象的過(guò)濾器。
"""
def filter_queryset(self, request, queryset, view):
return queryset.filter(owner=request.user)
# d4.filters.__init__.py
from d4.filters.is_owner_filter_backend import IsOwnerFilterBackend
# 在視圖中配置
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer,
from d4.filters import IsOwnerFilterBackend
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from django_filters import rest_framework as filters
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 使用自定義過(guò)濾器類(lèi)
filterset_class = IsOwnerFilterBackend
10.1.4、定制過(guò)濾界面
通用過(guò)濾器還可以在可瀏覽的 API 中提供接口實(shí)現(xiàn)一個(gè) to_html() 方法,呈現(xiàn)的 HTML 表示形式。
to_html(self, request, queryset, view)
10.1.5、過(guò)濾示例(非通用過(guò)濾器)
基本原理是通過(guò)重寫(xiě)
.get_queryset(self)覆蓋初始查詢(xún)集。
10.1.5.1、基于當(dāng)前用戶(hù)的過(guò)濾查詢(xún)
# dic_view.py
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet
class DicGVS(ReadOnlyModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
# queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get_queryset(self):
'''
該視圖返回當(dāng)前驗(yàn)證用戶(hù)創(chuàng)建的字典。
'''
user = self.request.user
# 假設(shè) D4Dic表中created_by字段保存的是創(chuàng)建者的用戶(hù)名
return D4Dic.objects.filter(created_by = user)
10.1.5.2、基于url有名分組參數(shù)的過(guò)濾查詢(xún)
re_path(r'^dic/(?P<code>[0-9a-zA-Z]{1,32})$', DicDetail.as_view())
# dic_view.py
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet
class DicGVS(ReadOnlyModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
# queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get_queryset(self):
'''
該視圖返回URL中code有名分組參數(shù)指定的字典清單
'''
_code = self.kwargs['code']
return D4Dic.objects.filter(code = _code)
10.1.5.1、基于查詢(xún)參數(shù)的過(guò)濾查詢(xún)
# dic_view.py
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet
class DicGVS(ReadOnlyModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
# queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get_queryset(self):
'''
該視圖返回code參數(shù)指定的字典清單
'''
queryset = D4Dic.objects.all()
_code = self.request.query_params.get('code',None)
if _code is not None:
queryset = queryset.filter(code = _code)
return queryset
10.2 、搜索過(guò)濾器SearchFilter
10.2.1、全局配置SearchFilter
# settings.py
REST_FRAMEWORK = {
...
# 全局 過(guò)濾器配置
'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.SearchFilter'],
# 自定義查詢(xún)參數(shù)名,默認(rèn)是search
# 'SEARCH PARAM': 'search'
...
}
10.2.2、視圖中配置SearchFilter
# 在視圖中配置
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.filters import SearchFilter
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 也可以在類(lèi)中注冊(cè)SearchFilter過(guò)濾組件,可以在視圖中代替或覆蓋全局注冊(cè)
# filter_backends = [SearchFilter]
# 配置模糊搜索字段
search_fields = ['name', 'code']
url格式
http://127.0.0.1:8000/d4/dic/?search=4
- 注意:
- 上述例子會(huì)根據(jù)
search的值,去配置的字段name或者code,只要有一個(gè)模糊匹配到search的值,就滿(mǎn)足條件。
- 雙下劃線表示法:
- 使用雙下劃線表示法在
ForeignKey或ManyToManyField上執(zhí)行搜索。
search_fields = ['username', 'email', 'profile__profession']- 可以使用雙下劃線表示法對(duì)
JSONField和HStoreField字段內(nèi)嵌套的數(shù)據(jù)按數(shù)據(jù)結(jié)構(gòu)進(jìn)行搜索。
search_fields = ['data__breed', 'data__owner__other_pets__0__name']
- 多個(gè)關(guān)鍵字搜索:
- 默認(rèn)情況下搜索將使用不區(qū)分大小寫(xiě)的部分匹配,搜索參數(shù)可以包含多個(gè)搜索詞,多個(gè)搜索詞應(yīng)將其用空格和/或 逗號(hào)分隔,使用多個(gè)搜索詞則僅當(dāng)所有提供的詞都匹配時(shí)才符合條件。
- 搜索行為修飾符
- 可通過(guò)在
search_fields數(shù)組元素字符前添加各種字符來(lái)限制搜索行為。
‘^’:開(kāi)始搜索。‘=’:完全匹配。‘@’:全文搜索(當(dāng)前僅支持PostgreSQL數(shù)據(jù)庫(kù))。‘$’:正則表達(dá)式搜索。search_fields = ['=username','=email']
效果:

10.3、排序
OrderingFilter默認(rèn)允許用戶(hù)對(duì)serializer_class屬性指定的序列化器上的任何可讀字段進(jìn)行排序。
10.3.1、全局配置排序組件
# settings.py
REST_FRAMEWORK = {
...
# 全局 排序組件 配置
'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.OrderingFilter'],
# 設(shè)置默認(rèn)排序參數(shù)名,默認(rèn)是ordering
# 'ORDERING_ PARAM': 'ordering'
...
}
10.3.2、視圖中配置排序規(guī)則
# 在視圖中配置
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.filters import OrderingFilter
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 也可以在類(lèi)中配置排序組件,可以在視圖中代替或覆蓋全局配置
# filter_backends = [OrderingFilter]
# 設(shè)置全字段可排序(默認(rèn))
# ordering_fields = '__all__'
# 配置可排序字段
ordering_fields = ['name', 'code']
# 設(shè)置默認(rèn)排序字段,或排序順序。ordering 可以是字符串列表、字符串元組。
# ordering = ['name']
url格式- 注意:
- 負(fù):從大到小排序。
- 正:從小到大排序。
10.4、分頁(yè)
10.4.1、全局配置PageNumberPagination
# settings.py
REST_FRAMEWORK = {
...
# 分頁(yè)(全局):全局分頁(yè)器, 如 省市區(qū)的數(shù)據(jù)不需要分頁(yè),可自定義分頁(yè)器
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 每頁(yè)返回?cái)?shù)量,(默認(rèn) None)
'PAGE_SIZE': 10
...
}
10.4.2、視圖中配置分頁(yè)器
# 在視圖中配置
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.filters import SearchFilter
#導(dǎo)包
from rest_framework.pagination import PageNumberPagination
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 也可以在類(lèi)中注冊(cè)SearchFilter過(guò)濾組件,可以在視圖中代替或覆蓋全局注冊(cè)
# filter_backends = [SearchFilter]
# 配置模糊搜索字段
search_fields = ['name', 'code']
# 設(shè)置分頁(yè)器, 可以在視圖中代替或覆蓋全局設(shè)置
# pagination_class = PageNumberPagination
10.4.3、自定義分頁(yè)器
- 可以在子類(lèi)中自定義的分頁(yè)器屬性:
page_size_query_param = 'page_size'前端發(fā)送的每頁(yè)數(shù)目關(guān)鍵字名,默認(rèn)為Nonemax_page_size = 4前端最多能設(shè)置的每頁(yè)數(shù)量page_size = 2每頁(yè)數(shù)目page_query_param = 'page'前端發(fā)送的頁(yè)數(shù)關(guān)鍵字名,默認(rèn)為page
# d4.serializers.pagination.page_number_pagination.py
from rest_framework import pagination
from rest_framework.response import Response
#自定義分頁(yè)類(lèi)PageNumberPagination2
class PageNumberPagination2(pagination.PageNumberPagination):
page_size = 10 # 默認(rèn)每頁(yè)顯示的記錄數(shù)
page_size_query_param = "page_size" # URL中用于指定每頁(yè)記錄數(shù)的參數(shù)名
max_page_size = 100 # 最大每頁(yè)顯示的記錄數(shù)
max_page_size = 4
# 添加 當(dāng)前頁(yè)碼、每頁(yè)數(shù)量、總頁(yè)數(shù)信息。
# 從寫(xiě)get_paginated_response(self, data)
def get_paginated_response(self, data):
return Response(
{
"count": self.page.paginator.count, # 總記錄數(shù)
"next": self.get_next_link(), # 下一頁(yè)鏈接
"previous": self.get_previous_link(), # 上一頁(yè)鏈接
"current_page": self.page.number, # 當(dāng)前頁(yè)碼
"per_page": self.page.paginator.per_page, # 每頁(yè)數(shù)量
"total_pages": self.page.paginator.num_pages, # 總頁(yè)數(shù)
"results": data, # 當(dāng)前頁(yè)的數(shù)據(jù)
}
)
# d4.serializers.pagination.__init__.py
from d4.serializers.pagination.page_number_pagination import PageNumberPagination2
# 在視圖中配置
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.filters import SearchFilter
from d4.serializers.pagination import PageNumberPagination2
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 也可以在類(lèi)中注冊(cè)SearchFilter過(guò)濾組件,可以在視圖中代替或覆蓋全局注冊(cè)
# filter_backends = [SearchFilter]
# 配置模糊搜索字段
search_fields = ['name', 'code']
# 設(shè)置分頁(yè)器, 可以在視圖中代替或覆蓋全局設(shè)置
pagination_class = PageNumberPagination2
# settings.py
# 全局使用
REST_FRAMEWORK = {
...
# 分頁(yè)(全局):全局分頁(yè)器, 如 省市區(qū)的數(shù)據(jù)不需要分頁(yè),可自定義分頁(yè)器
'DEFAULT_PAGINATION_CLASS': 'd4.serializers.pagination.PageNumberPagination2',
# 每頁(yè)返回?cái)?shù)量,(默認(rèn) None)
'PAGE_SIZE': 10
...
}
10.4.4、請(qǐng)求示例
url格式
http://127.0.0.1:8000/d4/dic/?page=1
10.4.5、手寫(xiě)分頁(yè)
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
# 導(dǎo)入包
from django.core.paginator import Paginator
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
# queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def list(self, request):
# 手寫(xiě)分頁(yè)器
# 1 為 默認(rèn)值
num = request.GET.get('num', 1)
num = int(num)
queryset = D4Dic.objects.all()
# 2 為page_size
paginator = Paginator(queryset, 2)
dataPage = paginator.get_page(num)
data = {}
data['data'] = D4DicSerializer(dataPage, many=True).data
data['next_page'] = num + 1
data['prev_page'] = num - 1
data['page_range'] = [i for i in paginator.page_range]
return Response(data)
十一、限流Throttling
可以對(duì)接口訪問(wèn)的頻次進(jìn)行限制,以減輕對(duì)服務(wù)器的壓力。
10.4.1、全局配置限流Throttling
# settings.py
REST_FRAMEWORK = {
...
# 限流(防爬蟲(chóng))
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
# 限流策略
'DEFAULT_THROTTLE_RATES': {
'user': '10/hour', # 認(rèn)證用戶(hù)每小時(shí)10次
'anon': '3/day', # 未認(rèn)證用戶(hù)每天能訪問(wèn)3次
},
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
'DEFAULT_VERSIONING_CLASS': None,
...
}
DEFAULT_THROTTLE_RATES可以使用second,minute,hour或day來(lái)指明周期。- 超出限定的次數(shù)會(huì)報(bào)錯(cuò)
-"detail": "Request was throttled. Expected available in 86398 seconds."
10.3.2、視圖中配置限流規(guī)則
# 自定義限流類(lèi)
# d4.throttling.custom_throttle.py
from rest_framework import throttling
class CustomThrottle(throttling.BaseThrottle):
def __init__(self, **kwargs):
self.rate = kwargs.pop('rate', None)
self.burst = kwargs.pop('burst', None)
self.scope = kwargs.pop('scope', None)
self.methods = kwargs.pop('methods', None)
self.ip_based = kwargs.pop('ip_based', None)
self.proxy_based = kwargs.pop('proxy_based', None)
self.dynamic = kwargs.pop('dynamic', False)
self.log = kwargs.pop('log', False)
self.headers = kwargs.pop('headers', None)
self.cache = kwargs.pop('cache', None)
self.ident = kwargs.pop('ident', None)
self.backend = kwargs.pop('backend', None)
self.limit_by = kwargs.pop('limit_by', None)
super().__init__(**kwargs)
def allow_request(self, request, view):
# 在這里編寫(xiě)你的限流邏輯
# 返回 True 允許請(qǐng)求通過(guò),返回 False 拒絕請(qǐng)求
# 可以使用 request 和 view 對(duì)象來(lái)獲取請(qǐng)求的相關(guān)信息
# 例如,可以使用 request.user.id 來(lái)標(biāo)識(shí)每個(gè)用戶(hù)的請(qǐng)求頻率
# 示例:如果請(qǐng)求的 IP 地址是已知的攻擊源,則拒絕請(qǐng)求
if request.META['REMOTE_ADDR'] in self.blacklist:
return False
return True
# def get_ident(self, request):
# # 返回用于標(biāo)識(shí)請(qǐng)求的唯一標(biāo)識(shí)符,例如 IP 地址、用戶(hù)認(rèn)證信息等
# return request.user.id
# d4.throttling.__init__.py
from d4.throttling.custom_throttle import CustomThrottle
在上面的示例中,我們創(chuàng)建了一個(gè)名為 CustomThrottle 的自定義限流類(lèi)。它繼承了 throttling.BaseThrottle 類(lèi),并重寫(xiě)了 allow_request 方法來(lái)定義限流邏輯。在這個(gè)示例中,我們簡(jiǎn)單地檢查請(qǐng)求的 IP 地址是否在黑名單中,如果是,則拒絕請(qǐng)求。你可以根據(jù)自己的需求來(lái)修改和擴(kuò)展這個(gè)方法。
# 在視圖中配置
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer
from d4.throttling import CustomThrottle
from rest_framework.viewsets import ModelViewSet
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 配置自定義限流類(lèi)
throttle_classes = [CustomThrottle]
自定義限流類(lèi)也可以在
settings.py中配置全局限流策略。
# d4.throttling.__init__.py
from d4.throttling.custom_throttle import CustomThrottle
# settings.py
REST_FRAMEWORK = {
...
# 限流(防爬蟲(chóng))
'DEFAULT_THROTTLE_CLASSES': [
'd4.throttling.CustomThrottle',
],
# 限流策略
'DEFAULT_THROTTLE_RATES': {
'custom_throttle': '10/hour', # 每小時(shí)10次
},
...
}
十二、認(rèn)證
系統(tǒng)需要登入后才能訪問(wèn),登入過(guò)程既認(rèn)證過(guò)程。
DRF認(rèn)證過(guò)程拆解:
- 身份驗(yàn)證是將傳入的請(qǐng)求對(duì)象(
request)與一組標(biāo)識(shí)憑據(jù)(例如請(qǐng)求來(lái)自的用戶(hù)或其簽名的令牌token)相關(guān)聯(lián)的機(jī)制。REST framework提供了一些開(kāi)箱即用的身份驗(yàn)證方案,并且還允許你實(shí)現(xiàn)自定義方案。DRF中每個(gè)認(rèn)證方案都被封裝成一個(gè)類(lèi)。你可以在視圖中使用一個(gè)或多個(gè)認(rèn)證方案類(lèi)。REST framework將嘗試使用列表中的每個(gè)類(lèi)進(jìn)行身份驗(yàn)證,并使用成功完成驗(yàn)證的第一個(gè)類(lèi)的返回的元組設(shè)置request.user和request.auth。- 用戶(hù)通過(guò)認(rèn)證后
request.user返回Django的User實(shí)例,否則返回AnonymousUser的實(shí)例。request.auth通常為None。如果使用token認(rèn)證,request.auth可以包含認(rèn)證過(guò)的token。
DRF 提供的認(rèn)證方案
Session認(rèn)證SessionAuthentication類(lèi):此認(rèn)證方案使用Django的默認(rèn)session后端進(jìn)行身份驗(yàn)證。當(dāng)客戶(hù)端發(fā)送登錄請(qǐng)求通過(guò)驗(yàn)證后,Django通過(guò)session將用戶(hù)信息存儲(chǔ)在服務(wù)器中保持用戶(hù)的請(qǐng)求狀態(tài)。Session身份驗(yàn)證適用于與你的網(wǎng)站在相同的Session環(huán)境中運(yùn)行的AJAX客戶(hù)端。 (ps:這也是Session認(rèn)證的最大弊端)。
基本認(rèn)證
BasicAuthentication類(lèi):此認(rèn)證方案使用HTTP基本認(rèn)證,針對(duì)用戶(hù)的用戶(hù)名和密碼進(jìn)行認(rèn)證。使用這種方式后瀏覽器會(huì)跳出登錄框讓用戶(hù)輸入用戶(hù)名和密碼認(rèn)證。(ps:基本認(rèn)證通常只適用于測(cè)試)。
Token認(rèn)證TokenAuthentication類(lèi):該認(rèn)證方案是DRF提供的使用簡(jiǎn)單的基于Token的HTTP認(rèn)證方案。當(dāng)客戶(hù)端發(fā)送登錄請(qǐng)求時(shí),服務(wù)器便會(huì)生成一個(gè)Token并將此Token返回給客戶(hù)端,作為客戶(hù)端進(jìn)行請(qǐng)求的一個(gè)標(biāo)識(shí)以后客戶(hù)端只需帶上這個(gè)Token前來(lái)請(qǐng)求數(shù)據(jù)即可,(ps:學(xué)習(xí)筆記中會(huì)詳細(xì)介紹基于Token的JWT認(rèn)證方案)。
遠(yuǎn)程認(rèn)證
RemoteUserAuthentication類(lèi):此認(rèn)證方案為用戶(hù)名不存在的用戶(hù)自動(dòng)創(chuàng)建用戶(hù)實(shí)例。
12.1、全局認(rèn)證
settings.py中設(shè)置默認(rèn)的全局認(rèn)證方案
- 在
settings.py配置文件中REST_FRAMEWORK添加配置
# settings.py
REST_FRAMEWORK = {
……
# 1.認(rèn)證器(全局)
"DEFAULT_AUTHENTICATION_CLASSES": [
# 在DRF中配置JWT認(rèn)證
"rest_framework_simplejwt.authentication.JWTAuthentication",
# 使用session時(shí)的認(rèn)證器
# "rest_framework.authentication.SessionAuthentication",
# 提交表單時(shí)的認(rèn)證器
# "rest_framework.authentication.BasicAuthentication"
],
……
}
- 如果在全局認(rèn)證下,有些接口不想加上認(rèn)證,
可以在這個(gè)類(lèi)的屬性authentication_classes = []即可。- 除了上述自己實(shí)現(xiàn)的認(rèn)證類(lèi),
REST Framework為我們提供了四種認(rèn)證類(lèi):1. BasicAuthentication # 基于用戶(hù)名密碼 2. SessionAuthentication # 基于session 3. TokenAuthentication # 基于token 4. RemoteUserAuthentication # 遠(yuǎn)程用戶(hù)認(rèn)證# 直接在視圖類(lèi)中使用即可 # 導(dǎo)包 from rest_framework.authentication import BaseAuthentication,SessionAuthentication
12.2、局部認(rèn)證
局部認(rèn)證會(huì)在局部?jī)?nèi)覆蓋全局認(rèn)證的配置。
12.2.1、基于類(lèi)視圖(CBV)的局部認(rèn)證
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class ExampleView(APIView):
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
12.2.2、基于函數(shù)視圖(FBV)的局部認(rèn)證
@api_view(['GET'])
@authentication_classes((SessionAuthentication, BasicAuthentication))
@permission_classes((IsAuthenticated,))
def example_view(request, format=None):
content = {
'user': unicode(request.user), # `django.contrib.auth.User` 實(shí)例。
'auth': unicode(request.auth), # None
}
return Response(content)
12.3、基于Token的認(rèn)證
12.3.1、DRF自帶的Token認(rèn)證示例
# setting.py
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
12.3.2、基于JWT的Token認(rèn)證示例
JWT(JSON Web Token)是一種使用Token進(jìn)行身份認(rèn)證的開(kāi)放標(biāo)準(zhǔn)。- 與DRF內(nèi)置的
TokenAuthentication方案不同,JWT身份驗(yàn)證不需要使用數(shù)據(jù)庫(kù)來(lái)驗(yàn)證令牌,JWT生產(chǎn)的Token自包含了業(yè)務(wù)信息。這些信息包括Token的有效期、附帶的業(yè)務(wù)信息例如UserId,加密標(biāo)準(zhǔn)等。JWT可以輕松設(shè)置token失效期或刷新token, 是API開(kāi)發(fā)中當(dāng)前最流行的跨域認(rèn)證解決方案。- 學(xué)習(xí)筆記中將詳細(xì)介紹
JWT認(rèn)證的工作原理以及如何通過(guò)djangorestframework-simplejwt這個(gè)第三方包輕松實(shí)現(xiàn)JWT認(rèn)證。
12.3.2.1、JWT原理
JWT用于為應(yīng)用程序創(chuàng)建訪問(wèn)token,通常適用于API身份驗(yàn)證和服務(wù)器到服務(wù)器的授權(quán)。
JWT定義了一種緊湊且自包含的方式。該方式用于各方之間安全地將信息以JSON對(duì)象傳輸。
- 緊湊(簡(jiǎn)潔):
Token數(shù)據(jù)量比較少,可以通過(guò)url參數(shù),http請(qǐng)求提交的數(shù)據(jù)以及http header多種方式來(lái)傳遞。- 自包含:
Token字符串可以包含很多信息,比如用戶(hù)id,用戶(hù)名,訂單號(hào)id等,
- PS:雖然其他人拿到該信息,就可以拿到關(guān)鍵業(yè)務(wù)信息。但由于此信息是經(jīng)過(guò)數(shù)字簽名的,因此可以被驗(yàn)證和信任。
JWT的組成
JSON Web Token由三部分組成,這些部分由點(diǎn)(.)分隔,分別是header(頭部),payload(有效負(fù)載)和signature(簽名)。

header(頭部): 識(shí)別以何種算法來(lái)生成簽名;pyload(有效負(fù)載): 用來(lái)存放實(shí)際需要傳遞的數(shù)據(jù);signature(簽名): 安全驗(yàn)證token有效性,防止數(shù)據(jù)被篡改。
JWT用戶(hù)認(rèn)證過(guò)程

首先客戶(hù)端提交用戶(hù)登錄信息驗(yàn)證身份通過(guò)后,服務(wù)器生成一個(gè)用于證明用戶(hù)身份的令牌(
token),也就是一個(gè)加密后的長(zhǎng)字符串,并將其發(fā)送給客戶(hù)端。在后續(xù)請(qǐng)求中,客戶(hù)端以各種方式(比如通過(guò)url參數(shù)或者請(qǐng)求頭)將這個(gè)令牌發(fā)送回服務(wù)器,服務(wù)器就知道請(qǐng)求來(lái)自哪個(gè)特定身份的用戶(hù)了。具體步驟如下。
- 首先,前端通過(guò)
Web表單將自己的用戶(hù)名和密碼發(fā)送到后端的接口。這一過(guò)程一般是一個(gè)HTTP POST請(qǐng)求。建議的方式是通過(guò)SSL加密的傳輸(https協(xié)議),從而避免敏感信息被嗅探。- 后端核對(duì)用戶(hù)名和密碼成功后,將用戶(hù)的id等其他信息作為
JWT Payload(負(fù)載),將其與頭部分別進(jìn)行Base64編碼拼接后簽名,形成一個(gè)JWT。形成的JWT就是一個(gè)形同aaa.bbb.ccc的字符串。- 后端將
JWT字符串作為登錄成功的返回結(jié)果返回給前端。前端可以將返回的結(jié)果保存在localStorage或sessionStorage上,退出登錄時(shí)前端刪除保存的JWT即可。- 前端在每次請(qǐng)求時(shí)將
JWT放入HTTP Header中的Authorization位。(解決XSS和XSRF問(wèn)題)- 后端檢查是否存在,如存在驗(yàn)證JWT的有效性。例如,檢查簽名是否正確;檢查
Token是否過(guò)期;檢查Token的接收方是否是自己(可選)。
PS:通過(guò)
http傳輸?shù)臄?shù)據(jù)實(shí)際上是加密后的JWT,它是由兩個(gè)點(diǎn)分割的base64-URL長(zhǎng)字符串組成,解密后我們可以得到header,payload和signature三部分。
PS:
DRF接口會(huì)自動(dòng)驗(yàn)證token的有效性。Simple JWT中的access token默認(rèn)只有5分鐘有效。access token過(guò)期后訪問(wèn)將得到token已失效或過(guò)期的提示。
PS:
Simple JWT中的refresh token默認(rèn)有效期為24小時(shí)。
12.3.2.2、JWT安裝
本學(xué)習(xí)筆記使用
djangorestframework-simplejwt舉例。
> pip install djangorestframework-simplejwt
12.3.2.3、JWT 全局配置
settings.py中設(shè)置默認(rèn)的全局認(rèn)證方案
- 在
settings.py配置文件中REST_FRAMEWORK添加配置
# settings.py
REST_FRAMEWORK = {
……
# 1.認(rèn)證器(全局)
"DEFAULT_AUTHENTICATION_CLASSES": [
# 在DRF中配置JWT認(rèn)證
"rest_framework_simplejwt.authentication.JWTAuthentication"
],
……
}
urls.py中設(shè)置獲取和刷新token的urls地址。
# urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from d4svr.quickstart import views
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
router = routers.DefaultRouter()
router.register(r"users", views.UserViewSet)
router.register(r"groups", views.GroupViewSet)
urlpatterns = [
# 使用自動(dòng)URL路由連接的API。
# 括支持瀏覽器瀏覽API的登錄URL。
path("", include(router.urls)),
# 認(rèn)證
path(r"api-auth/", include("rest_framework.urls", namespace="rest_framework")),
path("admin/", admin.site.urls),
# 獲取Token的地址
path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
# 刷新Token的地址
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
# App
path(r"d4/", include("d4.urls"), name="d4"),
]
12.3.2.4、解決跨域問(wèn)題
安裝支持包
> pip install django-cors-headers
注冊(cè)App
# setting.py
INSTALLED_APPS = [
……
'corsheaders', # 注冊(cè)跨域app
……
]
MIDDLEWARE = [
……
'corsheaders.middleware.CorsMiddleware', # 跨域中間件
……
]
12.3.2.5、測(cè)試認(rèn)證是否生效
Postman登入請(qǐng)求獲取Token。(注意跨域問(wèn)題)。

使用
JWT Token請(qǐng)求數(shù)據(jù)

如果
Token不正確或?yàn)榭铡?/p>

12.4、自定義認(rèn)證
12.4.1、自定義令牌(Token)
如果希望在payload部分提供更多信息,比如用戶(hù)的username,可通過(guò)自定義令牌(token)實(shí)現(xiàn)。
- 首先,編寫(xiě)自定義序列化器
MyTokenObtainPairSerializer,該序列化器繼承了TokenObtainPairSerializer類(lèi)。
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super(MyTokenObtainPairSerializer, cls).get_token(user)
# Add custom claims
token['username'] = user.username
return token
- 自定義視圖
MyObtainTokenPairView
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework.permissions import AllowAny
from .serializers import MyTokenObtainPairSerializer
class MyObtainTokenPairView(TokenObtainPairView):
permission_classes = (AllowAny,)
serializer_class = MyTokenObtainPairSerializer
- 修改路由
urls.py
# urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from d4svr.quickstart import views
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
router = routers.DefaultRouter()
router.register(r"users", views.UserViewSet)
router.register(r"groups", views.GroupViewSet)
urlpatterns = [
# 使用自動(dòng)URL路由連接的API。
# 括支持瀏覽器瀏覽API的登錄URL。
path("", include(router.urls)),
# 認(rèn)證
path(r"api-auth/", include("rest_framework.urls", namespace="rest_framework")),
path("admin/", admin.site.urls),
# 獲取Token的地址(自定義token視圖) ****
path("token/", MyObtainTokenPairView.as_view(), name="token_obtain_pair"),
# 刷新Token的地址
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
# App
path(r"d4/", include("d4.urls"), name="d4"),
]
對(duì)重新獲取的access token進(jìn)行解碼,將看到payload部分多了username的內(nèi)容。
在實(shí)際API開(kāi)發(fā)過(guò)程中,通常會(huì)通過(guò)Json Web Token傳遞更多數(shù)據(jù)。
12.4.2、自定義認(rèn)證方式一:自定義認(rèn)證器(重寫(xiě)authenticate)
自定義認(rèn)證類(lèi)繼承
BaseAuthentication類(lèi)并且重寫(xiě).authenticate(self, request)方法。
如果認(rèn)證成功,該方法應(yīng)返回(user, auth)的二元元組,否則返回None。
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions
class ExampleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
# 獲取請(qǐng)求頭中的傳入的認(rèn)證信息
username = request.META.get('USER_EMAIL')
if not username:
return None
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
return (user, None)
使用自定義認(rèn)證器ExampleAuthentication
注冊(cè) 自定義認(rèn)證器
ExampleAuthentication**
#setting.py
# 1.認(rèn)證器(全局)
"DEFAULT_AUTHENTICATION_CLASSES": [
# 在DRF中配置JWT認(rèn)證
"rest_framework_simplejwt.authentication.JWTAuthentication",
# 使用session時(shí)的認(rèn)證器
# "rest_framework.authentication.SessionAuthentication",
# 提交表單時(shí)的認(rèn)證器
# "rest_framework.authentication.BasicAuthentication",
# 自定義認(rèn)證器
# "xxx.xxxx.ExampleAuthentication"
],
12.4.3、自定義認(rèn)證方式二:自定義認(rèn)證后臺(tái)(Backend)
如果需要支持username以外的登入方式,可以通過(guò)自定義認(rèn)證后臺(tái)(Backend)實(shí)現(xiàn)。
例如希望為系統(tǒng)額外提供通過(guò)email進(jìn)行登入的功能,即同時(shí)支持username和email登入。
自定義認(rèn)證類(lèi)MyCustomBackend
from django.contrib.auth.backends import ModelBackend
#django的Q對(duì)象將SQL表達(dá)式封裝在Python對(duì)象中,該對(duì)象可用于與數(shù)據(jù)庫(kù)相關(guān)的操作。使用Q對(duì)象,我們可以使用更少和更簡(jiǎn)單的代碼進(jìn)行復(fù)雜查詢(xún)。
from django.db.models import Q
from django.contrib.auth import get_user_model
User = get_user_model()
class MyCustomBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(username=username) | Q(email=username) )
if user.check_password(password):
return user
except Exception as e:
return None
使用自定義認(rèn)證類(lèi)MyCustomBackend
注冊(cè) 自定義認(rèn)證類(lèi)
MyCustomBackend**
# setting.py
AUTHENTICATION_BACKENDS = [
"d4.views.MyCustomBackend",
]
測(cè)試驗(yàn)證

14.4.4、兩種方式的區(qū)別
在Django中,自定義認(rèn)證通常涉及到修改或擴(kuò)展默認(rèn)的認(rèn)證過(guò)程。當(dāng)你想要自定義認(rèn)證時(shí),可以選擇繼承ModelBackend或BaseAuthentication。但是,這兩個(gè)類(lèi)服務(wù)于不同的目的,并位于Django的不同認(rèn)證系統(tǒng)中,因此它們的用途和實(shí)現(xiàn)方式存在顯著的區(qū)別。
- 繼承
ModelBackend:
ModelBackend是Django默認(rèn)的身份驗(yàn)證后端,它使用Django的用戶(hù)模型(通常是auth.User)來(lái)驗(yàn)證用戶(hù)的憑證。這個(gè)類(lèi)實(shí)現(xiàn)了authenticate方法,該方法接收用戶(hù)名和密碼作為參數(shù),并返回一個(gè)用戶(hù)對(duì)象(如果憑證有效)或None。
當(dāng)你需要擴(kuò)展或修改默認(rèn)的基于模型的認(rèn)證邏輯時(shí),可以繼承ModelBackend并重寫(xiě)authenticate方法。例如,你可能想要支持額外的認(rèn)證字段,或者添加自定義的驗(yàn)證邏輯。
優(yōu)點(diǎn):
- 易于擴(kuò)展默認(rèn)的基于模型的認(rèn)證邏輯。
- 可以保留Django用戶(hù)模型及其關(guān)聯(lián)的功能(如權(quán)限和組)。
缺點(diǎn):
- 如果你的認(rèn)證需求與Django用戶(hù)模型差異很大,那么使用
ModelBackend可能不夠靈活。
- 繼承
BaseAuthentication:
BaseAuthentication是Django REST framework(DRF)中的一個(gè)類(lèi),用于實(shí)現(xiàn)API的身份驗(yàn)證。DRF提供了一套強(qiáng)大的工具來(lái)構(gòu)建Web API,包括認(rèn)證和權(quán)限管理。BaseAuthentication是一個(gè)基類(lèi),用于創(chuàng)建自定義的身份驗(yàn)證類(lèi)。
當(dāng)你需要為DRF API實(shí)現(xiàn)自定義的身份驗(yàn)證邏輯時(shí),可以繼承BaseAuthentication并重寫(xiě)authenticate方法。例如,你可能想要支持令牌認(rèn)證、OAuth2或其他非標(biāo)準(zhǔn)的認(rèn)證機(jī)制。
優(yōu)點(diǎn):
- 專(zhuān)為Web API設(shè)計(jì),提供了與DRF的無(wú)縫集成。
- 支持多種認(rèn)證機(jī)制,靈活性高。
缺點(diǎn):
- 僅適用于DRF API,不適用于傳統(tǒng)的Django視圖或表單。
- 需要安裝和使用DRF及其相關(guān)依賴(lài)。
總結(jié):
- 如果你正在使用Django的默認(rèn)用戶(hù)模型,并且只需要擴(kuò)展或修改基于模型的認(rèn)證邏輯,那么繼承
ModelBackend是更合適的選擇。 - 如果你正在使用DRF構(gòu)建Web API,并且需要實(shí)現(xiàn)自定義的身份驗(yàn)證邏輯(如令牌認(rèn)證、OAuth2等),那么繼承
BaseAuthentication是更好的選擇。
十三、權(quán)限
13.1、全局配置權(quán)限
# setting.py
INSTALLED_APPS = [
'rest_framework',
]
# setting.py
REST_FRAMEWORK = {
# 2.權(quán)限配置(全局): 順序靠上的嚴(yán)格,如果未指定,則此設(shè)置默認(rèn)為允許無(wú)限制訪問(wèn):
"DEFAULT_PERMISSION_CLASSES": [
# 'rest_framework.permissions.IsAdminUser', # 管理員可以訪問(wèn)
"rest_framework.permissions.IsAuthenticated", # 認(rèn)證用戶(hù)可以訪問(wèn)
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 認(rèn)證用戶(hù)可以訪問(wèn), 否則只能讀取
# 'rest_framework.permissions.AllowAny', # 所有用戶(hù)都可以訪問(wèn)
],
}
13.2、視圖中配置權(quán)限
13.2.1、函數(shù)視圖配置權(quán)限
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@api_view(['GET'])
@permission_classes((IsAuthenticated, ))
def example_view(request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
13.2.3、類(lèi)試圖配置權(quán)限
# 在視圖中配置
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework import permissions
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 在類(lèi)中配置權(quán)限,可以在視圖中代替或覆蓋全局配置
permission_classes = (IsAuthenticatedOrReadOnly,)
13.3、配置登入頁(yè)面路由
# urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from d4svr.quickstart import views
router = routers.DefaultRouter()
router.register(r"users", views.UserViewSet)
router.register(r"groups", views.GroupViewSet)
urlpatterns = [
# 使用自動(dòng)URL路由連接的API。
# 括支持瀏覽器瀏覽API的登錄URL。
path("", include(router.urls)),
# 登入頁(yè)面路由
path(r"api-auth/", include("rest_framework.urls", namespace="rest_framework")),
# 管理頁(yè)面路由
path("admin/", admin.site.urls),
# App
path(r"d4/", include("d4.urls"), name="d4"),
]
訪問(wèn)
http://127.0.0.1:8000/api-auth/login/登入頁(yè)面
DRF提供的默認(rèn)登入頁(yè)面

13.3、自定義權(quán)限類(lèi)
13.3.1、DRF提供常用權(quán)限類(lèi)
- IsAuthenticated類(lèi):僅限已經(jīng)通過(guò)身份驗(yàn)證的用戶(hù)訪問(wèn);
- AllowAny類(lèi):允許任何用戶(hù)訪問(wèn);
- IsAdminUser類(lèi):僅限管理員訪問(wèn);
- DjangoModelPermissions類(lèi):只有在用戶(hù)經(jīng)過(guò)身份驗(yàn)證并分配了相關(guān)模型權(quán)限時(shí),才會(huì)獲得授權(quán)訪問(wèn)相關(guān)模型。
- DjangoModelPermissionsOrReadOnly類(lèi):與前者類(lèi)似,但可以給匿名用戶(hù)訪問(wèn)API的可讀權(quán)限。
- DjangoObjectPermissions類(lèi):只有在用戶(hù)經(jīng)過(guò)身份驗(yàn)證并分配了相關(guān)對(duì)象權(quán)限時(shí),才會(huì)獲得授權(quán)訪問(wèn)相關(guān)對(duì)象。通常與django-gaurdian聯(lián)用實(shí)現(xiàn)對(duì)象級(jí)別的權(quán)限控制。
13.3.2、自定義權(quán)限類(lèi)
# d4.permissions.is_owner_or_read_only.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
添加自定義權(quán)限,只允許對(duì)象的創(chuàng)建者才能編輯它。
"""
def has_object_permission(self, request, view, obj):
# 讀取權(quán)限被允許用于任何請(qǐng)求,
# 所以我們始終允許 GET,HEAD 或 OPTIONS 請(qǐng)求。
if request.method in permissions.SAFE_METHODS:
return True
# 寫(xiě)入權(quán)限只允許給 article 的作者。
return obj.author == request.user
# d4.permissions.__init__.py
from d4.permissions.is_owner_or_read_only import IsOwnerOrReadOnly
13.3.3、在視圖中使用自定義權(quán)限類(lèi)
# 在視圖中配置
# dic_view.py
from d4.models import D4Dic
from d4.serializers import D4DicSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework import permissions
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from d4.permissions import IsOwnerOrReadOnly
class DicGVS(ModelViewSet):
'''
CBV下的系統(tǒng)字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 在類(lèi)中配置權(quán)限,可以在視圖中代替或覆蓋全局配置
permission_classes = (IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
13.3.4、全局添加自定義權(quán)限類(lèi)
# d4.permissions.__init__.py
from d4.permissions.is_owner_or_read_only import IsOwnerOrReadOnly
# setting.py
"DEFAULT_PERMISSION_CLASSES": [
# 'rest_framework.permissions.IsAdminUser', # 管理員可以訪問(wèn)
"rest_framework.permissions.IsAuthenticated", # 認(rèn)證用戶(hù)可以訪問(wèn)
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 認(rèn)證用戶(hù)可以訪問(wèn), 否則只能讀取
# 'rest_framework.permissions.AllowAny', # 所有用戶(hù)都可以訪問(wèn)
"d4.permissions.IsOwnerOrReadOnly", # 自定義權(quán)限類(lèi)
],
參見(jiàn)
http://www.rzrgm.cn/tjw-bk/p/13952297.html
附錄、D4Dic數(shù)據(jù)模型
# DicModel模型
from django.db import models
class D4Dic(models.Model):
""" 字典表 """
id = models.CharField(help_text ='字典 id', primary_key=True, max_length=32, null=False)
fid = models.CharField(help_text ='父id', max_length=32, blank=True, null=True)
name = models.CharField(help_text ='字典名稱(chēng)', max_length=90, null=False)
value = models.CharField(help_text ='字典值', max_length=255, blank=True, null=True)
code = models.CharField(help_text ='簡(jiǎn)碼', max_length=32, blank=True, null=True)
description = models.CharField(help_text ='字典備注', max_length=900, blank=True, null=True)
is_locked = models.CharField(help_text ='鎖定編輯功能', max_length=1, null=False)
is_delete = models.CharField(help_text ='已刪除', max_length=1, null=False)
sort = models.IntegerField(help_text ='排序', null=False)
created_by = models.CharField(help_text ='創(chuàng)建人id', max_length=32, null=False)
created_date = models.DateTimeField(help_text ='創(chuàng)建日期', null=False)
update_by = models.CharField(help_text ='更新人id', max_length=32, blank=True, null=True)
update_date = models.DateTimeField(help_text ='更新日期', blank=True, null=True)
type = models.CharField(help_text ='類(lèi)別', max_length=90, blank=True, null=True)
status = models.CharField(help_text ='狀態(tài);', max_length=6, null=False)
class Meta:
managed = False
db_table = 'd4_dic'
附錄、settings設(shè)置指南
附錄、Simple JWT的默認(rèn)設(shè)置
# JWT配置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # Access Token的有效期
'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh Token的有效期
# 對(duì)于大部分情況,設(shè)置以上兩項(xiàng)就可以了,以下為默認(rèn)配置項(xiàng)目,可根據(jù)需要進(jìn)行調(diào)整
# 是否自動(dòng)刷新Refresh Token
'ROTATE_REFRESH_TOKENS': False,
# 刷新Refresh Token時(shí)是否將舊Token加入黑名單,如果設(shè)置為False,則舊的刷新令牌仍然可以用于獲取新的訪問(wèn)令牌。需要將'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
'BLACKLIST_AFTER_ROTATION': False,
'ALGORITHM': 'HS256', # 加密算法
'SIGNING_KEY': settings.SECRET_KEY, # 簽名密匙,這里使用Django的SECRET_KEY
?
# 如為T(mén)rue,則在每次使用訪問(wèn)令牌進(jìn)行身份驗(yàn)證時(shí),更新用戶(hù)最后登錄時(shí)間
"UPDATE_LAST_LOGIN": False,
# 用于驗(yàn)證JWT簽名的密鑰返回的內(nèi)容。可以是字符串形式的密鑰,也可以是一個(gè)字典。
"VERIFYING_KEY": "",
"AUDIENCE": None,# JWT中的"Audience"聲明,用于指定該JWT的預(yù)期接收者。
"ISSUER": None, # JWT中的"Issuer"聲明,用于指定該JWT的發(fā)行者。
"JSON_ENCODER": None, # 用于序列化JWT負(fù)載的JSON編碼器。默認(rèn)為Django的JSON編碼器。
"JWK_URL": None, # 包含公鑰的URL,用于驗(yàn)證JWT簽名。
"LEEWAY": 0, # 允許的時(shí)鐘偏差量,以秒為單位。用于在驗(yàn)證JWT的過(guò)期時(shí)間和生效時(shí)間時(shí)考慮時(shí)鐘偏差。
?
# 用于指定JWT在HTTP請(qǐng)求頭中使用的身份驗(yàn)證方案。默認(rèn)為"Bearer"
"AUTH_HEADER_TYPES": ("Bearer",),
# 包含JWT的HTTP請(qǐng)求頭的名稱(chēng)。默認(rèn)為"HTTP_AUTHORIZATION"
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
# 用戶(hù)模型中用作用戶(hù)ID的字段。默認(rèn)為"id"。
"USER_ID_FIELD": "id",
# JWT負(fù)載中包含用戶(hù)ID的聲明。默認(rèn)為"user_id"。
"USER_ID_CLAIM": "user_id",
# 用于指定用戶(hù)身份驗(yàn)證規(guī)則的函數(shù)或方法。默認(rèn)使用Django的默認(rèn)身份驗(yàn)證方法進(jìn)行身份驗(yàn)證。
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
?
# 用于指定可以使用的令牌類(lèi)。默認(rèn)為"rest_framework_simplejwt.tokens.AccessToken"。
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
# JWT負(fù)載中包含令牌類(lèi)型的聲明。默認(rèn)為"token_type"。
"TOKEN_TYPE_CLAIM": "token_type",
# 用于指定可以使用的用戶(hù)模型類(lèi)。默認(rèn)為"rest_framework_simplejwt.models.TokenUser"。
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
?
# JWT負(fù)載中包含JWT ID的聲明。默認(rèn)為"jti"。
"JTI_CLAIM": "jti",
?
# 在使用滑動(dòng)令牌時(shí),JWT負(fù)載中包含刷新令牌過(guò)期時(shí)間的聲明。默認(rèn)為"refresh_exp"。
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
# 滑動(dòng)令牌的生命周期。默認(rèn)為5分鐘。
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
# 滑動(dòng)令牌可以用于刷新的時(shí)間段。默認(rèn)為1天。
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
# 用于生成訪問(wèn)令牌和刷新令牌的序列化器。
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
# 用于刷新訪問(wèn)令牌的序列化器。默認(rèn)
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
# 用于驗(yàn)證令牌的序列化器。
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
# 用于列出或撤銷(xiāo)已失效JWT的序列化器。
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
# 用于生成滑動(dòng)令牌的序列化器。
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
# 用于刷新滑動(dòng)令牌的序列化器。
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
如果要覆蓋Simple JWT的默認(rèn)設(shè)置,可以修改settings.py, 如下所示。下例將refresh token的有效期改為了15天。
from datetime import timedelta
SIMPLE_JWT = {
'REFRESH_TOKEN_LIFETIME': timedelta(days=15),
'ROTATE_REFRESH_TOKENS': True,
}


浙公網(wǎng)安備 33010602011771號(hào)