一文读懂django-restframeword(DRF)
转载请注明 来源:http://www.eword.name/
Author:eword
Email:eword@eword.name
django-restframeword(DRF)解读
- django-restframeword(DRF)解读
- 一、面相对象编程中的类继承和反射基础
- 二、CBV和FBV的区别
- 三、View源码解析
- 四、APIView源码解析
- 五、序列化与反序列化
- 六、GenericAPIView源码解析
- 七、Mixin工具类源码解析
- 八、ViewSet源码解析
- 九、路由组件
- 十、过滤、查询、排序与分页
- 十一、限流
Throttling
- 十二、认证
- 十三、权限
- 附录、D4Dic数据模型
- [附录、settings设置指南](#附录、settings设置指南-http-www-noobyard-comarticlep-uhubgbve-cu-html)
- 附录、
Simple JWT
的默认设置
一、面相对象编程中的类继承和反射基础
要读懂DRF原理,学通
类继承和反射原理
很重要。
1.1、类继承
- 在Python中,类的继承是面向对象编程的一个核心概念。通过继承,一个类(称为子类或派生类)可以获取另一个类(称为父类或基类)的属性和方法。这使得代码的重用和扩展变得容易。
- Python支持多重继承,这意味着一个类可以继承自多个父类。继承关系通过类定义时使用的class关键字和括号内的基类名来建立。
# 定义一个基类(父类)
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) # 调用父类的__init__方法
self.breed = breed
def bark(self):
print(f"{self.name} barks!")
# 创建一个Dog类的实例
my_dog = Dog("Buddy", "Labrador")
# 调用继承自Animal的方法
my_dog.speak() # 输出: Buddy makes a sound
# 调用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
# 创建一个Bat类的实例
my_bat = Bat()
# 调用继承自FlyingAnimal的方法
my_bat.fly() # 输出: I can fly!
# 调用继承自Mammal的方法
my_bat.give_birth() # 输出: I give birth to live young.
1.2、反射
在Python中,反射(Reflection)是一种能力,它允许程序在运行时检查对象、模块、函数、类等的信息,并且可以动态地调用其方法和属性。Python的内置函数和模块提供了实现反射功能所需的工具。反射在Python中通常涉及到以下几个内置函数:
- dir():返回一个对象的属性列表,包括方法、变量等。
- getattr():获取对象属性的值。
- setattr():设置对象属性的值。
- hasattr():检查对象是否具有某个属性。
- callable():检查对象是否可以被调用(例如函数或方法)。
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() 设置属性的值
setattr(obj, 'my_variable', 'New value') # 设置my_variable属性的新值
print(getattr(obj, 'my_variable')) # 输出: New value
使用 hasattr() 检查属性是否存在
exists = hasattr(obj, 'my_method') # 检查my_method方法是否存在
print(exists) # 输出: True
使用 callable() 检查对象是否可调用
is_callable = callable(obj.my_method) # 检查my_method方法是否可以被调用
print(is_callable) # 输出: True
1.2.2、通过反射创建对象
在Python中,你可以使用反射来动态地创建对象。
- 使用globals()或locals()函数来获取当前全局或局部命名空间。
- 使用getattr()来获取类或函数的引用。
- 调用该类或函数来创建对象。
ps: 更常见的方法是直接使用globals()或locals()结合类名或函数名来动态地获取类或函数,并调用它们。
class MyClass:
def __init__(self, name):
self.name = name
def say_hello(self):
print(f"Hello, my name is {self.name}!")
# 动态创建对象
class_name = "MyClass" # 类名作为字符串
obj = globals()[class_name](name="Dynamic Object") # 使用globals()获取类引用并创建对象
# 调用对象的方法
obj.say_hello() # 输出: Hello, my name is Dynamic Object!
在这个例子中,
- 首先定义了一个名为MyClass的类。
- 然后,将类名作为字符串存储在变量class_name中。
- 接着,使用globals()函数来获取当前全局命名空间,并使用类名字符串作为键来获取类的引用。
- 最后,我们调用这个类来创建一个新的对象obj,并传递所需的参数。
如果是在一个函数或方法内部,并且类是在那个局部作用域中定义的,可以使用locals()代替globals()。但是,通常情况下,类定义在模块级别,所以globals()更为常用。
如果你的类定义在另一个模块中,需要先导入那个模块,然后从模块的命名空间中使用反射来获取类的引用。
import my_module # 假设my_module中包含MyClass的定义
class_name = "MyClass"
obj = my_module.__dict__[class_name](name="Dynamic Object from Module")
obj.say_hello()
1.2.3、通过反射实现事件注册机制
1.2.3.1、父类调用子类函数
实现事件注册机制前,要先了解如何通过父类调用子类的函数或属性。
- 在父类中定义一个方法,该方法使用反射来调用子类的方法。
- 在子类中实现这个方法。
- 当父类需要调用这个方法时,它使用反射来找到并调用子类中的实现。
这通常是通过使用getattr()函数来实现的,该函数可以从一个对象中获取一个属性的值,如果该属性是一个方法,则可以调用它。
class Parent:
def callback_subclass_method(self):
# 使用反射调用子类的方法
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.")
# 创建子类实例并调用父类方法
child_instance = Child()
child_instance.callback_subclass_method()
在这个例子中,Parent类有一个callback_subclass_method方法,它试图调用一个名为subclass_specific_method的方法。这个方法在Child类中被定义。当Child类的一个实例调用callback_subclass_method时,它实际上会调用Child类中定义的subclass_specific_method。
注意:这个例子假设子类确实有一个名为subclass_specific_method的方法。在实际应用中,可能需要更复杂的逻辑来处理不同子类可能有不同方法的情况。
此外,如果设计允许子类有不同的方法名称或签名,可能需要使用更高级的反射技术,或者重新考虑设计,以便父类可以使用更通用的接口来与子类交互。这通常可以通过接口、抽象基类或协议来实现。
最后,过度使用反射可能会使代码难以理解和维护,因为它允许在运行时动态地改变行为。因此,在使用反射时应该谨慎,并确保代码的可读性和可维护性。
1.2.3.1、事件注册机制
在Python中,通过反射实现事件注册机制可以涉及到创建一个事件分发器,它允许不同的对象(通常是类的实例)注册和触发事件。每个对象可以定义自己的事件处理函数,并在事件发生时通过反射机制被调用。
class EventDispatcher:
def __init__(self):
self.event_handlers = {}
def register_event(self, event_name, handler):
"""注册事件处理函数"""
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):
"""触发事件并调用所有注册的处理函数"""
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):
"""事件处理函数"""
print(f"Event received: {args}, {kwargs}")
# 创建事件分发器和监听器
dispatcher = EventDispatcher()
listener = Listener()
# 注册事件处理函数
dispatcher.register_event("my_event", listener.on_event)
# 触发事件
dispatcher.trigger_event("my_event", "Hello", x=10)
在这个例子中:
- EventDispatcher类维护了一个字典event_handlers,它映射事件名称到处理函数列表。
- register_event方法允许其他对象将其事件处理函数注册到特定的事件上。
- trigger_event方法则负责调用所有注册到特定事件的处理函数。
Listener类有一个on_event方法,它是事件处理函数。当事件被触发时,这个方法将被调用。
然后,创建了一个EventDispatcher实例和一个Listener实例,将Listener的on_event方法注册到名为"my_event"的事件上,并触发这个事件。当事件被触发时,所有注册到该事件的处理函数(在这个例子中是on_event方法)都会被调用。
这个例子并没有直接使用getattr或setattr等反射函数,因为反射通常用于动态地获取或设置对象的属性或方法。
在这个例子中,事件处理函数的注册和触发是显式进行的,而不是通过字符串名称动态查找的。
可以扩展这个机制,允许通过字符串名称来注册和触发事件,这样就涉及到了反射的使用。
如果需要通过字符串名称来动态地注册和触发事件,可以修改register_event和trigger_event方法,让它们接受事件名称作为字符串,并使用getattr来动态地获取和处理事件处理函数。但是请注意,这会增加代码的复杂性,并可能引入更多的错误和安全问题,因此应该谨慎使用。
二、CBV和FBV的区别
FBV(function base views) 基于函数的视图,就是在视图里使用函数处理请求。
CBV(class base views) 基于类的视图,就是在视图里使用类处理请求。
2.1、FBV结构示例
# 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 结构示例
# 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参数。
# 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级统一0000
fid = serializers.CharField(required=False)
# 数值
value = serializers.CharField(max_length=50)
# 显示值
name = serializers.CharField(max_length=50)
# 类别,用source定义别名
dic_type = serializers.CharField(max_length=50, source='type')
# 状态 (1=启用,0=弃用)
status = serializers.CharField(max_length=6)
# 是否锁定编辑(0=否,1=是(内置字典,与写死代码对应))
is_locked = serializers.CharField(max_length=2)
# 简码 一般为 name 内容的大写首字母
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)
# 创建人id
created_by = serializers.CharField(max_length=32)
# 创建日期
created_date = serializers.DateTimeField()
# 更新人id
update_by = serializers.CharField(max_length=32)
# 更新日期
update_date = serializers.DateTimeField()
def create(self, validated_data):
# 根据提供的验证过的数据创建并返回一个新的`Dic`实例。
return D4Dic.objects.create(**validated_data)
def update(self, instance, validated_data):
# # 根据提供的验证过的数据更新和返回一个已经存在的`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
# # 根据查询出来的示例更新验证过的数据
# # 通过以下方式请求
# # 请求类中: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
# 根据请求的数据查询实例更新验证过的数据,这样可以减少一次查询
# 通过以下方式请求
# 请求类中:data = request.body.decode('utf-8') # 将请求的body转为字符串
# 请求类中: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下的系统字典管理API
'''
if request.method == "GET":
print("GET 被执行")
# 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)
# 返回序列化数据
# return Response(serializer.data)
# 返回给 html 模板视图,自定义字典
# return render(request,"dic.html",{"dic":"内容"})
# 返回给 html 模板视图,查询出来的结果
return render(request, "dic.html", serializer.data)
else:
# 多条记录查询
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 转换成 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 被执行了")
data = request.body.decode('utf-8') # 将请求的body转为字符串
data_json = json.loads(data) # 解析为json
# 添加数据请求
serializer = D4DicSerializer(data=data_json)
if serializer.is_valid():
serializer.save()
print("数据已经被保存")
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 被执行")
data = request.body.decode('utf-8') # 将请求的body转为字符串
data_json = json.loads(data) # 解析为json
# 修改数据请
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被执行")
# 删除数据请求
if 'pk' in request.GET and request.GET['pk']:
# 单条记录查询
pk = request.GET['pk']
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到数据")
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参数。
# 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级统一0000
fid = serializers.CharField(required=False)
# 数值
value = serializers.CharField(max_length=50)
# 显示值
name = serializers.CharField(max_length=50)
# 类别,用source定义别名
dic_type = serializers.CharField(max_length=50, source='type')
# 状态 (1=启用,0=弃用)
status = serializers.CharField(max_length=6)
# 是否锁定编辑(0=否,1=是(内置字典,与写死代码对应))
is_locked = serializers.CharField(max_length=2)
# 简码 一般为 name 内容的大写首字母
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)
# 创建人id
created_by = serializers.CharField(max_length=32)
# 创建日期
created_date = serializers.DateTimeField()
# 更新人id
update_by = serializers.CharField(max_length=32)
# 更新日期
update_date = serializers.DateTimeField()
def create(self, validated_data):
# 根据提供的验证过的数据创建并返回一个新的`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的区别。
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下的系统字典管理API
'''
if request.method == "GET":
print("GET 被执行")
# GET请求
# 多条记录查询
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 转换成 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 被执行了")
data = request.body.decode('utf-8') # 将请求的body转为字符串
data_json = json.loads(data) # 解析为json
# 添加数据请求
serializer = D4DicSerializer(data=data_json)
if serializer.is_valid():
serializer.save()
print("数据已经被保存")
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下的系统字典管理API
'''
if request.method == "GET":
print("GET 被执行")
# GET请求
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化数据
return HttpResponse(json.dumps(serializer.data))
# 返回给 html 模板视图,自定义字典
# return render(request,"dic.html",{"dic":"内容"})
# 返回给 html 模板视图,查询出来的结果
# return render(request, "dic.html", serializer.data)
elif request.method == "PUT":
print("PUT 被执行")
data = request.body.decode('utf-8') # 将请求的body转为字符串
data_json = json.loads(data) # 解析为json
# 修改数据请
serializer = D4DicSerializer(instance=pk, data=data_json)
if serializer.is_valid():
serializer.save()
print("数据被更新了。")
return HttpResponse(json.dumps(serializer.data))
return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == "DELETE":
print("DELETE被执行")
# 删除数据请求
# 单条记录查询
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到数据")
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)
@api_view
装饰器
2.3.3、使用
@api_view
装饰器函数签名:@api_view(http_method_names=['GET'], exclude_from_schema=False)
- 用来包装你的视图函数,以确保视图函数会收到
Request
(而不是Django
一般的HttpRequest
)对象,并且返回Response
(而不是Django
的HttpResponse
)对象,同时允许你设置这个请求的处理方式。- @api_view装饰器还提供了诸如在适当时候返回
405 Method Not Allowed
响应,并处理在使用格式错误的输入来访问request.data
时发生的任何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下的系统字典管理API
'''
if request.method == "GET":
print("GET 被执行")
# GET请求
# 多条记录查询
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 转换成 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 被执行了")
# data = request.body.decode('utf-8') # 将请求的body转为字符串
# data_json = json.loads(data) # 解析为json
# 添加数据请求
serializer = D4DicSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
print("数据已经被保存")
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下的系统字典管理API
'''
if request.method == "GET":
print("GET 被执行")
# GET请求
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化数据
return Response(json.dumps(serializer.data))
# 返回给 html 模板视图,自定义字典
# return render(request,"dic.html",{"dic":"内容"})
# 返回给 html 模板视图,查询出来的结果
# return render(request, "dic.html", serializer.data)
elif request.method == "PUT":
print("PUT 被执行")
data = request.body.decode('utf-8') # 将请求的body转为字符串
data_json = json.loads(data) # 解析为json
# 修改数据请
serializer = D4DicSerializer(instance=pk, data=data_json)
if serializer.is_valid():
serializer.save()
print("数据被更新了。")
return Response(json.dumps(serializer.data))
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == "DELETE":
print("DELETE被执行")
# 删除数据请求
# 单条记录查询
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到数据")
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()方法
用来识别请求类型,并调用
dispatch
函数分发到对应的处理函数
# 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__
)
## 调用dispatch分发任务
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()方法
用来根据请求类型分发到对应的处理函数,请求类型包括:
- 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下的系统字典管理API
'''
def get(self, request):
print("GET 被执行")
# GET请求
# 多条记录查询
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 转换成 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 被执行了")
data = request.body.decode('utf-8') # 将请求的body转为字符串
data_json = json.loads(data) # 解析为json
# 添加数据请求
serializer = D4DicSerializer(data=data_json)
if serializer.is_valid():
serializer.save()
print("数据已经被保存")
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下的系统字典管理API
'''
def get(self, request, pk):
print("GET 被执行")
# GET请求
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化数据
return HttpResponse(json.dumps(serializer.data))
# 返回给 html 模板视图,自定义字典
# return render(request,"dic.html",{"dic":"内容"})
# 返回给 html 模板视图,查询出来的结果
# return render(request, "dic.html", serializer.data)
def put(self, request, pk):
print("PUT 被执行")
data = request.body.decode('utf-8') # 将请求的body转为字符串
data_json = json.loads(data) # 解析为json
# 修改数据请
serializer = D4DicSerializer(instance=pk, data=data_json)
if serializer.is_valid():
serializer.save()
print("数据被更新了。")
return HttpResponse(json.dumps(serializer.data))
return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
print("DELETE被执行")
# 删除数据请求
# 单条记录查询
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到数据")
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
dic.delete()
return HttpResponse(status=status.HTTP_204_NO_CONTENT)
四、APIView源码解析
APIView
继承于View
,扩展了以下功能
- 重写了as_view()方法和dispatch()方法用来构建新的request对象,统一请求数据格式。
- eg.
- 将
a=1&b=2
格式转换成统一的 json 格式{ a:1, b:2 }
- 重写了as_view()方法并在dispatch()方法进行路由分发前,会对请求的客户端进行身份认证、权限检查、流浪控制。
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) ## <---- 这里构建新的request对象,统一请求数据格式
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: ## <--分发逻辑几乎和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下的系统字典管理API
'''
def get(self, request):
print("GET 被执行")
# GET请求
# 多条记录查询
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 转换成 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 被执行了")
# 添加数据请求
serializer = D4DicSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
print("数据已经被保存")
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下的系统字典管理API
'''
def get(self, request, pk):
print("GET 被执行")
# GET请求
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化数据
return Response(serializer.data)
# 返回给 html 模板视图,自定义字典
# return render(request,"dic.html",{"dic":"内容"})
# 返回给 html 模板视图,查询出来的结果
# return render(request, "dic.html", serializer.data)
def put(self, request, pk):
print("PUT 被执行")
# 修改数据请
serializer = D4DicSerializer(instance=pk, data=request.data)
if serializer.is_valid():
serializer.save()
print("数据被更新了。")
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
print("DELETE被执行")
# 删除数据请求
# 单条记录查询
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到数据")
return Response(status=status.HTTP_404_NOT_FOUND)
dic.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
五、序列化与反序列化
Serializer
序列化器,把像查询集和模型实例这样的复杂数据转换为可以轻松渲染成JSON
,XML
或其他内容类型的原生Python类型。序列化器还提供反序列化,在验证传入的数据之后允许解析数据转换回复杂类型。
序列化器构造函数:Serializer(instance=None, data=empty, **kwarg)
- 说明:
- 用于序列化时,将模型类对象传入
instance
参数
- 用于反序列化时,将要被反序列化的数据传入
data
参数
- 除了
instance
和data
参数外,在构造Serializer
对象时,还可通过context
参数额外添加数据,如serializer = AccountSerializer(account, context={'request': request})
通过context
参数附加的数据,可以通过Serializer
对象的context
属性获取。- 注意
- 序列化器声明了以后,在视图中调用 后才会执行。
- 不管是序列化还是反序列化,都需要通过构造函数把数据传递到序列化器。
- 序列化器的字段声明类似
Model
。
django drf
框架在使用序列化器进行序列化时会把模型数据转换成字典。
- django drf 框架的视图在返回
Response
时会把字典转换成json
。或者把客户端发送过来的数据(request.data
)转换 成 字典.
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级统一0000
fid = serializers.CharField(required=False)
# 数值
value = serializers.CharField(max_length=50)
# 显示值
name = serializers.CharField(max_length=50)
# 类别,用source定义别名
type = serializers.CharField(max_length=50, source='dic_type')
# 状态 (1=启用,0=弃用)
status = serializers.CharField(max_length=6)
# 是否锁定编辑(0=否,1=是(内置字典,与写死代码对应))
is_locked = serializers.CharField(max_length=2)
# 简码 一般为 name 内容的大写首字母
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)
# 创建人id
created_by = serializers.CharField(max_length=32, null=False)
# 创建日期
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
参数。
- 如果传了就调用
def update(self, instance, validated_data)
方法。- 如果没有就调用
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
方法,是需要用户在子类中重载,实现业务逻辑的。这样用户在调用序列化器的.save()
方法时,会根据是否传了instance
参数,将通过校验的validated_data
数据传递给重载的update
或create
方法。
- 注意:
- 在调用
.save()
方法前要先调用.is_valid()
方法,对传入的数据进行校验。
update
和create
方法
5.2.1、 在子类中重载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级统一0000
fid = serializers.CharField(required=False)
# 数值
value = serializers.CharField(max_length=50)
# 显示值
name = serializers.CharField(max_length=50)
# 类别,用source定义别名
dic_type = serializers.CharField(max_length=50, source='type')
# 状态 (1=启用,0=弃用)
status = serializers.CharField(max_length=6)
# 是否锁定编辑(0=否,1=是(内置字典,与写死代码对应))
is_locked = serializers.CharField(max_length=2)
# 简码 一般为 name 内容的大写首字母
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)
# 创建人id
created_by = serializers.CharField(max_length=32)
# 创建日期
created_date = serializers.DateTimeField()
# 更新人id
update_by = serializers.CharField(max_length=32)
# 更新日期
update_date = serializers.DateTimeField()
def create(self, validated_data):
# 根据提供的验证过的数据创建并返回一个新的`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
创建序列化器,可以简化字段、create
和update
的定义。
ModelSerializer
将继承Serializer
时需要重复做的字段定义、create
方法和update
方法进行了封装,简化了序列化器的构建。
# serializers.d4_serializers.py
from rest_framework import serializers
from d4.models.d4_models import D4Dic
class D4DicSerializer(serializers.ModelSerializer):
# # 通过 source为 type 字段设置别名dic_type.
# # 注意:dic_type和 type 是同一个字段,且会同时被序列化,除非把其中一个exclude
# dic_type = serializers.CharField(max_length=50, source='type')
class Meta:
model = D4Dic
# 序列化全部字段
fields = "__all__"
# # 序列化数组内的字段
# fields = ['id', 'fid', 'name', 'value']
# # 排除数组内的字段,注意: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
主要增加了操作序列化器和数据库查询的方法,作用是为Mixin
扩展类的执行提供方法支持(通常在使用时,可搭配一个或多个Mixin
扩展类)。
GenericAPIView
中需要关注的属性和方法如下:
get_serializer_class(self)
- 当一个视图类中用到多个序列化器时,可以通过
get_serializer_cass
方法返回的序列化器类名判断是哪个序列化器。默认返回的serializer_class
,可以被重写。get_serializer(self, *args, **kwargs)
- 在视图中提供获取序列化器对象的方法(返回序列化器对象),主要用来提供给
Mixin
扩展类使用。- 注意:该方法会在返回的的序列化器对象的
context
属性中补充三个数据:request
、format
、view
:
- request: 当前视图的请求对象
- view: 当前请求的类视图对象
- format: 当前请求期望返回的数据格式
get_queryset(self)
- 返回视图使用的查询集,主要用来提供给
Mixin
扩展类使用,是列表视图与详情视图获取数据的基础,默认返回 的queryset
属性,可以被重写。
def get_queryset(self) myclass=xxxx_model_class return myclass.object.all()
get_object(self)
- 执行数据查询过滤,返回符合查询条件的数据详情对象,主要用来提供给
Mixin
扩展类使用。在试图中可以调用该方法获取详情信息的模型类对象。- 若数据不存在,会返回
404
。- 该方法会默认使用
APIView
提供的check_object _permissions
方法检当前对象是否有权限被访问。- 要配合路由设置中的
有名分组
进行,get_object
会根据有名分组
的参数进行查询。
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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get(self, request):
print("GET 被执行")
# 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 被执行了")
# 添加数据请求
# serializer = D4DicSerializer(data=request.data)
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
print("数据已经被保存")
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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get(self, request, pk):
print("GET 被执行")
# GET请求
# self.get_object() 基于有名路由分组进行过滤
serializer = self.get_serializer(instance=self.get_object(), many=False)
# 返回序列化数据
return Response(serializer.data)
# 返回给 html 模板视图,自定义字典
# return render(request,"dic.html",{"dic":"内容"})
# 返回给 html 模板视图,查询出来的结果
# return render(request, "dic.html", serializer.data)
def put(self, request, pk):
print("PUT 被执行")
# 修改数据请
serializer = self.get_serializer(instance=self.get_object(), data=request.data)
if serializer.is_valid():
serializer.save()
print("数据被更新了。")
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
print("DELETE被执行")
# 删除数据请求
# 单条记录查询
self.get_object().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
七、Mixin工具类源码解析
- Mixin作用:
- 提供了几种后端视图组合,用来实现对数据资源进行增删改查处理流程。即封装了数据资源的增删改查操作,其他视图可以通过继承相应的Mixin扩展类来复用代码,减少自己编写的代码量。
- 这些Mixin扩展类需要搭配GenericAPIView通用视图基类使用,因为它们的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法。
7.1、Mixin 保函的视图组合
7.1.1、CreateModelMixin
- 作用:
- 创建(添加)操作视图扩展类,提供
create(self, request, *args, **kwargs)
方法快速实现创建(添加)视图,返回201
状态码。
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)
方法快速实现列表视图,返回200
状态码。- 该
Mixin
的list
方法会对数据进行过滤和分页。
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
- 作用:
- 数据详情页视图扩展类,提供
retrieve(self, request, *args, **kwargs)
方法快速实现列表视图,返回200
状态码。
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)
方法快速实现列表视图,返回200
状态码。
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)
方法快速实现列表视图,返回204
状态码。
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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get(self, request):
print("GET 被执行")
return self.list(request)
def post(self, request):
print("POST 被执行了")
return self.create(request)
class DicDetail(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
'''
CBV下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get(self, request, pk):
print("GET 被执行")
return self.retrieve(request, pk)
def put(self, request, pk):
print("PUT 被执行")
return self.update(request, pk)
def delete(self, request, pk):
print("DELETE被执行")
return self.destroy(request, pk)
7.3、Mixin 再封装
由7.2k也看出
DicList
类和DicDetail
类表示了Dic
资源的5个操作(增删改查查),而其他资源如果也只想实现这5个操作,那么代码相似度是极高的。因此可以对其进行二次封装,封装的思路主要是简化了继承关系和对应的操作。封装的类如下:
7.3.1、CreateAPIView
继承了
CreateModelMixin
类和GenericAPIView
类,并实现了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
类,并实现了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
类,并实现了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
类,并实现了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
类,并实现了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
类,并实现了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
类,并实现了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
类,并实现了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
类,并实现了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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
class DicDetail(RetrieveUpdateDestroyAPIView):
'''
CBV下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
八、ViewSet源码解析
ViewSetMixin
重构了分发机制。
通过路由设置改变操作绑定的处理函数。
- eg.
- get操作绑定操作函数为get_object,post操作绑定操作函数为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__))
# 》》》》》《《《《《
# 核心代码重构了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
# 》》》》》《《《《《
# 核心代码,通过循环重新绑定了操作函数/方法。》》》》》《《《《《
# 》》》》》《《《《《
# actions === .as_view({"get":"get_object", "post":"post_object"})
for method, action in actions.items():
handler = getattr(self, action)
# 将 get 操作的执行函数设置成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的所有功能外,还支持制定路由分发。
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下的系统字典管理API
'''
def get_list(self, request):
print("GET 被执行")
# GET请求
# 多条记录查询
dics = D4Dic.objects.all()
serializer = D4DicSerializer(dics, many=True)
# serializer.data 转换成 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 被执行了")
# 添加数据请求
serializer = D4DicSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
print("数据已经被保存")
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 被执行")
# GET请求
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = D4DicSerializer(dic)
# 返回序列化数据
return Response(serializer.data)
# 返回给 html 模板视图,自定义字典
# return render(request,"dic.html",{"dic":"内容"})
# 返回给 html 模板视图,查询出来的结果
# return render(request, "dic.html", serializer.data)
def update(self, request, pk):
print("PUT 被执行")
# 修改数据请
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("数据被更新了。")
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
print("DELETE被执行")
# 删除数据请求
# 单条记录查询
try:
dic = D4Dic.objects.get(id=pk)
except D4Dic.DoesNotExist:
print("查不到数据")
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的所有功能外,还支持制定路由分发。
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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def list(self, request):
print("GET 被执行")
# 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 被执行了")
# 添加数据请求
# serializer = D4DicSerializer(data=request.data)
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
print("数据已经被保存")
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 被执行")
# GET请求
# self.get_object() 基于有名路由分组进行过滤
serializer = self.get_serializer(instance=self.get_object(), many=False)
# 返回序列化数据
return Response(serializer.data)
# 返回给 html 模板视图,自定义字典
# return render(request,"dic.html",{"dic":"内容"})
# 返回给 html 模板视图,查询出来的结果
# return render(request, "dic.html", serializer.data)
def update(self, request, pk):
print("PUT 被执行")
# 修改数据请
serializer = self.get_serializer(instance=self.get_object(), data=request.data)
if serializer.is_valid():
serializer.save()
print("数据被更新了。")
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
print("DELETE被执行")
# 删除数据请求
# 单条记录查询
self.get_object().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
8.3.3、结合 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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
8.4、ReadOnlyModelViewSet
ViewSet继承了RetrieveModelMixin、ListModelMixin和GenericViewSet,除了有GenericAPIView的所有功能外,还支持制定路由分发和简化了“查(单数据详情)查(列表数据)”操作代码。
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
# 只实现了两个查询,即查询符合条件的数据详情和查询数据列表。
from d4.d4_models import D4Dic
from d4.d4_serializers import D4DicSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet
class DicGVS(ReadOnlyModelViewSet):
'''
CBV下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
8.5、ModelViewSet
ViewSet继承了CreateModelMixin、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin、ListModelMixin和GenericViewSet,除了有GenericAPIView的所有功能外,还支持制定路由分发和简化了“增删改查查”操作代码。
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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
九、路由组件
9.1、path和re_path的区别
# 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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
9.3、ViewSet视图集中的额外行为路由
9.3.1、方式一
例如在
dic_view.py
中额外添加获取最后一条数据的lastest
操作和只更新value
的read
操作。
- 为额外行为单独设置路由
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
更新操作,是特定某一条数据的更新操作,需要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下的系统字典管理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下的系统字典管理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的区别
二者仅有一点区别,就是
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、配置过滤规则
# 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下的系统字典管理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=1
http://127.0.0.1:8000/d4/dic/?name=111
10.1.4、自定义过滤器类
上述过滤默认是
精准匹配
。使用DjangoFilterBackend
如需要实现模糊匹配可以通过自定义过滤器类实现。
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']
# 如果不想要精准匹配的查询条件可以使用空数组
# fields = []
# d4.filters.__init__.py
from d4.filters.dic_filter import D4DicFilter
参数说明:
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)
方法,该方法应返回一个经过过滤的新查询集。除了允许客户端执行搜索和过滤外,对于限制通用过滤器后端仅响应给定请求或用户的对象也很有用。
# 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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 使用自定义过滤器类
filterset_class = IsOwnerFilterBackend
10.1.4、定制过滤界面
通用过滤器还可以在可浏览的 API 中提供接口实现一个 to_html() 方法,呈现的 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下的系统字典管理API
'''
# queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get_queryset(self):
'''
该视图返回当前验证用户创建的字典。
'''
user = self.request.user
# 假设 D4Dic表中created_by字段保存的是创建者的用户名
return D4Dic.objects.filter(created_by = user)
10.1.5.2、基于url有名分组参数的过滤查询
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下的系统字典管理API
'''
# queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get_queryset(self):
'''
该视图返回URL中code有名分组参数指定的字典清单
'''
_code = self.kwargs['code']
return D4Dic.objects.filter(code = _code)
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下的系统字典管理API
'''
# queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
def get_queryset(self):
'''
该视图返回code参数指定的字典清单
'''
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'],
# 自定义查询参数名,默认是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下的系统字典管理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
- 注意:
- 上述例子会根据
search
的值,去配置的字段name
或者code
,只要有一个模糊匹配到search
的值,就满足条件。
- 双下划线表示法:
- 使用双下划线表示法在
ForeignKey
或ManyToManyField
上执行搜索。
search_fields = ['username', 'email', 'profile__profession']
- 可以使用双下划线表示法对
JSONField
和HStoreField
字段内嵌套的数据按数据结构进行搜索。
search_fields = ['data__breed', 'data__owner__other_pets__0__name']
- 多个关键字搜索:
- 默认情况下搜索将使用不区分大小写的部分匹配,搜索参数可以包含多个搜索词,多个搜索词应将其用空格和/或 逗号分隔,使用多个搜索词则仅当所有提供的词都匹配时才符合条件。
- 搜索行为修饰符
- 可通过在
search_fields
数组元素字符前添加各种字符来限制搜索行为。
‘^’
:开始搜索。‘=’
:完全匹配。‘@’
:全文搜索(当前仅支持PostgreSQL数据库)。‘$’
:正则表达式搜索。search_fields = ['=username','=email']
效果:
10.3、排序
OrderingFilter
默认允许用户对serializer_class
属性指定的序列化器上的任何可读字段进行排序。
10.3.1、全局配置排序组件
# settings.py
REST_FRAMEWORK = {
...
# 全局 排序组件 配置
'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.OrderingFilter'],
# 设置默认排序参数名,默认是ordering
# 'ORDERING_ PARAM': 'ordering'
...
}
10.3.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 OrderingFilter
class DicGVS(ModelViewSet):
'''
CBV下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 也可以在类中配置排序组件,可以在视图中代替或覆盖全局配置
# filter_backends = [OrderingFilter]
# 设置全字段可排序(默认)
# ordering_fields = '__all__'
# 配置可排序字段
ordering_fields = ['name', 'code']
# 设置默认排序字段,或排序顺序。ordering 可以是字符串列表、字符串元组。
# ordering = ['name']
10.4、分页
10.4.1、全局配置PageNumberPagination
# settings.py
REST_FRAMEWORK = {
...
# 分页(全局):全局分页器, 如 省市区的数据不需要分页,可自定义分页器
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 每页返回数量,(默认 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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 也可以在类中注册SearchFilter过滤组件,可以在视图中代替或覆盖全局注册
# filter_backends = [SearchFilter]
# 配置模糊搜索字段
search_fields = ['name', 'code']
# 设置分页器, 可以在视图中代替或覆盖全局设置
# pagination_class = PageNumberPagination
10.4.3、自定义分页器
- 可以在子类中自定义的分页器属性:
page_size_query_param = 'page_size'
前端发送的每页数目关键字名,默认为None
max_page_size = 4
前端最多能设置的每页数量page_size = 2
每页数目page_query_param = 'page'
前端发送的页数关键字名,默认为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 # 默认每页显示的记录数
page_size_query_param = "page_size" # URL中用于指定每页记录数的参数名
max_page_size = 100 # 最大每页显示的记录数
max_page_size = 4
# 添加 当前页码、每页数量、总页数信息。
# 从写get_paginated_response(self, data)
def get_paginated_response(self, data):
return Response(
{
"count": self.page.paginator.count, # 总记录数
"next": self.get_next_link(), # 下一页链接
"previous": self.get_previous_link(), # 上一页链接
"current_page": self.page.number, # 当前页码
"per_page": self.page.paginator.per_page, # 每页数量
"total_pages": self.page.paginator.num_pages, # 总页数
"results": data, # 当前页的数据
}
)
# 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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 也可以在类中注册SearchFilter过滤组件,可以在视图中代替或覆盖全局注册
# filter_backends = [SearchFilter]
# 配置模糊搜索字段
search_fields = ['name', 'code']
# 设置分页器, 可以在视图中代替或覆盖全局设置
pagination_class = PageNumberPagination2
# settings.py
# 全局使用
REST_FRAMEWORK = {
...
# 分页(全局):全局分页器, 如 省市区的数据不需要分页,可自定义分页器
'DEFAULT_PAGINATION_CLASS': 'd4.serializers.pagination.PageNumberPagination2',
# 每页返回数量,(默认 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下的系统字典管理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
十一、限流可以对接口访问的频次进行限制,以减轻对服务器的压力。
Throttling
10.4.1、全局配置限流# 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
来指明周期。- 超出限定的次数会报错
-"detail": "Request was throttled. Expected available in 86398 seconds."
10.3.2、视图中配置限流规则
# 自定义限流类
# 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 对象来获取请求的相关信息
# 例如,可以使用 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
在上面的示例中,我们创建了一个名为 CustomThrottle 的自定义限流类。它继承了 throttling.BaseThrottle 类,并重写了 allow_request 方法来定义限流逻辑。在这个示例中,我们简单地检查请求的 IP 地址是否在黑名单中,如果是,则拒绝请求。你可以根据自己的需求来修改和扩展这个方法。
# 在视图中配置
# 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下的系统字典管理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次
},
...
}
十二、认证
系统需要登入后才能访问,登入过程既认证过程。
DRF
认证过程拆解:
- 身份验证是将传入的请求对象(
request
)与一组标识凭据(例如请求来自的用户或其签名的令牌token
)相关联的机制。REST framework
提供了一些开箱即用的身份验证方案,并且还允许你实现自定义方案。DRF
中每个认证方案都被封装成一个类。你可以在视图中使用一个或多个认证方案类。REST framework
将尝试使用列表中的每个类进行身份验证,并使用成功完成验证的第一个类的返回的元组设置request.user
和request.auth
。- 用户通过认证后
request.user
返回Django
的User
实例,否则返回AnonymousUser
的实例。request.auth
通常为None
。如果使用token
认证,request.auth
可以包含认证过的token
。
DRF 提供的认证方案
Session
认证SessionAuthentication
类:此认证方案使用Django
的默认session
后端进行身份验证。当客户端发送登录请求通过验证后,Django
通过session
将用户信息存储在服务器中保持用户的请求状态。Session
身份验证适用于与你的网站在相同的Session
环境中运行的AJAX
客户端。 (ps:这也是Session
认证的最大弊端)。
基本认证
BasicAuthentication
类:此认证方案使用HTTP
基本认证,针对用户的用户名和密码进行认证。使用这种方式后浏览器会跳出登录框让用户输入用户名和密码认证。(ps:基本认证通常只适用于测试)。
Token
认证TokenAuthentication
类:该认证方案是DRF提供的使用简单的基于Token
的HTTP
认证方案。当客户端发送登录请求时,服务器便会生成一个Token并将此Token
返回给客户端,作为客户端进行请求的一个标识以后客户端只需带上这个Token
前来请求数据即可,(ps:学习笔记中会详细介绍基于Token
的JWT
认证方案)。
远程认证
RemoteUserAuthentication
类:此认证方案为用户名不存在的用户自动创建用户实例。
12.1、全局认证
settings.py
中设置默认的全局认证方案
- 在
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 = []
即可。- 除了上述自己实现的认证类,
REST Framework
为我们提供了四种认证类:1. BasicAuthentication # 基于用户名密码 2. SessionAuthentication # 基于session 3. TokenAuthentication # 基于token 4. RemoteUserAuthentication # 远程用户认证
# 直接在视图类中使用即可 # 导包 from rest_framework.authentication import BaseAuthentication,SessionAuthentication
12.2、局部认证
局部认证会在局部内覆盖全局认证的配置。
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、基于函数视图(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内置的
TokenAuthentication
方案不同,JWT身份验证不需要使用数据库来验证令牌,JWT
生产的Token
自包含了业务信息。这些信息包括Token
的有效期、附带的业务信息例如UserId,加密标准等。JWT
可以轻松设置token
失效期或刷新token
, 是API开发中当前最流行的跨域认证解决方案。- 学习笔记中将详细介绍
JWT
认证的工作原理以及如何通过djangorestframework-simplejwt
这个第三方包轻松实现JWT
认证。
12.3.2.1、JWT原理
JWT
用于为应用程序创建访问token
,通常适用于API身份验证和服务器到服务器的授权。
JWT
定义了一种紧凑且自包含的方式。该方式用于各方之间安全地将信息以JSON
对象传输。
- 紧凑(简洁):
Token
数据量比较少,可以通过url
参数,http
请求提交的数据以及http header
多种方式来传递。- 自包含:
Token
字符串可以包含很多信息,比如用户id
,用户名
,订单号id
等,
- PS:虽然其他人拿到该信息,就可以拿到关键业务信息。但由于此信息是经过数字签名的,因此可以被验证和信任。
JWT的组成
JSON Web Token
由三部分组成,这些部分由点(.)分隔,分别是header
(头部),payload
(有效负载)和signature
(签名)。
header
(头部): 识别以何种算法来生成签名;pyload
(有效负载): 用来存放实际需要传递的数据;signature
(签名): 安全验证token
有效性,防止数据被篡改。
JWT用户认证过程
首先客户端提交用户登录信息验证身份通过后,服务器生成一个用于证明用户身份的令牌(
token
),也就是一个加密后的长字符串,并将其发送给客户端。在后续请求中,客户端以各种方式(比如通过url参数或者请求头)将这个令牌发送回服务器,服务器就知道请求来自哪个特定身份的用户了。具体步骤如下。
- 首先,前端通过
Web
表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST
请求。建议的方式是通过SSL
加密的传输(https协议
),从而避免敏感信息被嗅探。- 后端核对用户名和密码成功后,将用户的id等其他信息作为
JWT Payload
(负载),将其与头部分别进行Base64
编码拼接后签名,形成一个JWT
。形成的JWT就是一个形同aaa.bbb.ccc
的字符串。- 后端将
JWT
字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage
或sessionStorage
上,退出登录时前端删除保存的JWT即可。- 前端在每次请求时将
JWT
放入HTTP Header
中的Authorization
位。(解决XSS
和XSRF
问题)- 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查
Token
是否过期;检查Token
的接收方是否是自己(可选)。
PS:通过
http
传输的数据实际上是加密后的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
中设置默认的全局认证方案
- 在
settings.py
配置文件中REST_FRAMEWORK
添加配置
# settings.py
REST_FRAMEWORK = {
……
# 1.认证器(全局)
"DEFAULT_AUTHENTICATION_CLASSES": [
# 在DRF中配置JWT认证
"rest_framework_simplejwt.authentication.JWTAuthentication"
],
……
}
urls.py
中设置获取和刷新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
请求数据
如果
Token
不正确或为空。
12.4、自定义认证
12.4.1、自定义令牌(Token)
如果希望在payload部分提供更多信息,比如用户的username,可通过自定义令牌(token)实现。
- 首先,编写自定义序列化器
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的内容。
在实际API开发过程中,通常会通过Json Web Token传递更多数据。
authenticate
)
12.4.2、自定义认证方式一:自定义认证器(重写自定义认证类继承
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)实现。
例如希望为系统额外提供通过email进行登入的功能,即同时支持username和email登入。
自定义认证类MyCustomBackend
from django.contrib.auth.backends import ModelBackend
#django的Q对象将SQL表达式封装在Python对象中,该对象可用于与数据库相关的操作。使用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、两种方式的区别
在Django中,自定义认证通常涉及到修改或扩展默认的认证过程。当你想要自定义认证时,可以选择继承ModelBackend
或BaseAuthentication
。但是,这两个类服务于不同的目的,并位于Django的不同认证系统中,因此它们的用途和实现方式存在显著的区别。
- 继承
ModelBackend
:
ModelBackend
是Django默认的身份验证后端,它使用Django的用户模型(通常是auth.User
)来验证用户的凭证。这个类实现了authenticate
方法,该方法接收用户名和密码作为参数,并返回一个用户对象(如果凭证有效)或None
。
当你需要扩展或修改默认的基于模型的认证逻辑时,可以继承ModelBackend
并重写authenticate
方法。例如,你可能想要支持额外的认证字段,或者添加自定义的验证逻辑。
优点:
- 易于扩展默认的基于模型的认证逻辑。
- 可以保留Django用户模型及其关联的功能(如权限和组)。
缺点:
- 如果你的认证需求与Django用户模型差异很大,那么使用
ModelBackend
可能不够灵活。
- 继承
BaseAuthentication
:
BaseAuthentication
是Django REST framework(DRF)中的一个类,用于实现API的身份验证。DRF提供了一套强大的工具来构建Web API,包括认证和权限管理。BaseAuthentication
是一个基类,用于创建自定义的身份验证类。
当你需要为DRF API实现自定义的身份验证逻辑时,可以继承BaseAuthentication
并重写authenticate
方法。例如,你可能想要支持令牌认证、OAuth2或其他非标准的认证机制。
优点:
- 专为Web API设计,提供了与DRF的无缝集成。
- 支持多种认证机制,灵活性高。
缺点:
- 仅适用于DRF API,不适用于传统的Django视图或表单。
- 需要安装和使用DRF及其相关依赖。
总结:
- 如果你正在使用Django的默认用户模型,并且只需要扩展或修改基于模型的认证逻辑,那么继承
ModelBackend
是更合适的选择。 - 如果你正在使用DRF构建Web API,并且需要实现自定义的身份验证逻辑(如令牌认证、OAuth2等),那么继承
BaseAuthentication
是更好的选择。
十三、权限
13.1、全局配置权限
# setting.py
INSTALLED_APPS = [
'rest_framework',
]
# setting.py
REST_FRAMEWORK = {
# 2.权限配置(全局): 顺序靠上的严格,如果未指定,则此设置默认为允许无限制访问:
"DEFAULT_PERMISSION_CLASSES": [
# 'rest_framework.permissions.IsAdminUser', # 管理员可以访问
"rest_framework.permissions.IsAuthenticated", # 认证用户可以访问
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 认证用户可以访问, 否则只能读取
# 'rest_framework.permissions.AllowAny', # 所有用户都可以访问
],
}
13.2、视图中配置权限
13.2.1、函数视图配置权限
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、类试图配置权限
# 在视图中配置
# 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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 在类中配置权限,可以在视图中代替或覆盖全局配置
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、自定义权限类
13.3.1、DRF提供常用权限类
- IsAuthenticated类:仅限已经通过身份验证的用户访问;
- AllowAny类:允许任何用户访问;
- IsAdminUser类:仅限管理员访问;
- DjangoModelPermissions类:只有在用户经过身份验证并分配了相关模型权限时,才会获得授权访问相关模型。
- DjangoModelPermissionsOrReadOnly类:与前者类似,但可以给匿名用户访问API的可读权限。
- DjangoObjectPermissions类:只有在用户经过身份验证并分配了相关对象权限时,才会获得授权访问相关对象。通常与django-gaurdian联用实现对象级别的权限控制。
13.3.2、自定义权限类
# d4.permissions.is_owner_or_read_only.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
添加自定义权限,只允许对象的创建者才能编辑它。
"""
def has_object_permission(self, request, view, obj):
# 读取权限被允许用于任何请求,
# 所以我们始终允许 GET,HEAD 或 OPTIONS 请求。
if request.method in permissions.SAFE_METHODS:
return True
# 写入权限只允许给 article 的作者。
return obj.author == request.user
# d4.permissions.__init__.py
from d4.permissions.is_owner_or_read_only import IsOwnerOrReadOnly
13.3.3、在视图中使用自定义权限类
# 在视图中配置
# 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下的系统字典管理API
'''
queryset = D4Dic.objects.all()
serializer_class = D4DicSerializer
# 在类中配置权限,可以在视图中代替或覆盖全局配置
permission_classes = (IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
13.3.4、全局添加自定义权限类
# 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", # 自定义权限类
],
参见
https://www.cnblogs.com/tjw-bk/p/13952297.html
附录、D4Dic数据模型
# 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 ='创建人id', max_length=32, null=False)
created_date = models.DateTimeField(help_text ='创建日期', 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 ='状态;', max_length=6, null=False)
class Meta:
managed = False
db_table = 'd4_dic'
settings设置指南
附录、Simple JWT
的默认设置
附录、# JWT配置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # Access Token的有效期
'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh Token的有效期
# 对于大部分情况,设置以上两项就可以了,以下为默认配置项目,可根据需要进行调整
# 是否自动刷新Refresh Token
'ROTATE_REFRESH_TOKENS': False,
# 刷新Refresh Token时是否将旧Token加入黑名单,如果设置为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签名的密钥返回的内容。可以是字符串形式的密钥,也可以是一个字典。
"VERIFYING_KEY": "",
"AUDIENCE": None,# JWT中的"Audience"声明,用于指定该JWT的预期接收者。
"ISSUER": None, # JWT中的"Issuer"声明,用于指定该JWT的发行者。
"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",
# 用于指定用户身份验证规则的函数或方法。默认使用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的默认设置,可以修改settings.py, 如下所示。下例将refresh token的有效期改为了15天。
from datetime import timedelta
SIMPLE_JWT = {
'REFRESH_TOKEN_LIFETIME': timedelta(days=15),
'ROTATE_REFRESH_TOKENS': True,
}