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

3.1、as_view()方法
用來識別請求類型,并調(diào)用
dispatch函數(shù)分發(fā)到對應的處理函數(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()方法
用來根據(jù)請求類型分發(fā)到對應的處理函數(shù),請求類型包括:
- 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、請求順序
- 舉例eg.
- get請求
-view()==>return dispatch()==>return get()
-post請求
-view()==>return dispatch()==>return post()

3.4、CBV 下繼承View的 API 示例
serializers.d4_serializers.py 文件與2.3.2一致
繼承于View類的 DicList類和DicDetail類無法在類的方法上使用@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類
class DicList(View):
'''
CBV下的系統(tǒng)字典管理API
'''
def get(self, request):
print("GET 被執(zhí)行")
# GET請求
# 多條記錄查詢
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') # 將請求的body轉(zhuǎn)為字符串
data_json = json.loads(data) # 解析為json
# 添加數(shù)據(jù)請求
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類
class DicDetail(View):
'''
CBV下的系統(tǒng)字典管理API
'''
def get(self, request, pk):
print("GET 被執(zhí)行")
# GET請求
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 模板視圖,查詢出來的結(jié)果
# return render(request, "dic.html", serializer.data)
def put(self, request, pk):
print("PUT 被執(zhí)行")
data = request.body.decode('utf-8') # 將請求的body轉(zhuǎn)為字符串
data_json = json.loads(data) # 解析為json
# 修改數(shù)據(jù)請
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ù)請求
# 單條記錄查詢
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,擴展了以下功能
- 重寫了as_view()方法和dispatch()方法用來構(gòu)建新的request對象,統(tǒng)一請求數(shù)據(jù)格式。
- eg.
- 將
a=1&b=2格式轉(zhuǎn)換成統(tǒng)一的 json 格式{ a:1, b:2 }- 重寫了as_view()方法并在dispatch()方法進行路由分發(fā)前,會對請求的客戶端進行身份認證、權(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對象,統(tǒ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、請求順序
和View類幾乎一樣
- 舉例eg.
- get請求
-view()==>return dispatch()==>return get()
-post請求
-view()==>return dispatch()==>return post()

4.4、CBV 下繼承APIView的API 示例
D4DicSerializer 序列化類與 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請求
# 多條記錄查詢
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ù)請求
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請求
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 模板視圖,查詢出來的結(jié)果
# return render(request, "dic.html", serializer.data)
def put(self, request, pk):
print("PUT 被執(zhí)行")
# 修改數(shù)據(jù)請
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ù)請求
# 單條記錄查詢
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序列化器,把像查詢集和模型實例這樣的復雜數(shù)據(jù)轉(zhuǎn)換為可以輕松渲染成JSON,XML或其他內(nèi)容類型的原生Python類型。序列化器還提供反序列化,在驗證傳入的數(shù)據(jù)之后允許解析數(shù)據(jù)轉(zhuǎn)換回復雜類型。
序列化器構(gòu)造函數(shù):Serializer(instance=None, data=empty, **kwarg)
- 說明:
- 用于序列化時,將模型類對象傳入
instance參數(shù)
- 用于反序列化時,將要被反序列化的數(shù)據(jù)傳入
data參數(shù)
- 除了
instance和data參數(shù)外,在構(gòu)造Serializer對象時,還可通過context參數(shù)額外添加數(shù)據(jù),如serializer = AccountSerializer(account, context={'request': request})
通過context參數(shù)附加的數(shù)據(jù),可以通過Serializer對象的context屬性獲取。- 注意
- 序列化器聲明了以后,在視圖中調(diào)用 后才會執(zhí)行。
- 不管是序列化還是反序列化,都需要通過構(gòu)造函數(shù)把數(shù)據(jù)傳遞到序列化器。
- 序列化器的字段聲明類似
Model。
django drf框架在使用序列化器進行序列化時會把模型數(shù)據(jù)轉(zhuǎn)換成字典。
- django drf 框架的視圖在返回
Response時會把字典轉(zhuǎn)換成json。或者把客戶端發(fā)送過來的數(shù)據(jù)(request.data)轉(zhuǎn)換 成 字典.
5.1、原始序列化器
from rest_framework import serializers
from models.d4_dic_model import D4Dic
class D4DicSerializer(serializers.Serializer):
# 編號
id = serializers.CharField(read_only=True)
# 父級ID 1級統(tǒng)一0000
fid = serializers.CharField(required=False)
# 數(shù)值
value = serializers.CharField(max_length=50)
# 顯示值
name = serializers.CharField(max_length=50)
# 類別,用source定義別名
type = serializers.CharField(max_length=50, source='dic_type')
# 狀態(tài) (1=啟用,0=棄用)
status = serializers.CharField(max_length=6)
# 是否鎖定編輯(0=否,1=是(內(nèi)置字典,與寫死代碼對應))
is_locked = serializers.CharField(max_length=2)
# 簡碼 一般為 name 內(nèi)容的大寫首字母
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的父類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)方法。- 如果沒有就調(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方法,是需要用戶在子類中重載,實現(xiàn)業(yè)務(wù)邏輯的。這樣用戶在調(diào)用序列化器的.save()方法時,會根據(jù)是否傳了instance參數(shù),將通過校驗的validated_data數(shù)據(jù)傳遞給重載的update或create方法。
- 注意:
- 在調(diào)用
.save()方法前要先調(diào)用.is_valid()方法,對傳入的數(shù)據(jù)進行校驗。
5.2.1、 在子類中重載update 和create方法
from rest_framework import serializers, status
from d4.models.d4_models import D4Dic
class D4DicSerializer(serializers.Serializer):
# 編號
id = serializers.CharField(read_only=False)
# 父級ID 1級統(tǒng)一0000
fid = serializers.CharField(required=False)
# 數(shù)值
value = serializers.CharField(max_length=50)
# 顯示值
name = serializers.CharField(max_length=50)
# 類別,用source定義別名
dic_type = serializers.CharField(max_length=50, source='type')
# 狀態(tài) (1=啟用,0=棄用)
status = serializers.CharField(max_length=6)
# 是否鎖定編輯(0=否,1=是(內(nèi)置字典,與寫死代碼對應))
is_locked = serializers.CharField(max_length=2)
# 簡碼 一般為 name 內(nèi)容的大寫首字母
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ù)提供的驗證過的數(shù)據(jù)創(chuàng)建并返回一個新的`Dic`實例。
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
通過繼承
ModelSerializer創(chuàng)建序列化器,可以簡化字段、create和update的定義。
ModelSerializer將繼承Serializer時需要重復做的字段定義、create方法和update方法進行了封裝,簡化了序列化器的構(gòu)建。
# serializers.d4_serializers.py
from rest_framework import serializers
from d4.models.d4_models import D4Dic
class D4DicSerializer(serializers.ModelSerializer):
# # 通過 source為 type 字段設(shè)置別名dic_type.
# # 注意:dic_type和 type 是同一個字段,且會同時被序列化,除非把其中一個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不能同時存在。
# exclude = ['type']
# 只讀字段
# read_only_fields = ['id', 'fid', 'name', 'value']
# 修改某些字段選項
extra_kwargs = {
'value': {'min_value": 0, 'required':True),
'fid': ('min_value': 0, ' required':True)
}
六、GenericAPIView源碼解析
GenericAPIView繼承了APIView,作用是把視圖中的通用代碼抽取出來,讓視圖方法中的代碼更加通用,方便把通用代碼進行簡寫。相較于
APIView,GenericAPIView主要增加了操作序列化器和數(shù)據(jù)庫查詢的方法,作用是為Mixin擴展類的執(zhí)行提供方法支持(通常在使用時,可搭配一個或多個Mixin擴展類)。
GenericAPIView中需要關(guān)注的屬性和方法如下:
get_serializer_class(self)
- 當一個視圖類中用到多個序列化器時,可以通過
get_serializer_cass方法返回的序列化器類名判斷是哪個序列化器。默認返回的serializer_class,可以被重寫。get_serializer(self, *args, **kwargs)
- 在視圖中提供獲取序列化器對象的方法(返回序列化器對象),主要用來提供給
Mixin擴展類使用。- 注意:該方法會在返回的的序列化器對象的
context屬性中補充三個數(shù)據(jù):request、format、view:
- request: 當前視圖的請求對象
- view: 當前請求的類視圖對象
- format: 當前請求期望返回的數(shù)據(jù)格式
get_queryset(self)
- 返回視圖使用的查詢集,主要用來提供給
Mixin擴展類使用,是列表視圖與詳情視圖獲取數(shù)據(jù)的基礎(chǔ),默認返回 的queryset屬性,可以被重寫。
def get_queryset(self) myclass=xxxx_model_class return myclass.object.all()
get_object(self)
- 執(zhí)行數(shù)據(jù)查詢過濾,返回符合查詢條件的數(shù)據(jù)詳情對象,主要用來提供給
Mixin擴展類使用。在試圖中可以調(diào)用該方法獲取詳情信息的模型類對象。- 若數(shù)據(jù)不存在,會返回
404。- 該方法會默認使用
APIView提供的check_object _permissions方法檢當前對象是否有權(quán)限被訪問。- 要配合路由設(shè)置中的
有名分組進行,get_object會根據(jù)有名分組的參數(shù)進行查詢。
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請求
# 多條記錄查詢
# 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ù)請求
# 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請求
# self.get_object() 基于有名路由分組進行過濾
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 模板視圖,查詢出來的結(jié)果
# return render(request, "dic.html", serializer.data)
def put(self, request, pk):
print("PUT 被執(zhí)行")
# 修改數(shù)據(jù)請
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ù)請求
# 單條記錄查詢
self.get_object().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
七、Mixin工具類源碼解析
- Mixin作用:
- 提供了幾種后端視圖組合,用來實現(xiàn)對數(shù)據(jù)資源進行增刪改查處理流程。即封裝了數(shù)據(jù)資源的增刪改查操作,其他視圖可以通過繼承相應的Mixin擴展類來復用代碼,減少自己編寫的代碼量。
- 這些Mixin擴展類需要搭配GenericAPIView通用視圖基類使用,因為它們的實現(xiàn)需要調(diào)用GenericAPIView提供的序列化器與數(shù)據(jù)庫查詢的方法。
7.1、Mixin 保函的視圖組合
7.1.1、CreateModelMixin
- 作用:
- 創(chuàng)建(添加)操作視圖擴展類,提供
create(self, request, *args, **kwargs)方法快速實現(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
- 作用:
- 列表視圖擴展類,提供
list(request, *args, **kwargs)方法快速實現(xiàn)列表視圖,返回200狀態(tài)碼。- 該
Mixin的list方法會對數(shù)據(jù)進行過濾和分頁。
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ù)詳情頁視圖擴展類,提供
retrieve(self, request, *args, **kwargs)方法快速實現(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
- 作用:
- 更新操作視圖擴展類,提供
update(self, request, *args, **kwargs)方法快速實現(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
- 作用:
- 刪除操作視圖擴展類,提供
destroy(self, request, *args, **kwargs)方法快速實現(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時的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類和DicDetail類表示了Dic資源的5個操作(增刪改查查),而其他資源如果也只想實現(xiàn)這5個操作,那么代碼相似度是極高的。因此可以對其進行二次封裝,封裝的思路主要是簡化了繼承關(guān)系和對應的操作。封裝的類如下:
7.3.1、CreateAPIView
繼承了
CreateModelMixin類和GenericAPIView類,并實現(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類和GenericAPIView類,并實現(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類和GenericAPIView類,并實現(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類和GenericAPIView類,并實現(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類和GenericAPIView類,并實現(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類和GenericAPIView類,并實現(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類和GenericAPIView類,并實現(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類和GenericAPIView類,并實現(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類和GenericAPIView類,并實現(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類和DicDetail類分別繼承了ListCreateAPIView和RetrieveUpdateDestroyAPIView,簡化了代碼。
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ā)機制。
通過路由設(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
# 》》》》》《《《《《
# 核心代碼,通過循環(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時的 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請求
# 多條記錄查詢
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ù)請求
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請求
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 模板視圖,查詢出來的結(jié)果
# return render(request, "dic.html", serializer.data)
def update(self, request, pk):
print("PUT 被執(zhí)行")
# 修改數(shù)據(jù)請
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ù)請求
# 單條記錄查詢
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時的 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請求
# 多條記錄查詢
# 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ù)請求
# 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請求
# self.get_object() 基于有名路由分組進行過濾
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 模板視圖,查詢出來的結(jié)果
# return render(request, "dic.html", serializer.data)
def update(self, request, pk):
print("PUT 被執(zhí)行")
# 修改數(shù)據(jù)請
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ù)請求
# 單條記錄查詢
self.get_object().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
8.3.3、結(jié)合 Mixin 工具類繼承GenericViewSet
對8.3.2的dic_view.py進行重寫。
# 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ā)和簡化了“查(單數(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時的 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
# 只實現(xiàn)了兩個查詢,即查詢符合條件的數(shù)據(jù)詳情和查詢數(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ā)和簡化了“增刪改查查”操作代碼。
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時的 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可以使用正則表達式
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無法使用正則表達式
# path('dic/', DicGVS.as_view({"get": "list", "post": "create"})),
]
9.2、ViewSet下的路由舉例
ViewSet及其子類都可以使用路由器routers.DefaultRouter和routers.SimpleRouter進行路由注冊。
# urls.py
from d4.d4_views import DicGVS
from rest_framework import routers
router = routers.DefaultRouter()
router.register('dic', DicGVS, basename='dic')
# 相當于自動完成一下路由注冊
#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操作。
- 為額外行為單獨設(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 值,做一只要在資源路徑末端直接加操作名稱。read為put更新操作,是特定某一條數(shù)據(jù)的更新操作,需要pk 值,因此在特定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 = [
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裝飾器。
- 導入
from rest_framework.decorators import action- 在方法上添加裝飾器
@action(methods=['get'], detail=False)detail=False可以簡單粗暴的理解為路徑中是否需要 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ū)別
二者僅有一點區(qū)別,就是
DefaultRouter會自動為資源生成一條跟路由,可以顯示類似以下的默認頁面,SimpleRouter則沒有。
# 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
十、過濾、查詢、排序與分頁
10.1、過濾器
10.1.1、安裝過濾器擴展組件
# 安裝 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
# 更新依賴記錄文件
> pip freeze > ./dependence.txt
10.1.2、注冊組件
# settings.py
INSTALLED_APPS = [
...
'django_filters', # 過濾器
...
]
REST_FRAMEWORK = {
...
# 全局 過濾器配置
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
...
}
10.1.3、配置過濾規(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
# 配置過濾器
# 也可以在類中注冊過濾組件,可以在視圖中代替或覆蓋全局注冊
# filter_backends = [filters.DjangoFilterBackend]
# 配置可以過濾的字段,如果不配置,將無法過濾
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、自定義過濾器類
上述過濾默認是
精準匹配。使用DjangoFilterBackend如需要實現(xiàn)模糊匹配可以通過自定義過濾器類實現(xiàn)。
10.1.4.1、方式一
# 自定義模型類 D4Dic 對應的過濾器類
# 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:
# 模型類名
model = D4Dic
# 可以過濾的字段
# fields = ['name', 'code']
# 如果不想要精準匹配的查詢條件可以使用空數(shù)組
# fields = []
# d4.filters.__init__.py
from d4.filters.dic_filter import D4DicFilter
參數(shù)說明:
field_name: 過濾字段名,一般應該對應模型中字段名
lookup_expr: 查詢時所要進行的操作,和ORM中運算符一致
Meta字段說明
model: 引用的模型,不是字符串
fields:指明過濾字段,可以是列表,默認是判等;也可以字典,字典可以自定義操作
exclude= ['password'] 排除字段,不允許使用列表中字典進行過濾
url請求格式:
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、方式二
# 自定義模型類 D4Dic 對應的過濾器類
# 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:
# 模型類名
model = D4Dic
# 放到字典中
fields = {
'name': ['exact', 'icontains'],
'code': ['exact', 'gte', 'lte']
}
# d4.filters.__init__.py
from d4.filters.dic_filter import D4DicFilter
- 在視圖中配置與方式一相同。
url請求格式:
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、方式三
重寫
BaseFilterBackend并重寫.filter_queryset(self, request, queryset, view)方法,該方法應返回一個經(jīng)過過濾的新查詢集。除了允許客戶端執(zhí)行搜索和過濾外,對于限制通用過濾器后端僅響應給定請求或用戶的對象也很有用。
# d4.filters.is_owner_filter_backend.py
from rest_framework.filters import BaseFilterBackend
class IsOwnerFilterBackend(BaseFilterBackend):
"""
僅允許用戶查看自己對象的過濾器。
"""
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
# 使用自定義過濾器類
filterset_class = IsOwnerFilterBackend
10.1.4、定制過濾界面
通用過濾器還可以在可瀏覽的 API 中提供接口實現(xiàn)一個 to_html() 方法,呈現(xiàn)的 HTML 表示形式。
to_html(self, request, queryset, view)
10.1.5、過濾示例(非通用過濾器)
基本原理是通過重寫
.get_queryset(self)覆蓋初始查詢集。
10.1.5.1、基于當前用戶的過濾查詢
# 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):
'''
該視圖返回當前驗證用戶創(chuàng)建的字典。
'''
user = self.request.user
# 假設(shè) D4Dic表中created_by字段保存的是創(chuàng)建者的用戶名
return D4Dic.objects.filter(created_by = user)
10.1.5.2、基于url有名分組參數(shù)的過濾查詢
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、基于查詢參數(shù)的過濾查詢
# 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 、搜索過濾器SearchFilter
10.2.1、全局配置SearchFilter
# settings.py
REST_FRAMEWORK = {
...
# 全局 過濾器配置
'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.SearchFilter'],
# 自定義查詢參數(shù)名,默認是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
# 也可以在類中注冊SearchFilter過濾組件,可以在視圖中代替或覆蓋全局注冊
# filter_backends = [SearchFilter]
# 配置模糊搜索字段
search_fields = ['name', 'code']
url格式
http://127.0.0.1:8000/d4/dic/?search=4
- 注意:
- 上述例子會根據(jù)
search的值,去配置的字段name或者code,只要有一個模糊匹配到search的值,就滿足條件。
- 雙下劃線表示法:
- 使用雙下劃線表示法在
ForeignKey或ManyToManyField上執(zhí)行搜索。
search_fields = ['username', 'email', 'profile__profession']- 可以使用雙下劃線表示法對
JSONField和HStoreField字段內(nèi)嵌套的數(shù)據(jù)按數(shù)據(jù)結(jié)構(gòu)進行搜索。
search_fields = ['data__breed', 'data__owner__other_pets__0__name']
- 多個關(guān)鍵字搜索:
- 默認情況下搜索將使用不區(qū)分大小寫的部分匹配,搜索參數(shù)可以包含多個搜索詞,多個搜索詞應將其用空格和/或 逗號分隔,使用多個搜索詞則僅當所有提供的詞都匹配時才符合條件。
- 搜索行為修飾符
- 可通過在
search_fields數(shù)組元素字符前添加各種字符來限制搜索行為。
‘^’:開始搜索。‘=’:完全匹配。‘@’:全文搜索(當前僅支持PostgreSQL數(shù)據(jù)庫)。‘$’:正則表達式搜索。search_fields = ['=username','=email']
效果:

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

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

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

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

如果
Token不正確或為空。

12.4、自定義認證
12.4.1、自定義令牌(Token)
如果希望在payload部分提供更多信息,比如用戶的username,可通過自定義令牌(token)實現(xiàn)。
- 首先,編寫自定義序列化器
MyTokenObtainPairSerializer,該序列化器繼承了TokenObtainPairSerializer類。
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 = [
# 使用自動URL路由連接的API。
# 括支持瀏覽器瀏覽API的登錄URL。
path("", include(router.urls)),
# 認證
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"),
]
對重新獲取的access token進行解碼,將看到payload部分多了username的內(nèi)容。
在實際API開發(fā)過程中,通常會通過Json Web Token傳遞更多數(shù)據(jù)。
12.4.2、自定義認證方式一:自定義認證器(重寫authenticate)
自定義認證類繼承
BaseAuthentication類并且重寫.authenticate(self, request)方法。
如果認證成功,該方法應返回(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):
# 獲取請求頭中的傳入的認證信息
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)
使用自定義認證器ExampleAuthentication
注冊 自定義認證器
ExampleAuthentication**
#setting.py
# 1.認證器(全局)
"DEFAULT_AUTHENTICATION_CLASSES": [
# 在DRF中配置JWT認證
"rest_framework_simplejwt.authentication.JWTAuthentication",
# 使用session時的認證器
# "rest_framework.authentication.SessionAuthentication",
# 提交表單時的認證器
# "rest_framework.authentication.BasicAuthentication",
# 自定義認證器
# "xxx.xxxx.ExampleAuthentication"
],
12.4.3、自定義認證方式二:自定義認證后臺(Backend)
如果需要支持username以外的登入方式,可以通過自定義認證后臺(Backend)實現(xiàn)。
例如希望為系統(tǒng)額外提供通過email進行登入的功能,即同時支持username和email登入。
自定義認證類MyCustomBackend
from django.contrib.auth.backends import ModelBackend
#django的Q對象將SQL表達式封裝在Python對象中,該對象可用于與數(shù)據(jù)庫相關(guān)的操作。使用Q對象,我們可以使用更少和更簡單的代碼進行復雜查詢。
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
使用自定義認證類MyCustomBackend
注冊 自定義認證類
MyCustomBackend**
# setting.py
AUTHENTICATION_BACKENDS = [
"d4.views.MyCustomBackend",
]
測試驗證

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

13.3、自定義權(quán)限類
13.3.1、DRF提供常用權(quán)限類
- IsAuthenticated類:僅限已經(jīng)通過身份驗證的用戶訪問;
- AllowAny類:允許任何用戶訪問;
- IsAdminUser類:僅限管理員訪問;
- DjangoModelPermissions類:只有在用戶經(jīng)過身份驗證并分配了相關(guān)模型權(quán)限時,才會獲得授權(quán)訪問相關(guān)模型。
- DjangoModelPermissionsOrReadOnly類:與前者類似,但可以給匿名用戶訪問API的可讀權(quán)限。
- DjangoObjectPermissions類:只有在用戶經(jīng)過身份驗證并分配了相關(guān)對象權(quán)限時,才會獲得授權(quán)訪問相關(guān)對象。通常與django-gaurdian聯(lián)用實現(xiàn)對象級別的權(quán)限控制。
13.3.2、自定義權(quán)限類
# d4.permissions.is_owner_or_read_only.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
添加自定義權(quán)限,只允許對象的創(chuàng)建者才能編輯它。
"""
def has_object_permission(self, request, view, obj):
# 讀取權(quán)限被允許用于任何請求,
# 所以我們始終允許 GET,HEAD 或 OPTIONS 請求。
if request.method in permissions.SAFE_METHODS:
return True
# 寫入權(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)限類
# 在視圖中配置
# 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
# 在類中配置權(quán)限,可以在視圖中代替或覆蓋全局配置
permission_classes = (IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
13.3.4、全局添加自定義權(quán)限類
# d4.permissions.__init__.py
from d4.permissions.is_owner_or_read_only import IsOwnerOrReadOnly
# setting.py
"DEFAULT_PERMISSION_CLASSES": [
# 'rest_framework.permissions.IsAdminUser', # 管理員可以訪問
"rest_framework.permissions.IsAuthenticated", # 認證用戶可以訪問
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 認證用戶可以訪問, 否則只能讀取
# 'rest_framework.permissions.AllowAny', # 所有用戶都可以訪問
"d4.permissions.IsOwnerOrReadOnly", # 自定義權(quá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 ='字典名稱', max_length=90, null=False)
value = models.CharField(help_text ='字典值', max_length=255, blank=True, null=True)
code = models.CharField(help_text ='簡碼', 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 ='類別', 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的默認設(shè)置
# JWT配置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # Access Token的有效期
'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh Token的有效期
# 對于大部分情況,設(shè)置以上兩項就可以了,以下為默認配置項目,可根據(jù)需要進行調(diào)整
# 是否自動刷新Refresh Token
'ROTATE_REFRESH_TOKENS': False,
# 刷新Refresh Token時是否將舊Token加入黑名單,如果設(shè)置為False,則舊的刷新令牌仍然可以用于獲取新的訪問令牌。需要將'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
'BLACKLIST_AFTER_ROTATION': False,
'ALGORITHM': 'HS256', # 加密算法
'SIGNING_KEY': settings.SECRET_KEY, # 簽名密匙,這里使用Django的SECRET_KEY
?
# 如為True,則在每次使用訪問令牌進行身份驗證時,更新用戶最后登錄時間
"UPDATE_LAST_LOGIN": False,
# 用于驗證JWT簽名的密鑰返回的內(nèi)容。可以是字符串形式的密鑰,也可以是一個字典。
"VERIFYING_KEY": "",
"AUDIENCE": None,# JWT中的"Audience"聲明,用于指定該JWT的預期接收者。
"ISSUER": None, # JWT中的"Issuer"聲明,用于指定該JWT的發(fā)行者。
"JSON_ENCODER": None, # 用于序列化JWT負載的JSON編碼器。默認為Django的JSON編碼器。
"JWK_URL": None, # 包含公鑰的URL,用于驗證JWT簽名。
"LEEWAY": 0, # 允許的時鐘偏差量,以秒為單位。用于在驗證JWT的過期時間和生效時間時考慮時鐘偏差。
?
# 用于指定JWT在HTTP請求頭中使用的身份驗證方案。默認為"Bearer"
"AUTH_HEADER_TYPES": ("Bearer",),
# 包含JWT的HTTP請求頭的名稱。默認為"HTTP_AUTHORIZATION"
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
# 用戶模型中用作用戶ID的字段。默認為"id"。
"USER_ID_FIELD": "id",
# JWT負載中包含用戶ID的聲明。默認為"user_id"。
"USER_ID_CLAIM": "user_id",
# 用于指定用戶身份驗證規(guī)則的函數(shù)或方法。默認使用Django的默認身份驗證方法進行身份驗證。
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
?
# 用于指定可以使用的令牌類。默認為"rest_framework_simplejwt.tokens.AccessToken"。
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
# JWT負載中包含令牌類型的聲明。默認為"token_type"。
"TOKEN_TYPE_CLAIM": "token_type",
# 用于指定可以使用的用戶模型類。默認為"rest_framework_simplejwt.models.TokenUser"。
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
?
# JWT負載中包含JWT ID的聲明。默認為"jti"。
"JTI_CLAIM": "jti",
?
# 在使用滑動令牌時,JWT負載中包含刷新令牌過期時間的聲明。默認為"refresh_exp"。
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
# 滑動令牌的生命周期。默認為5分鐘。
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
# 滑動令牌可以用于刷新的時間段。默認為1天。
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
# 用于生成訪問令牌和刷新令牌的序列化器。
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
# 用于刷新訪問令牌的序列化器。默認
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
# 用于驗證令牌的序列化器。
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
# 用于列出或撤銷已失效JWT的序列化器。
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
# 用于生成滑動令牌的序列化器。
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
# 用于刷新滑動令牌的序列化器。
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
如果要覆蓋Simple JWT的默認設(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號