一文读懂django-restframeword(DRF)

转载请注明 来源:http://www.eword.name/
Author:eword
Email:eword@eword.name

django-restframeword(DRF)解读

一、面相对象编程中的类继承和反射基础

要读懂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中,你可以使用反射来动态地创建对象。

  1. 使用globals()或locals()函数来获取当前全局或局部命名空间。
  2. 使用getattr()来获取类或函数的引用。
  3. 调用该类或函数来创建对象。

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!

在这个例子中,

  1. 首先定义了一个名为MyClass的类。
  2. 然后,将类名作为字符串存储在变量class_name中。
  3. 接着,使用globals()函数来获取当前全局命名空间,并使用类名字符串作为键来获取类的引用。
  4. 最后,我们调用这个类来创建一个新的对象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、父类调用子类函数

实现事件注册机制前,要先了解如何通过父类调用子类的函数或属性。

  1. 在父类中定义一个方法,该方法使用反射来调用子类的方法。
  2. 在子类中实现这个方法。
  3. 当父类需要调用这个方法时,它使用反射来找到并调用子类中的实现。

这通常是通过使用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/dichttp://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)

2.3.3、使用@api_view装饰器

@api_view装饰器函数签名:@api_view(http_method_names=['GET'], exclude_from_schema=False)

  • 用来包装你的视图函数,以确保视图函数会收到Request(而不是Django一般的HttpRequest)对象,并且返回Response(而不是DjangoHttpResponse)对象,同时允许你设置这个请求的处理方式。
  • @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()

CBV

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 序列化器,把像查询集和模型实例这样的复杂数据转换为可以轻松渲染成JSONXML或其他内容类型的原生Python类型。序列化器还提供反序列化,在验证传入的数据之后允许解析数据转换回复杂类型。
序列化器构造函数:Serializer(instance=None, data=empty, **kwarg)

  • 说明:
      1. 用于序列化时,将模型类对象传入instance参数
      1. 用于反序列化时,将要被反序列化的数据传入data参数
      1. 除了instancedata参数外,在构造Serializer对象时,还可通过context参数额外添加数据,如serializer = AccountSerializer(account, context={'request': request})
        通过context参数附加的数据,可以通过Serializer对象的context属性获取。
  • 注意
      1. 序列化器声明了以后,在视图中调用 后才会执行。
      1. 不管是序列化还是反序列化,都需要通过构造函数把数据传递到序列化器。
      1. 序列化器的字段声明类似 Model
      1. django drf 框架在使用序列化器进行序列化时会把模型数据转换成字典。
      1. 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 中定义的updatecreate 方法如下:
    def update(self, instance, validated_data):
        raise NotImplementedError('`update()` must be implemented.')

    def create(self, validated_data):
        raise NotImplementedError('`create()` must be implemented.')

BaseSerializer 中定义的updatecreate 方法,是需要用户在子类中重载,实现业务逻辑的。这样用户在调用序列化器的.save()方法时,会根据是否传了instance参数,将通过校验的validated_data 数据传递给重载的updatecreate 方法。

  • 注意:
    • 在调用.save()方法前要先调用.is_valid()方法,对传入的数据进行校验。

5.2.1、 在子类中重载updatecreate方法

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创建序列化器,可以简化字段、createupdate的定义。
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,作用是把视图中的通用代码抽取出来,让视图方法中的代码更加通用,方便把通用代码进行简写。

  • 相较于APIViewGenericAPIView主要增加了操作序列化器和数据库查询的方法,作用是为Mixin扩展类的执行提供方法支持(通常在使用时,可搭配一个或多个Mixin扩展类)。

  • GenericAPIView中需要关注的属性和方法如下:

    • get_serializer_class(self)
      • 当一个视图类中用到多个序列化器时,可以通过get_serializer_cass方法返回的序列化器类名判断是哪个序列化器。默认返回的serializer_class,可以被重写。
    • get_serializer(self, *args, **kwargs)
      • 在视图中提供获取序列化器对象的方法(返回序列化器对象),主要用来提供给Mixin扩展类使用。
      • 注意:该方法会在返回的的序列化器对象的context属性中补充三个数据:requestformatview
        • 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状态码。
    • Mixinlist方法会对数据进行过滤和分页。
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类,并实现了putpatch操作。

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

继承了ListModelMixinCreateModelMixin类和GenericAPIView类,并实现了getpost操作。

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

继承了RetrieveModelMixinUpdateModelMixin类和GenericAPIView类,并实现了getputpatch操作。

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

继承了RetrieveModelMixinDestroyModelMixin类和GenericAPIView类,并实现了getdelete操作。

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

继承了RetrieveModelMixinUpdateModelMixinDestroyModelMixin类和GenericAPIView类,并实现了getputpatchdelete操作。

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类分别继承了ListCreateAPIViewRetrieveUpdateDestroyAPIView,简化了代码。

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操作和只更新valueread操作。

  • 为额外行为单独设置路由
    • 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"})),
  • 注意
    • lastestget操作,且不指定 pk 值,做一只要在资源路径末端直接加操作名称。
    • readput更新操作,是特定某一条数据的更新操作,需要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的值,就满足条件。
  • 双下划线表示法
    • 使用双下划线表示法在 ForeignKeyManyToManyField 上执行搜索。
      • search_fields = ['username', 'email', 'profile__profession']
    • 可以使用双下划线表示法对JSONFieldHStoreField 字段内嵌套的数据按数据结构进行搜索。
      • 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

可以对接口访问的频次进行限制,以减轻对服务器的压力。

10.4.1、全局配置限流Throttling

# settings.py
  
REST_FRAMEWORK = {
    ...
    # 限流(防爬虫)
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    # 限流策略
    'DEFAULT_THROTTLE_RATES': {
        'user': '10/hour',  # 认证用户每小时10次
        'anon': '3/day',  # 未认证用户每天能访问3次
    },
    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
    'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
    'DEFAULT_VERSIONING_CLASS': None,
    ...
}
  • DEFAULT_THROTTLE_RATES 可以使用 secondminutehourday来指明周期。
  • 超出限定的次数会报错
    -"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次
    },
    ...
}

十二、认证

重要参考:Django REST Framework 之认证、权限(超详细)

系统需要登入后才能访问,登入过程既认证过程。

DRF 认证过程拆解:

  1. 身份验证是将传入的请求对象(request)与一组标识凭据(例如请求来自的用户或其签名的令牌token)相关联的机制。
  2. REST framework 提供了一些开箱即用的身份验证方案,并且还允许你实现自定义方案。DRF中每个认证方案都被封装成一个类。你可以在视图中使用一个或多个认证方案类。REST framework 将尝试使用列表中的每个类进行身份验证,并使用成功完成验证的第一个类的返回的元组设置 request.userrequest.auth
  3. 用户通过认证后request.user返回DjangoUser实例,否则返回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提供的使用简单的基于TokenHTTP认证方案。当客户端发送登录请求时,服务器便会生成一个Token并将此Token返回给客户端,作为客户端进行请求的一个标识以后客户端只需带上这个Token前来请求数据即可,(ps:学习笔记中会详细介绍基于TokenJWT认证方案)。

远程认证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参数或者请求头)将这个令牌发送回服务器,服务器就知道请求来自哪个特定身份的用户了。具体步骤如下。

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
  2. 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同aaa.bbb.ccc的字符串。
  3. 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStoragesessionStorage上,退出登录时前端删除保存的JWT即可。
  4. 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSSXSRF问题)
  5. 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。

PS:通过http传输的数据实际上是加密后的JWT,它是由两个点分割的base64-URL长字符串组成,解密后我们可以得到header, payloadsignature三部分。

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中设置获取和刷新tokenurls地址。

# 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)实现。

  1. 首先,编写自定义序列化器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

  1. 自定义视图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

  1. 修改路由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传递更多数据。

12.4.2、自定义认证方式一:自定义认证器(重写authenticate

自定义认证类继承BaseAuthentication类并且重写.authenticate(self, request)方法。
如果认证成功,该方法应返回(user, auth)的二元元组,否则返回None

from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions

class ExampleAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):
        # 获取请求头中的传入的认证信息
        username = request.META.get('USER_EMAIL')
        if not username:
            return None

        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            raise exceptions.AuthenticationFailed('No such user')

        return (user, None)

使用自定义认证器ExampleAuthentication

注册 自定义认证器ExampleAuthentication**

#setting.py
    # 1.认证器(全局)
    "DEFAULT_AUTHENTICATION_CLASSES": [
        # 在DRF中配置JWT认证
        "rest_framework_simplejwt.authentication.JWTAuthentication",
        # 使用session时的认证器
        # "rest_framework.authentication.SessionAuthentication",
        # 提交表单时的认证器
        # "rest_framework.authentication.BasicAuthentication",
        # 自定义认证器
        # "xxx.xxxx.ExampleAuthentication"
    ],

12.4.3、自定义认证方式二:自定义认证后台(Backend)

如果需要支持username以外的登入方式,可以通过自定义认证后台(Backend)实现。
例如希望为系统额外提供通过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中,自定义认证通常涉及到修改或扩展默认的认证过程。当你想要自定义认证时,可以选择继承ModelBackendBaseAuthentication。但是,这两个类服务于不同的目的,并位于Django的不同认证系统中,因此它们的用途和实现方式存在显著的区别。

  1. 继承ModelBackend

ModelBackend是Django默认的身份验证后端,它使用Django的用户模型(通常是auth.User)来验证用户的凭证。这个类实现了authenticate方法,该方法接收用户名和密码作为参数,并返回一个用户对象(如果凭证有效)或None

当你需要扩展或修改默认的基于模型的认证逻辑时,可以继承ModelBackend并重写authenticate方法。例如,你可能想要支持额外的认证字段,或者添加自定义的验证逻辑。

优点

  • 易于扩展默认的基于模型的认证逻辑。
  • 可以保留Django用户模型及其关联的功能(如权限和组)。

缺点

  • 如果你的认证需求与Django用户模型差异很大,那么使用ModelBackend可能不够灵活。
  1. 继承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,
}

posted @ 2024-04-05 23:27  影乌  阅读(6)  评论(0编辑  收藏  举报