转载请注明 来源: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") |
| |
| |
| class Dog(Animal): |
| def __init__(self, name, breed): |
| super().__init__(name) |
| self.breed = breed |
| |
| def bark(self): |
| print(f"{self.name} barks!") |
| |
| |
| my_dog = Dog("Buddy", "Labrador") |
| |
| |
| my_dog.speak() |
| |
| |
| my_dog.bark() |
多重继承
| |
| class FlyingAnimal: |
| def fly(self): |
| print("I can fly!") |
| |
| |
| class Mammal: |
| def give_birth(self): |
| print("I give birth to live young.") |
| |
| |
| class Bat(FlyingAnimal, Mammal): |
| pass |
| |
| |
| my_bat = Bat() |
| |
| |
| my_bat.fly() |
| |
| |
| my_bat.give_birth() |
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') |
| print(value) |
使用 setattr() 设置属性的值
| setattr(obj, 'my_variable', 'New value') |
| print(getattr(obj, 'my_variable')) |
使用 hasattr() 检查属性是否存在
| exists = hasattr(obj, 'my_method') |
| print(exists) |
使用 callable() 检查对象是否可调用
| is_callable = callable(obj.my_method) |
| print(is_callable) |
1.2.2、通过反射创建对象
在Python中,你可以使用反射来动态地创建对象。
- 使用globals()或locals()函数来获取当前全局或局部命名空间。
- 使用getattr()来获取类或函数的引用。
- 调用该类或函数来创建对象。
ps: 更常见的方法是直接使用globals()或locals()结合类名或函数名来动态地获取类或函数,并调用它们。
| class MyClass: |
| def __init__(self, name): |
| self.name = name |
| |
| def say_hello(self): |
| print(f"Hello, my name is {self.name}!") |
| |
| |
| class_name = "MyClass" |
| obj = globals()[class_name](name="Dynamic Object") |
| |
| |
| obj.say_hello() |
在这个例子中,
- 首先定义了一个名为MyClass的类。
- 然后,将类名作为字符串存储在变量class_name中。
- 接着,使用globals()函数来获取当前全局命名空间,并使用类名字符串作为键来获取类的引用。
- 最后,我们调用这个类来创建一个新的对象obj,并传递所需的参数。
如果是在一个函数或方法内部,并且类是在那个局部作用域中定义的,可以使用locals()代替globals()。但是,通常情况下,类定义在模块级别,所以globals()更为常用。
如果你的类定义在另一个模块中,需要先导入那个模块,然后从模块的命名空间中使用反射来获取类的引用。
| import my_module |
| |
| class_name = "MyClass" |
| obj = my_module.__dict__[class_name](name="Dynamic Object from Module") |
| obj.say_hello() |
1.2.3、通过反射实现事件注册机制
1.2.3.1、父类调用子类函数
实现事件注册机制前,要先了解如何通过父类调用子类的函数或属性。
- 在父类中定义一个方法,该方法使用反射来调用子类的方法。
- 在子类中实现这个方法。
- 当父类需要调用这个方法时,它使用反射来找到并调用子类中的实现。
这通常是通过使用getattr()函数来实现的,该函数可以从一个对象中获取一个属性的值,如果该属性是一个方法,则可以调用它。
| class Parent: |
| def callback_subclass_method(self): |
| |
| method_name = "subclass_specific_method" |
| if hasattr(self, method_name): |
| method = getattr(self, method_name) |
| method() |
| else: |
| print("No subclass-specific method found.") |
| |
| class Child(Parent): |
| def subclass_specific_method(self): |
| print("This is a method specific to the subclass.") |
| |
| |
| child_instance = Child() |
| child_instance.callback_subclass_method() |
在这个例子中,Parent类有一个callback_subclass_method方法,它试图调用一个名为subclass_specific_method的方法。这个方法在Child类中被定义。当Child类的一个实例调用callback_subclass_method时,它实际上会调用Child类中定义的subclass_specific_method。
注意:这个例子假设子类确实有一个名为subclass_specific_method的方法。在实际应用中,可能需要更复杂的逻辑来处理不同子类可能有不同方法的情况。
此外,如果设计允许子类有不同的方法名称或签名,可能需要使用更高级的反射技术,或者重新考虑设计,以便父类可以使用更通用的接口来与子类交互。这通常可以通过接口、抽象基类或协议来实现。
最后,过度使用反射可能会使代码难以理解和维护,因为它允许在运行时动态地改变行为。因此,在使用反射时应该谨慎,并确保代码的可读性和可维护性。
1.2.3.1、事件注册机制
在Python中,通过反射实现事件注册机制可以涉及到创建一个事件分发器,它允许不同的对象(通常是类的实例)注册和触发事件。每个对象可以定义自己的事件处理函数,并在事件发生时通过反射机制被调用。
| class EventDispatcher: |
| def __init__(self): |
| self.event_handlers = {} |
| |
| def register_event(self, event_name, handler): |
| """注册事件处理函数""" |
| if event_name not in self.event_handlers: |
| self.event_handlers[event_name] = [] |
| self.event_handlers[event_name].append(handler) |
| |
| def trigger_event(self, event_name, *args, **kwargs): |
| """触发事件并调用所有注册的处理函数""" |
| if event_name in self.event_handlers: |
| for handler in self.event_handlers[event_name]: |
| handler(*args, **kwargs) |
| |
| class Listener: |
| def on_event(self, *args, **kwargs): |
| """事件处理函数""" |
| print(f"Event received: {args}, {kwargs}") |
| |
| |
| dispatcher = EventDispatcher() |
| listener = Listener() |
| |
| |
| dispatcher.register_event("my_event", listener.on_event) |
| |
| |
| dispatcher.trigger_event("my_event", "Hello", x=10) |
在这个例子中:
- EventDispatcher类维护了一个字典event_handlers,它映射事件名称到处理函数列表。
- register_event方法允许其他对象将其事件处理函数注册到特定的事件上。
- trigger_event方法则负责调用所有注册到特定事件的处理函数。
Listener类有一个on_event方法,它是事件处理函数。当事件被触发时,这个方法将被调用。
然后,创建了一个EventDispatcher实例和一个Listener实例,将Listener的on_event方法注册到名为"my_event"的事件上,并触发这个事件。当事件被触发时,所有注册到该事件的处理函数(在这个例子中是on_event方法)都会被调用。
这个例子并没有直接使用getattr或setattr等反射函数,因为反射通常用于动态地获取或设置对象的属性或方法。
在这个例子中,事件处理函数的注册和触发是显式进行的,而不是通过字符串名称动态查找的。
可以扩展这个机制,允许通过字符串名称来注册和触发事件,这样就涉及到了反射的使用。
如果需要通过字符串名称来动态地注册和触发事件,可以修改register_event和trigger_event方法,让它们接受事件名称作为字符串,并使用getattr来动态地获取和处理事件处理函数。但是请注意,这会增加代码的复杂性,并可能引入更多的错误和安全问题,因此应该谨慎使用。
二、CBV和FBV的区别
FBV(function base views) 基于函数的视图,就是在视图里使用函数处理请求。
CBV(class base views) 基于类的视图,就是在视图里使用类处理请求。
2.1、FBV结构示例
| |
| urlpatterns = [ |
| path("login/", views.login), |
| ] |
| |
| 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 结构示例
| |
| urlpatterns = [ |
| path("login/", views.Login.as_view()), |
| ] |
| |
| 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参数。
| |
| from rest_framework import serializers, status |
| from d4.models.d4_models import D4Dic |
| |
| |
| class D4DicSerializer(serializers.Serializer): |
| |
| id = serializers.CharField(read_only=False) |
| |
| fid = serializers.CharField(required=False) |
| |
| value = serializers.CharField(max_length=50) |
| |
| name = serializers.CharField(max_length=50) |
| |
| dic_type = serializers.CharField(max_length=50, source='type') |
| |
| status = serializers.CharField(max_length=6) |
| |
| is_locked = serializers.CharField(max_length=2) |
| |
| code = serializers.CharField(max_length=255) |
| |
| description = serializers.CharField(max_length=255) |
| |
| sort = serializers.IntegerField() |
| |
| is_delete = serializers.CharField(max_length=2) |
| |
| created_by = serializers.CharField(max_length=32) |
| |
| created_date = serializers.DateTimeField() |
| |
| update_by = serializers.CharField(max_length=32) |
| |
| update_date = serializers.DateTimeField() |
| |
| def create(self, validated_data): |
| |
| return D4Dic.objects.create(**validated_data) |
| |
| def update(self, instance, validated_data): |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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 |
| |
| |
| urlpatterns = [ |
| path('dic', dic_view.Dic), |
| |
| 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 被执行") |
| |
| 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 render(request, "dic.html", serializer.data) |
| else: |
| |
| dics = D4Dic.objects.all() |
| serializer = D4DicSerializer(dics, many=True) |
| |
| |
| |
| |
| data = json.dumps(serializer.data) |
| return HttpResponse(data) |
| elif request.method == "POST": |
| print("POST 被执行了") |
| data = request.body.decode('utf-8') |
| data_json = json.loads(data) |
| |
| 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') |
| data_json = json.loads(data) |
| |
| serializer = D4DicSerializer(instance=data_json, data=data_json) |
| if serializer.is_valid(): |
| serializer.save() |
| return HttpResponse(json.dumps(serializer.data)) |
| return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST) |
| elif request.method == "DELETE": |
| print("DELETE被执行") |
| |
| if 'pk' in request.GET and request.GET['pk']: |
| |
| pk = request.GET['pk'] |
| try: |
| dic = D4Dic.objects.get(id=pk) |
| except D4Dic.DoesNotExist: |
| print("查不到数据") |
| return HttpResponse(status=status.HTTP_404_NOT_FOUND) |
| dic.delete() |
| return HttpResponse(status=status.HTTP_204_NO_CONTENT) |
| else: |
| return HttpResponse("pk不存在") |
| |
| return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR) |
方别使用 GET、POST、PUT、DELETE的形式访问一下 http://xxx.xxx.xxx.xxx:8000/dic
和 http://xxx.xxx.xxx.xxx:8000/dic/xxx
试一下效果吧。
2.3.2、模式二:两个方法操作分开List和Detail
通过Url来携带id等pk参数。
| |
| from rest_framework import serializers, status |
| from d4.models.d4_models import D4Dic |
| |
| |
| class D4DicSerializer(serializers.Serializer): |
| |
| |
| id = serializers.CharField(read_only=False) |
| |
| fid = serializers.CharField(required=False) |
| |
| value = serializers.CharField(max_length=50) |
| |
| name = serializers.CharField(max_length=50) |
| |
| dic_type = serializers.CharField(max_length=50, source='type') |
| |
| status = serializers.CharField(max_length=6) |
| |
| is_locked = serializers.CharField(max_length=2) |
| |
| code = serializers.CharField(max_length=255) |
| |
| description = serializers.CharField(max_length=255) |
| |
| sort = serializers.IntegerField() |
| |
| is_delete = serializers.CharField(max_length=2) |
| |
| created_by = serializers.CharField(max_length=32) |
| |
| created_date = serializers.DateTimeField() |
| |
| update_by = serializers.CharField(max_length=32) |
| |
| update_date = serializers.DateTimeField() |
| |
| def create(self, validated_data): |
| |
| return D4Dic.objects.create(**validated_data) |
| |
| def update(self, instance, validated_data): |
| |
| dic = D4Dic.objects.filter(pk=instance) |
| if not dic.exists(): |
| raise Exception(status.HTTP_404_NOT_FOUND) |
| |
| dic.update(**validated_data) |
| return validated_data |
| |
| from django.urls import path, include, re_path |
| from d4.views import dic_view |
| urlpatterns = [ |
| |
| re_path(r'^dic/$', dic_view.DicList), |
| re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', dic_view.DicDetail), |
| |
| 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 被执行") |
| |
| |
| dics = D4Dic.objects.all() |
| serializer = D4DicSerializer(dics, many=True) |
| |
| |
| |
| |
| data = json.dumps(serializer.data) |
| return HttpResponse(data) |
| elif request.method == "POST": |
| print("POST 被执行了") |
| data = request.body.decode('utf-8') |
| data_json = json.loads(data) |
| |
| 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 被执行") |
| |
| 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)) |
| |
| |
| |
| |
| elif request.method == "PUT": |
| print("PUT 被执行") |
| data = request.body.decode('utf-8') |
| data_json = json.loads(data) |
| |
| 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
(而不是Django
的HttpResponse
)对象,同时允许你设置这个请求的处理方式。
- @api_view装饰器还提供了诸如在适当时候返回
405 Method Not Allowed
响应,并处理在使用格式错误的输入来访问request.data
时发生的任何ParseError
异常。
- 简单来说使用
@api_view
装饰器的的效果类似在 CBV
下继承APIView
的效果
| from rest_framework.decorators import api_view |
| from rest_framework import status |
| from rest_framework.response import Response |
| from d4.models.d4_models import D4Dic |
| from d4.serializers.d4_serializers import D4DicSerializer |
| import json |
| |
| |
| @api_view(['GET', "POST"]) |
| def DicList(request): |
| ''' |
| FBV下的系统字典管理API |
| ''' |
| if request.method == "GET": |
| print("GET 被执行") |
| |
| |
| dics = D4Dic.objects.all() |
| serializer = D4DicSerializer(dics, many=True) |
| |
| |
| |
| |
| data = json.dumps(serializer.data) |
| return Response(data) |
| elif request.method == "POST": |
| print("POST 被执行了") |
| |
| |
| |
| 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 被执行") |
| |
| 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)) |
| |
| |
| |
| |
| elif request.method == "PUT": |
| print("PUT 被执行") |
| data = request.body.decode('utf-8') |
| data_json = json.loads(data) |
| |
| 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
函数分发到对应的处理函数
| |
| @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__ |
| ) |
| |
| return self.dispatch(request, *args, **kwargs) |
| |
| view.view_class = cls |
| view.view_initkwargs = initkwargs |
| |
| |
| |
| |
| view.__doc__ = cls.__doc__ |
| view.__module__ = cls.__module__ |
| view.__annotations__ = cls.dispatch.__annotations__ |
| |
| |
| view.__dict__.update(cls.dispatch.__dict__) |
| |
| |
| 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
| |
| def dispatch(self, request, *args, **kwargs): |
| |
| |
| |
| 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。
| |
| |
| 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()), |
| ] |
| |
| |
| 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 |
| |
| |
| class DicList(View): |
| ''' |
| CBV下的系统字典管理API |
| ''' |
| |
| |
| def get(self, request): |
| print("GET 被执行") |
| |
| |
| dics = D4Dic.objects.all() |
| serializer = D4DicSerializer(dics, many=True) |
| |
| |
| |
| |
| data = json.dumps(serializer.data) |
| return HttpResponse(data) |
| |
| |
| def post(self, request): |
| print("POST 被执行了") |
| data = request.body.decode('utf-8') |
| data_json = json.loads(data) |
| |
| 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) |
| |
| |
| class DicDetail(View): |
| ''' |
| CBV下的系统字典管理API |
| ''' |
| |
| def get(self, request, pk): |
| print("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)) |
| |
| |
| |
| |
| |
| def put(self, request, pk): |
| print("PUT 被执行") |
| data = request.body.decode('utf-8') |
| data_json = json.loads(data) |
| |
| 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()方法
| |
| |
| @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 |
| |
| |
| |
| return csrf_exempt(view) |
| |
4.2、dispatch()方法
| |
| |
| |
| |
| |
| |
| 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) |
| self.request = request |
| self.headers = self.default_response_headers |
| |
| try: |
| self.initial(request, *args, **kwargs) |
| |
| |
| 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 |
| |
| response = handler(request, *args, **kwargs) |
| |
| except Exception as exc: |
| response = self.handle_exception(exc) |
| |
| self.response = self.finalize_response(request, response, *args, **kwargs) |
| return self.response |
| |
4.3、请求顺序
和View类几乎一样
- 举例eg.
- get请求
- view()==>return dispatch()==>return get()
-post请求
- view()==>return dispatch()==>return post()

4.4、CBV 下继承APIView的API 示例
D4DicSerializer 序列化类与 2.3.2的 serializers.d4_serializers.py 文件一致。
| |
| 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()), |
| |
| |
| 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 被执行") |
| |
| |
| dics = D4Dic.objects.all() |
| serializer = D4DicSerializer(dics, many=True) |
| |
| |
| |
| |
| 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 被执行") |
| |
| 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) |
| |
| |
| |
| |
| |
| def put(self, request, pk): |
| print("PUT 被执行") |
| |
| serializer = D4DicSerializer(instance=pk, data=request.data) |
| if serializer.is_valid(): |
| serializer.save() |
| print("数据被更新了。") |
| return Response(serializer.data) |
| return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) |
| |
| def delete(self, request, pk): |
| print("DELETE被执行") |
| |
| |
| try: |
| dic = D4Dic.objects.get(id=pk) |
| except D4Dic.DoesNotExist: |
| print("查不到数据") |
| return Response(status=status.HTTP_404_NOT_FOUND) |
| dic.delete() |
| return Response(status=status.HTTP_204_NO_CONTENT) |
| |
五、序列化与反序列化
Serializer
序列化器,把像查询集和模型实例这样的复杂数据转换为可以轻松渲染成JSON
,XML
或其他内容类型的原生Python类型。序列化器还提供反序列化,在验证传入的数据之后允许解析数据转换回复杂类型。
序列化器构造函数:Serializer(instance=None, data=empty, **kwarg)
- 说明:
-
- 用于序列化时,将模型类对象传入
instance
参数
-
- 用于反序列化时,将要被反序列化的数据传入
data
参数
-
- 除了
instance
和data
参数外,在构造Serializer
对象时,还可通过context
参数额外添加数据,如serializer = AccountSerializer(account, context={'request': request})
通过context
参数附加的数据,可以通过Serializer
对象的context
属性获取。
- 注意
-
- 序列化器声明了以后,在视图中调用 后才会执行。
-
- 不管是序列化还是反序列化,都需要通过构造函数把数据传递到序列化器。
-
- 序列化器的字段声明类似
Model
。
-
django drf
框架在使用序列化器进行序列化时会把模型数据转换成字典。
-
- django drf 框架的视图在返回
Response
时会把字典转换成json
。或者把客户端发送过来的数据(request.data
)转换 成 字典.
5.1、原始序列化器
| |
| from rest_framework import serializers |
| from models.d4_dic_model import D4Dic |
| |
| |
| class D4DicSerializer(serializers.Serializer): |
| |
| id = serializers.CharField(read_only=True) |
| |
| fid = serializers.CharField(required=False) |
| |
| value = serializers.CharField(max_length=50) |
| |
| name = serializers.CharField(max_length=50) |
| |
| type = serializers.CharField(max_length=50, source='dic_type') |
| |
| status = serializers.CharField(max_length=6) |
| |
| is_locked = serializers.CharField(max_length=2) |
| |
| code = serializers.CharField(max_length=255) |
| |
| description = serializers.CharField(max_length=255) |
| |
| sort = serializers.IntegerField() |
| |
| is_delete = serializers.CharField(max_length=2) |
| |
| created_by = serializers.CharField(max_length=32, null=False) |
| |
| created_date = serializers.DateTimeField(null=False) |
| |
| 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.' |
| ) |
| |
| |
| assert 'commit' not in kwargs, ( |
| "'commit' is not a valid keyword argument to the 'save()' method. " |
| "If you need to access data before committing to the database then " |
| "inspect 'serializer.validated_data' instead. " |
| "You can also pass additional keyword arguments to 'save()' if you " |
| "need to set extra attributes on the saved model instance. " |
| "For example: 'serializer.save(owner=request.user)'.'" |
| ) |
| |
| assert not hasattr(self, '_data'), ( |
| "You cannot call `.save()` after accessing `serializer.data`." |
| "If you need to access data before committing to the database then " |
| "inspect 'serializer.validated_data' instead. " |
| ) |
| |
| validated_data = {**self.validated_data, **kwargs} |
| |
| if self.instance is not None: |
| |
| self.instance = self.update(self.instance, validated_data) |
| assert self.instance is not None, ( |
| '`update()` did not return an object instance.' |
| ) |
| else: |
| |
| self.instance = self.create(validated_data) |
| assert self.instance is not None, ( |
| '`create()` did not return an object instance.' |
| ) |
| |
| return self.instance |
BaseSerializer
中.save()
方法判断了序列化器是否传了instance
参数。
- 如果传了就调用
def update(self, instance, validated_data)
方法。
- 如果没有就调用
def create(self, validated_data)
方法
BaseSerializer
中定义的update
和create
方法如下:
| def update(self, instance, validated_data): |
| raise NotImplementedError('`update()` must be implemented.') |
| |
| def create(self, validated_data): |
| raise NotImplementedError('`create()` must be implemented.') |
BaseSerializer
中定义的update
和create
方法,是需要用户在子类中重载,实现业务逻辑的。这样用户在调用序列化器的.save()
方法时,会根据是否传了instance
参数,将通过校验的validated_data
数据传递给重载的update
或create
方法。
- 注意:
- 在调用
.save()
方法前要先调用.is_valid()
方法,对传入的数据进行校验。
5.2.1、 在子类中重载update
和create
方法
| from rest_framework import serializers, status |
| from d4.models.d4_models import D4Dic |
| |
| |
| class D4DicSerializer(serializers.Serializer): |
| |
| |
| id = serializers.CharField(read_only=False) |
| |
| fid = serializers.CharField(required=False) |
| |
| value = serializers.CharField(max_length=50) |
| |
| name = serializers.CharField(max_length=50) |
| |
| dic_type = serializers.CharField(max_length=50, source='type') |
| |
| status = serializers.CharField(max_length=6) |
| |
| is_locked = serializers.CharField(max_length=2) |
| |
| code = serializers.CharField(max_length=255) |
| |
| description = serializers.CharField(max_length=255) |
| |
| sort = serializers.IntegerField() |
| |
| is_delete = serializers.CharField(max_length=2) |
| |
| created_by = serializers.CharField(max_length=32) |
| |
| created_date = serializers.DateTimeField() |
| |
| update_by = serializers.CharField(max_length=32) |
| |
| update_date = serializers.DateTimeField() |
| |
| def create(self, validated_data): |
| |
| return D4Dic.objects.create(**validated_data) |
| |
| def update(self, instance, validated_data): |
| |
| dic = D4Dic.objects.filter(pk=instance) |
| if not dic.exists(): |
| raise Exception(status.HTTP_404_NOT_FOUND) |
| |
| dic.update(**validated_data) |
| return validated_data |
| |
5.3、ModelSerializer
通过继承ModelSerializer
创建序列化器,可以简化字段、create
和update
的定义。
ModelSerializer
将继承Serializer
时需要重复做的字段定义、create
方法和update
方法进行了封装,简化了序列化器的构建。
| |
| from rest_framework import serializers |
| from d4.models.d4_models import D4Dic |
| |
| |
| class D4DicSerializer(serializers.ModelSerializer): |
| |
| |
| |
| |
| class Meta: |
| model = D4Dic |
| |
| |
| fields = "__all__" |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| extra_kwargs = { |
| 'value': {'min_value": 0, 'required':True), |
| 'fid': ('min_value': 0, ' required':True) |
| } |
| |
六、GenericAPIView源码解析
-
GenericAPIView
继承了APIView
,作用是把视图中的通用代码抽取出来,让视图方法中的代码更加通用,方便把通用代码进行简写。
-
相较于APIView
,GenericAPIView
主要增加了操作序列化器和数据库查询的方法,作用是为Mixin
扩展类的执行提供方法支持(通常在使用时,可搭配一个或多个Mixin
扩展类)。
-
GenericAPIView
中需要关注的属性和方法如下:
get_serializer_class(self)
- 当一个视图类中用到多个序列化器时,可以通过
get_serializer_cass
方法返回的序列化器类名判断是哪个序列化器。默认返回的serializer_class
,可以被重写。
get_serializer(self, *args, **kwargs)
- 在视图中提供获取序列化器对象的方法(返回序列化器对象),主要用来提供给
Mixin
扩展类使用。
- 注意:该方法会在返回的的序列化器对象的
context
属性中补充三个数据:request
、 format
、 view
:
- request: 当前视图的请求对象
- view: 当前请求的类视图对象
- format: 当前请求期望返回的数据格式
get_queryset(self)
- 返回视图使用的查询集,主要用来提供给
Mixin
扩展类使用,是列表视图与详情视图获取数据的基础,默认返回 的queryset
属性,可以被重写。
| def get_queryset(self) |
| myclass=xxxx_model_class |
| return myclass.object.all() |
get_object(self)
- 执行数据查询过滤,返回符合查询条件的数据详情对象,主要用来提供给
Mixin
扩展类使用。在试图中可以调用该方法获取详情信息的模型类对象。
- 若数据不存在,会返回
404
。
- 该方法会默认使用
APIView
提供的check_object _permissions
方法检当前对象是否有权限被访问。
- 要配合路由设置中的
有名分组
进行,get_object
会根据有名分组
的参数进行查询。
6.1、CBV 下继承GenericAPIView的API 示例
| |
| 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 被执行") |
| |
| |
| |
| |
| serializer = self.get_serializer(self.get_queryset(), many=True) |
| return Response(serializer.data) |
| |
| def post(self, request): |
| print("POST 被执行了") |
| |
| |
| 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 被执行") |
| |
| |
| serializer = self.get_serializer(instance=self.get_object(), many=False) |
| |
| return Response(serializer.data) |
| |
| |
| |
| |
| |
| def put(self, request, pk): |
| print("PUT 被执行") |
| |
| serializer = self.get_serializer(instance=self.get_object(), data=request.data) |
| if serializer.is_valid(): |
| serializer.save() |
| print("数据被更新了。") |
| return Response(serializer.data) |
| return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) |
| |
| def delete(self, request, pk): |
| print("DELETE被执行") |
| |
| |
| self.get_object().delete() |
| return Response(status=status.HTTP_204_NO_CONTENT) |
| |
七、Mixin工具类源码解析
- Mixin作用:
- 提供了几种后端视图组合,用来实现对数据资源进行增删改查处理流程。即封装了数据资源的增删改查操作,其他视图可以通过继承相应的Mixin扩展类来复用代码,减少自己编写的代码量。
- 这些Mixin扩展类需要搭配GenericAPIView通用视图基类使用,因为它们的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法。
7.1、Mixin 保函的视图组合
7.1.1、CreateModelMixin
- 作用:
- 创建(添加)操作视图扩展类,提供
create(self, request, *args, **kwargs)
方法快速实现创建(添加)视图,返回201
状态码。
| class CreateModelMixin: |
| """ |
| Create a model instance. |
| """ |
| def create(self, request, *args, **kwargs): |
| serializer = self.get_serializer(data=request.data) |
| serializer.is_valid(raise_exception=True) |
| self.perform_create(serializer) |
| headers = self.get_success_headers(serializer.data) |
| return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) |
| |
| def perform_create(self, serializer): |
| serializer.save() |
| |
| def get_success_headers(self, data): |
| try: |
| return {'Location': str(data[api_settings.URL_FIELD_NAME])} |
| except (TypeError, KeyError): |
| return {} |
7.1.2、ListModelMixin
- 作用:
- 列表视图扩展类,提供
list(request, *args, **kwargs)
方法快速实现列表视图,返回200
状态码。
- 该
Mixin
的list
方法会对数据进行过滤和分页。
| class ListModelMixin: |
| """ |
| List a queryset. |
| """ |
| def list(self, request, *args, **kwargs): |
| queryset = self.filter_queryset(self.get_queryset()) |
| |
| page = self.paginate_queryset(queryset) |
| if page is not None: |
| serializer = self.get_serializer(page, many=True) |
| return self.get_paginated_response(serializer.data) |
| |
| serializer = self.get_serializer(queryset, many=True) |
| return Response(serializer.data) |
7.1.3、RetrieveModelMixin
- 作用:
- 数据详情页视图扩展类,提供
retrieve(self, request, *args, **kwargs)
方法快速实现列表视图,返回200
状态码。
| class RetrieveModelMixin: |
| """ |
| Retrieve a model instance. |
| """ |
| def retrieve(self, request, *args, **kwargs): |
| instance = self.get_object() |
| serializer = self.get_serializer(instance) |
| return Response(serializer.data) |
7.1.4、UpdateModelMixin
- 作用:
- 更新操作视图扩展类,提供
update(self, request, *args, **kwargs)
方法快速实现列表视图,返回200
状态码。
| class UpdateModelMixin: |
| """ |
| Update a model instance. |
| """ |
| def update(self, request, *args, **kwargs): |
| partial = kwargs.pop('partial', False) |
| instance = self.get_object() |
| serializer = self.get_serializer(instance, data=request.data, partial=partial) |
| serializer.is_valid(raise_exception=True) |
| self.perform_update(serializer) |
| |
| if getattr(instance, '_prefetched_objects_cache', None): |
| |
| |
| 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 示例
| |
| from d4.models.d4_models import D4Dic |
| from d4.serializers.d4_serializers import D4DicSerializer |
| from rest_framework.generics import GenericAPIView |
| from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin |
| |
| |
| class DicList(GenericAPIView, ListModelMixin, CreateModelMixin): |
| ''' |
| CBV下的系统字典管理API |
| ''' |
| queryset = D4Dic.objects.all() |
| serializer_class = D4DicSerializer |
| |
| def get(self, request): |
| print("GET 被执行") |
| return self.list(request) |
| |
| def post(self, request): |
| print("POST 被执行了") |
| return self.create(request) |
| |
| |
| class DicDetail(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): |
| ''' |
| CBV下的系统字典管理API |
| ''' |
| queryset = D4Dic.objects.all() |
| serializer_class = D4DicSerializer |
| |
| def get(self, request, pk): |
| print("GET 被执行") |
| return self.retrieve(request, pk) |
| |
| def put(self, request, pk): |
| print("PUT 被执行") |
| return self.update(request, pk) |
| |
| def delete(self, request, pk): |
| print("DELETE被执行") |
| return self.destroy(request, pk) |
| |
7.3、Mixin 再封装
由7.2k也看出DicList
类和DicDetail
类表示了Dic
资源的5个操作(增删改查查),而其他资源如果也只想实现这5个操作,那么代码相似度是极高的。因此可以对其进行二次封装,封装的思路主要是简化了继承关系和对应的操作。封装的类如下:
7.3.1、CreateAPIView
继承了CreateModelMixin
类和GenericAPIView
类,并实现了post
操作。
| class CreateAPIView(mixins.mixins.CreateModelMixin, |
| GenericAPIView): |
| """ |
| Concrete view for creating a model instance. |
| """ |
| def post(self, request, *args, **kwargs): |
| return self.create(request, *args, **kwargs) |
7.3.2、ListAPIView
继承了ListModelMixin
类和GenericAPIView
类,并实现了get
操作。
| class ListAPIView(mixins.ListModelMixin, |
| GenericAPIView): |
| """ |
| Concrete view for listing a queryset. |
| """ |
| def get(self, request, *args, **kwargs): |
| return self.list(request, *args, **kwargs) |
7.3.3、RetrieveAPIView
继承了RetrieveModelMixin
类和GenericAPIView
类,并实现了get
操作。
| class RetrieveAPIView(mixins.RetrieveModelMixin, |
| GenericAPIView): |
| """ |
| Concrete view for retrieving a model instance. |
| """ |
| def get(self, request, *args, **kwargs): |
| return self.retrieve(request, *args, **kwargs) |
7.3.4、DestroyAPIView
继承了DestroyModelMixin
类和GenericAPIView
类,并实现了delete
操作。
| class DestroyAPIView(mixins.DestroyModelMixin, |
| GenericAPIView): |
| """ |
| Concrete view for deleting a model instance. |
| """ |
| def delete(self, request, *args, **kwargs): |
| return self.destroy(request, *args, **kwargs) |
7.3.5、UpdateAPIView
继承了UpdateModelMixin
类和GenericAPIView
类,并实现了put
、patch
操作。
| class UpdateAPIView(mixins.UpdateModelMixin, |
| GenericAPIView): |
| """ |
| Concrete view for updating a model instance. |
| """ |
| def put(self, request, *args, **kwargs): |
| return self.update(request, *args, **kwargs) |
| |
| def patch(self, request, *args, **kwargs): |
| return self.partial_update(request, *args, **kwargs) |
7.3.6、ListCreateAPIView
继承了ListModelMixin
,CreateModelMixin
类和GenericAPIView
类,并实现了get
、post
操作。
| class ListCreateAPIView(mixins.ListModelMixin, |
| mixins.CreateModelMixin, |
| GenericAPIView): |
| """ |
| Concrete view for listing a queryset or creating a model instance. |
| """ |
| def get(self, request, *args, **kwargs): |
| return self.list(request, *args, **kwargs) |
| |
| def post(self, request, *args, **kwargs): |
| return self.create(request, *args, **kwargs) |
7.3.7、RetrieveUpdateAPIView
继承了RetrieveModelMixin
,UpdateModelMixin
类和GenericAPIView
类,并实现了get
、put
、patch
操作。
| class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, |
| mixins.UpdateModelMixin, |
| GenericAPIView): |
| """ |
| Concrete view for retrieving, updating a model instance. |
| """ |
| def get(self, request, *args, **kwargs): |
| return self.retrieve(request, *args, **kwargs) |
| |
| def put(self, request, *args, **kwargs): |
| return self.update(request, *args, **kwargs) |
| |
| def patch(self, request, *args, **kwargs): |
| return self.partial_update(request, *args, **kwargs) |
7.3.8、RetrieveDestroyAPIView
继承了RetrieveModelMixin
,DestroyModelMixin
类和GenericAPIView
类,并实现了get
、delete
操作。
| class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, |
| mixins.DestroyModelMixin, |
| GenericAPIView): |
| """ |
| Concrete view for retrieving or deleting a model instance. |
| """ |
| def get(self, request, *args, **kwargs): |
| return self.retrieve(request, *args, **kwargs) |
| |
| def delete(self, request, *args, **kwargs): |
| return self.destroy(request, *args, **kwargs) |
7.3.9、RetrieveUpdateDestroyAPIView
继承了RetrieveModelMixin
、UpdateModelMixin
、DestroyModelMixin
类和GenericAPIView
类,并实现了get
、put
、patch
、delete
操作。
| class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, |
| mixins.UpdateModelMixin, |
| mixins.DestroyModelMixin, |
| GenericAPIView): |
| """ |
| Concrete view for retrieving, updating or deleting a model instance. |
| """ |
| def get(self, request, *args, **kwargs): |
| return self.retrieve(request, *args, **kwargs) |
| |
| def put(self, request, *args, **kwargs): |
| return self.update(request, *args, **kwargs) |
| |
| def patch(self, request, *args, **kwargs): |
| return self.partial_update(request, *args, **kwargs) |
| |
| def delete(self, request, *args, **kwargs): |
| return self.destroy(request, *args, **kwargs) |
7.4、Mixin再封装后的API示例
这里DicList
类和DicDetail
类分别继承了ListCreateAPIView
和RetrieveUpdateDestroyAPIView
,简化了代码。
| from d4.models.d4_models import D4Dic |
| from d4.serializers.d4_serializers import D4DicSerializer |
| from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView |
| |
| |
| class DicList(ListCreateAPIView): |
| ''' |
| CBV下的系统字典管理API |
| ''' |
| queryset = D4Dic.objects.all() |
| serializer_class = D4DicSerializer |
| |
| |
| class DicDetail(RetrieveUpdateDestroyAPIView): |
| ''' |
| CBV下的系统字典管理API |
| ''' |
| queryset = D4Dic.objects.all() |
| serializer_class = D4DicSerializer |
| |
八、ViewSet源码解析
ViewSetMixin
重构了分发机制。
通过路由设置改变操作绑定的处理函数。
- eg.
- get操作绑定操作函数为get_object,post操作绑定操作函数为post_object。
re_path(r'^dic/$', DicList.as_view("get":"get_object", "post":"post_object"))
8.1、ViewSetMixin
| class ViewSetMixin: |
| """ |
| This is the magic. |
| |
| Overrides `.as_view()` so that it takes an `actions` keyword that performs |
| the binding of HTTP methods to actions on the Resource. |
| |
| For example, to create a concrete view binding the 'GET' and 'POST' methods |
| to the 'list' and 'create' actions... |
| |
| view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) |
| """ |
| |
| @classonlymethod |
| def as_view(cls, actions=None, **initkwargs): |
| """ |
| Because of the way class based views create a closure around the |
| instantiated view, we need to totally reimplement `.as_view`, |
| and slightly modify the view function that is created and returned. |
| """ |
| |
| |
| cls.name = None |
| cls.description = None |
| |
| |
| |
| |
| cls.suffix = None |
| |
| |
| cls.detail = None |
| |
| |
| |
| cls.basename = None |
| |
| |
| if not actions: |
| raise TypeError("The `actions` argument must be provided when " |
| "calling `.as_view()` on a ViewSet. For example " |
| "`.as_view({'get': 'list'})`") |
| |
| |
| 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)) |
| |
| |
| if 'name' in initkwargs and 'suffix' in initkwargs: |
| raise TypeError("%s() received both `name` and `suffix`, which are " |
| "mutually exclusive arguments." % (cls.__name__)) |
| |
| |
| |
| def view(request, *args, **kwargs): |
| self = cls(**initkwargs) |
| |
| if 'get' in actions and 'head' not in actions: |
| actions['head'] = actions['get'] |
| |
| |
| |
| |
| self.action_map = actions |
| |
| |
| |
| |
| |
| |
| |
| for method, action in actions.items(): |
| handler = getattr(self, action) |
| |
| setattr(self, method, handler) |
| |
| self.request = request |
| self.args = args |
| self.kwargs = kwargs |
| |
| |
| return self.dispatch(request, *args, **kwargs) |
| |
| |
| update_wrapper(view, cls, updated=()) |
| |
| |
| |
| update_wrapper(view, cls.dispatch, assigned=()) |
| |
| |
| |
| |
| 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': |
| |
| |
| |
| 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() |
| |
| |
| if self.detail is None: |
| return action_urls |
| |
| |
| 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 |
| |
| 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 示例
| |
| 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"})), |
| ] |
| |
| |
| 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 被执行") |
| |
| |
| dics = D4Dic.objects.all() |
| serializer = D4DicSerializer(dics, many=True) |
| |
| |
| |
| |
| 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 被执行") |
| |
| 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) |
| |
| |
| |
| |
| |
| 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 示例
| |
| 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"})), |
| ] |
| |
| |
| 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 被执行") |
| |
| |
| |
| |
| serializer = self.get_serializer(self.get_queryset(), many=True) |
| return Response(serializer.data) |
| |
| def add(self, request): |
| print("POST 被执行了") |
| |
| |
| 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 被执行") |
| |
| |
| serializer = self.get_serializer(instance=self.get_object(), many=False) |
| |
| return Response(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进行重写。
| |
| 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"})), |
| ] |
| |
| 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 示例
| |
| 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"})), |
| ] |
| |
| |
| 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 示例
| |
| 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"})), |
| ] |
| |
| 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进行路由注册。
| |
| |
| from d4.d4_views import DicGVS |
| from rest_framework import routers |
| |
| |
| router = routers.DefaultRouter() |
| router.register('dic', DicGVS, basename='dic') |
| |
| |
| |
| |
| urlpatterns = [ |
| ] |
| |
| urlpatterns += router.urls |
| |
| |
| from d4.d4_models import D4Dic |
| from d4.d4_serializers import D4DicSerializer |
| from rest_framework.viewsets import ModelViewSet |
| |
| |
| class DicGVS(ModelViewSet): |
| ''' |
| CBV下的系统字典管理API |
| ''' |
| queryset = D4Dic.objects.all() |
| serializer_class = D4DicSerializer |
9.3、ViewSet视图集中的额外行为路由
9.3.1、方式一
例如在dic_view.py
中额外添加获取最后一条数据的lastest
操作和只更新value
的read
操作。
- 为额外行为单独设置路由
re_path(r'^dic/lastest$', DicGVS.as_view({"get": "lastest"})),
re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})/read$', DicGVS.as_view({"put": "read"})),
- 注意
lastest
为get
操作,且不指定 pk 值,做一只要在资源路径末端直接加操作名称。
read
为put
更新操作,是特定某一条数据的更新操作,需要pk 值,因此在特定pk值记录的末端添加操作名称。
| |
| 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 |
| |
| |
| 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 值。
| |
| 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 |
| |
| |
| 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
则没有。

| |
| 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
| |
| 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、注册组件
| |
| |
| INSTALLED_APPS = [ |
| ... |
| 'django_filters', |
| ... |
| ] |
| |
| REST_FRAMEWORK = { |
| ... |
| |
| 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] |
| ... |
| } |
10.1.3、配置过滤规则
| |
| 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 |
| |
| |
| |
| |
| |
| 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、方式一
| |
| |
| |
| from d4.models import D4Dic |
| from django_filters import rest_framework as filters |
| |
| |
| class DicFilter(filters.FilterSet): |
| """ 字典表 """ |
| |
| |
| 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') |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class Meta: |
| |
| model = D4Dic |
| |
| |
| |
| |
| |
| |
| 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、方式二
| |
| |
| 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'] |
| } |
| |
| 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)
方法,该方法应返回一个经过过滤的新查询集。除了允许客户端执行搜索和过滤外,对于限制通用过滤器后端仅响应给定请求或用户的对象也很有用。
| |
| from rest_framework.filters import BaseFilterBackend |
| |
| |
| class IsOwnerFilterBackend(BaseFilterBackend): |
| """ |
| 仅允许用户查看自己对象的过滤器。 |
| """ |
| |
| def filter_queryset(self, request, queryset, view): |
| return queryset.filter(owner=request.user) |
| |
| |
| from d4.filters.is_owner_filter_backend import IsOwnerFilterBackend |
| |
| |
| |
| |
| 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、基于当前用户的过滤查询
| |
| from d4.d4_models import D4Dic |
| from d4.d4_serializers import D4DicSerializer |
| from rest_framework.viewsets import ReadOnlyModelViewSet |
| |
| |
| class DicGVS(ReadOnlyModelViewSet): |
| ''' |
| CBV下的系统字典管理API |
| ''' |
| |
| serializer_class = D4DicSerializer |
| |
| def get_queryset(self): |
| ''' |
| 该视图返回当前验证用户创建的字典。 |
| ''' |
| |
| user = self.request.user |
| |
| 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()) |
| |
| from d4.d4_models import D4Dic |
| from d4.d4_serializers import D4DicSerializer |
| from rest_framework.viewsets import ReadOnlyModelViewSet |
| |
| |
| class DicGVS(ReadOnlyModelViewSet): |
| ''' |
| CBV下的系统字典管理API |
| ''' |
| |
| serializer_class = D4DicSerializer |
| |
| def get_queryset(self): |
| ''' |
| 该视图返回URL中code有名分组参数指定的字典清单 |
| ''' |
| |
| _code = self.kwargs['code'] |
| return D4Dic.objects.filter(code = _code) |
10.1.5.1、基于查询参数的过滤查询
| |
| from d4.d4_models import D4Dic |
| from d4.d4_serializers import D4DicSerializer |
| from rest_framework.viewsets import ReadOnlyModelViewSet |
| |
| |
| class DicGVS(ReadOnlyModelViewSet): |
| ''' |
| CBV下的系统字典管理API |
| ''' |
| |
| 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
| |
| |
| REST_FRAMEWORK = { |
| ... |
| |
| 'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.SearchFilter'], |
| |
| |
| ... |
| } |
10.2.2、视图中配置SearchFilter
| |
| |
| |
| 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 |
| |
| |
| |
| search_fields = ['name', 'code'] |
url
格式
http://127.0.0.1:8000/d4/dic/?search=4
- 注意:
- 上述例子会根据
search
的值,去配置的字段 name
或者 code
,只要有一个模糊匹配到search
的值,就满足条件。
- 双下划线表示法:
- 使用双下划线表示法在
ForeignKey
或 ManyToManyField
上执行搜索。
search_fields = ['username', 'email', 'profile__profession']
- 可以使用双下划线表示法对
JSONField
和 HStoreField
字段内嵌套的数据按数据结构进行搜索。
search_fields = ['data__breed', 'data__owner__other_pets__0__name']
- 多个关键字搜索:
- 默认情况下搜索将使用不区分大小写的部分匹配,搜索参数可以包含多个搜索词,多个搜索词应将其用空格和/或 逗号分隔,使用多个搜索词则仅当所有提供的词都匹配时才符合条件。
- 搜索行为修饰符
- 可通过在
search_fields
数组元素字符前添加各种字符来限制搜索行为。
‘^’
:开始搜索。
‘=’
:完全匹配。
‘@’
:全文搜索(当前仅支持PostgreSQL数据库)。
‘$’
:正则表达式搜索。
search_fields = ['=username','=email']
效果:

10.3、排序
OrderingFilter
默认允许用户对 serializer_class
属性指定的序列化器上的任何可读字段进行排序。
10.3.1、全局配置排序组件
| |
| |
| REST_FRAMEWORK = { |
| ... |
| |
| 'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.OrderingFilter'], |
| |
| |
| ... |
| } |
10.3.2、视图中配置排序规则
| |
| |
| |
| 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 |
| |
| |
| |
| |
| |
| ordering_fields = ['name', 'code'] |
| |
| |
10.4、分页
10.4.1、全局配置PageNumberPagination
| |
| |
| REST_FRAMEWORK = { |
| ... |
| |
| 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', |
| |
| 'PAGE_SIZE': 10 |
| ... |
| } |
10.4.2、视图中配置分页器
| |
| |
| |
| 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 |
| |
| |
| |
| search_fields = ['name', 'code'] |
| |
| |
| |
10.4.3、自定义分页器
- 可以在子类中自定义的分页器属性:
page_size_query_param = 'page_size'
前端发送的每页数目关键字名,默认为None
max_page_size = 4
前端最多能设置的每页数量
page_size = 2
每页数目
page_query_param = 'page'
前端发送的页数关键字名,默认为page
| |
| from rest_framework import pagination |
| from rest_framework.response import Response |
| |
| |
| class PageNumberPagination2(pagination.PageNumberPagination): |
| page_size = 10 |
| page_size_query_param = "page_size" |
| max_page_size = 100 |
| max_page_size = 4 |
| |
| |
| |
| 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, |
| } |
| ) |
| |
| |
| from d4.serializers.pagination.page_number_pagination import PageNumberPagination2 |
| |
| |
| |
| |
| 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 |
| |
| |
| |
| search_fields = ['name', 'code'] |
| |
| pagination_class = PageNumberPagination2 |
| |
| |
| |
| |
| REST_FRAMEWORK = { |
| ... |
| |
| 'DEFAULT_PAGINATION_CLASS': 'd4.serializers.pagination.PageNumberPagination2', |
| |
| 'PAGE_SIZE': 10 |
| ... |
| } |
10.4.4、请求示例
url
格式
http://127.0.0.1:8000/d4/dic/?page=1
10.4.5、手写分页
| |
| 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 |
| ''' |
| |
| serializer_class = D4DicSerializer |
| |
| def list(self, request): |
| |
| |
| num = request.GET.get('num', 1) |
| num = int(num) |
| queryset = D4Dic.objects.all() |
| |
| 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
| |
| |
| REST_FRAMEWORK = { |
| ... |
| |
| 'DEFAULT_THROTTLE_CLASSES': [ |
| 'rest_framework.throttling.AnonRateThrottle', |
| 'rest_framework.throttling.UserRateThrottle', |
| ], |
| |
| 'DEFAULT_THROTTLE_RATES': { |
| 'user': '10/hour', |
| 'anon': '3/day', |
| }, |
| 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation', |
| 'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata', |
| 'DEFAULT_VERSIONING_CLASS': None, |
| ... |
| } |
DEFAULT_THROTTLE_RATES
可以使用 second
,minute
,hour
或day
来指明周期。
- 超出限定的次数会报错
-"detail": "Request was throttled. Expected available in 86398 seconds."
10.3.2、视图中配置限流规则
| |
| |
| 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): |
| |
| |
| |
| |
| |
| if request.META['REMOTE_ADDR'] in self.blacklist: |
| return False |
| return True |
| |
| |
| |
| |
| |
| from d4.throttling.custom_throttle import CustomThrottle |
| |
在上面的示例中,我们创建了一个名为 CustomThrottle 的自定义限流类。它继承了 throttling.BaseThrottle 类,并重写了 allow_request 方法来定义限流逻辑。在这个示例中,我们简单地检查请求的 IP 地址是否在黑名单中,如果是,则拒绝请求。你可以根据自己的需求来修改和扩展这个方法。
| |
| |
| |
| 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
中配置全局限流策略。
| |
| from d4.throttling.custom_throttle import CustomThrottle |
| |
| |
| |
| REST_FRAMEWORK = { |
| ... |
| |
| 'DEFAULT_THROTTLE_CLASSES': [ |
| 'd4.throttling.CustomThrottle', |
| ], |
| |
| 'DEFAULT_THROTTLE_RATES': { |
| 'custom_throttle': '10/hour', |
| }, |
| ... |
| } |
十二、认证
重要参考:Django REST Framework 之认证、权限(超详细)
系统需要登入后才能访问,登入过程既认证过程。
DRF
认证过程拆解:
- 身份验证是将传入的请求对象(
request
)与一组标识凭据(例如请求来自的用户或其签名的令牌token
)相关联的机制。
REST framework
提供了一些开箱即用的身份验证方案,并且还允许你实现自定义方案。DRF
中每个认证方案都被封装成一个类。你可以在视图中使用一个或多个认证方案类。REST framework
将尝试使用列表中的每个类进行身份验证,并使用成功完成验证的第一个类的返回的元组设置 request.user
和request.auth
。
- 用户通过认证后
request.user
返回Django
的User
实例,否则返回AnonymousUser
的实例。request.auth
通常为None
。如果使用token
认证,request.auth
可以包含认证过的token
。
DRF 提供的认证方案
Session
认证SessionAuthentication
类:此认证方案使用Django
的默认session
后端进行身份验证。当客户端发送登录请求通过验证后,Django
通过session
将用户信息存储在服务器中保持用户的请求状态。Session
身份验证适用于与你的网站在相同的Session
环境中运行的AJAX
客户端。 (ps:这也是Session
认证的最大弊端)。
基本认证BasicAuthentication
类:此认证方案使用HTTP
基本认证,针对用户的用户名和密码进行认证。使用这种方式后浏览器会跳出登录框让用户输入用户名和密码认证。(ps:基本认证通常只适用于测试)。
Token
认证TokenAuthentication
类:该认证方案是DRF提供的使用简单的基于Token
的HTTP
认证方案。当客户端发送登录请求时,服务器便会生成一个Token并将此Token
返回给客户端,作为客户端进行请求的一个标识以后客户端只需带上这个Token
前来请求数据即可,(ps:学习笔记中会详细介绍基于Token
的JWT
认证方案)。
远程认证RemoteUserAuthentication
类:此认证方案为用户名不存在的用户自动创建用户实例。
12.1、全局认证
settings.py
中设置默认的全局认证方案
- 在
settings.py
配置文件中 REST_FRAMEWORK
添加配置
| |
| |
| REST_FRAMEWORK = { |
| …… |
| |
| "DEFAULT_AUTHENTICATION_CLASSES": [ |
| |
| "rest_framework_simplejwt.authentication.JWTAuthentication", |
| |
| |
| |
| |
| ], |
| …… |
| } |
- 如果在全局认证下,有些接口不想加上认证,
可以在这个类的属性 authentication_classes = []
即可。
- 除了上述自己实现的认证类,
REST Framework
为我们提供了四种认证类:
| 1. BasicAuthentication |
| 2. SessionAuthentication |
| 3. TokenAuthentication |
| 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), |
| 'auth': unicode(request.auth), |
| } |
| return Response(content) |
| |
| |
12.3、基于Token的认证
12.3.1、DRF自带的Token认证示例
| |
| |
| INSTALLED_APPS = ( |
| ... |
| 'rest_framework.authtoken' |
| ) |
| |
12.3.2、基于JWT的Token认证示例
JWT(JSON Web Token)
是一种使用Token进行身份认证的开放标准。
- 与DRF内置的
TokenAuthentication
方案不同,JWT身份验证不需要使用数据库来验证令牌,JWT
生产的Token
自包含了业务信息。这些信息包括Token
的有效期、附带的业务信息例如UserId,加密标准等。
JWT
可以轻松设置token
失效期或刷新token
, 是API开发中当前最流行的跨域认证解决方案。
- 学习笔记中将详细介绍
JWT
认证的工作原理以及如何通过djangorestframework-simplejwt
这个第三方包轻松实现JWT
认证。
12.3.2.1、JWT原理
JWT
用于为应用程序创建访问token
,通常适用于API身份验证和服务器到服务器的授权。
JWT
定义了一种紧凑且自包含的方式。该方式用于各方之间安全地将信息以JSON
对象传输。
- 紧凑(简洁):
Token
数据量比较少,可以通过url
参数,http
请求提交的数据以及http header
多种方式来传递。
- 自包含:
Token
字符串可以包含很多信息,比如用户id
,用户名
,订单号id
等,
- PS:虽然其他人拿到该信息,就可以拿到关键业务信息。但由于此信息是经过数字签名的,因此可以被验证和信任。
JWT的组成
JSON Web Token
由三部分组成,这些部分由点(.)分隔,分别是header
(头部),payload
(有效负载)和signature
(签名)。

header
(头部): 识别以何种算法来生成签名;
pyload
(有效负载): 用来存放实际需要传递的数据;
signature
(签名): 安全验证token
有效性,防止数据被篡改。
JWT用户认证过程

首先客户端提交用户登录信息验证身份通过后,服务器生成一个用于证明用户身份的令牌(token
),也就是一个加密后的长字符串,并将其发送给客户端。在后续请求中,客户端以各种方式(比如通过url参数或者请求头)将这个令牌发送回服务器,服务器就知道请求来自哪个特定身份的用户了。具体步骤如下。
- 首先,前端通过
Web
表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST
请求。建议的方式是通过SSL
加密的传输(https协议
),从而避免敏感信息被嗅探。
- 后端核对用户名和密码成功后,将用户的id等其他信息作为
JWT Payload
(负载),将其与头部分别进行Base64
编码拼接后签名,形成一个JWT
。形成的JWT就是一个形同aaa.bbb.ccc
的字符串。
- 后端将
JWT
字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage
或sessionStorage
上,退出登录时前端删除保存的JWT即可。
- 前端在每次请求时将
JWT
放入HTTP Header
中的Authorization
位。(解决XSS
和XSRF
问题)
- 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查
Token
是否过期;检查Token
的接收方是否是自己(可选)。
PS:通过http
传输的数据实际上是加密后的JWT
,它是由两个点分割的base64-URL
长字符串组成,解密后我们可以得到header
, payload
和signature
三部分。
PS:DRF
接口会自动验证token
的有效性。Simple JWT
中的access token
默认只有5分钟有效。access token
过期后访问将得到token已失效或过期的提示。
PS:Simple JWT
中的refresh token
默认有效期为24小时。
12.3.2.2、JWT安装
本学习笔记使用djangorestframework-simplejwt
举例。
| > pip install djangorestframework-simplejwt |
12.3.2.3、JWT 全局配置
settings.py
中设置默认的全局认证方案
- 在
settings.py
配置文件中 REST_FRAMEWORK
添加配置
| |
| |
| REST_FRAMEWORK = { |
| …… |
| |
| "DEFAULT_AUTHENTICATION_CLASSES": [ |
| |
| "rest_framework_simplejwt.authentication.JWTAuthentication" |
| ], |
| …… |
| } |
urls.py
中设置获取和刷新token
的urls
地址。
| |
| 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 = [ |
| |
| |
| path("", include(router.urls)), |
| |
| path(r"api-auth/", include("rest_framework.urls", namespace="rest_framework")), |
| path("admin/", admin.site.urls), |
| |
| path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), |
| |
| path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), |
| |
| path(r"d4/", include("d4.urls"), name="d4"), |
| ] |
| |
12.3.2.4、解决跨域问题
安装支持包
| > pip install django-cors-headers |
注册App
| |
| INSTALLED_APPS = [ |
| …… |
| 'corsheaders', |
| …… |
| ] |
| |
| MIDDLEWARE = [ |
| …… |
| 'corsheaders.middleware.CorsMiddleware', |
| …… |
| ] |
12.3.2.5、测试认证是否生效
Postman
登入请求获取Token
。(注意跨域问题)。

使用JWT Token
请求数据

如果Token
不正确或为空。

12.4、自定义认证
12.4.1、自定义令牌(Token)
如果希望在payload部分提供更多信息,比如用户的username,可通过自定义令牌(token)实现。
- 首先,编写自定义序列化器
MyTokenObtainPairSerializer
,该序列化器继承了TokenObtainPairSerializer
类。
| from rest_framework_simplejwt.serializers import TokenObtainPairSerializer |
| |
| class MyTokenObtainPairSerializer(TokenObtainPairSerializer): |
| @classmethod |
| def get_token(cls, user): |
| token = super(MyTokenObtainPairSerializer, cls).get_token(user) |
| |
| |
| token['username'] = user.username |
| return token |
| |
- 自定义视图
MyObtainTokenPairView
| from rest_framework_simplejwt.views import TokenObtainPairView |
| from rest_framework.permissions import AllowAny |
| from .serializers import MyTokenObtainPairSerializer |
| |
| class MyObtainTokenPairView(TokenObtainPairView): |
| permission_classes = (AllowAny,) |
| serializer_class = MyTokenObtainPairSerializer |
| |
- 修改路由
urls.py
| |
| 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 = [ |
| |
| |
| path("", include(router.urls)), |
| |
| path(r"api-auth/", include("rest_framework.urls", namespace="rest_framework")), |
| path("admin/", admin.site.urls), |
| |
| |
| path("token/", MyObtainTokenPairView.as_view(), name="token_obtain_pair"), |
| |
| |
| path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), |
| |
| 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**
| |
| |
| "DEFAULT_AUTHENTICATION_CLASSES": [ |
| |
| "rest_framework_simplejwt.authentication.JWTAuthentication", |
| |
| |
| |
| |
| |
| |
| ], |
12.4.3、自定义认证方式二:自定义认证后台(Backend)
如果需要支持username以外的登入方式,可以通过自定义认证后台(Backend)实现。
例如希望为系统额外提供通过email进行登入的功能,即同时支持username和email登入。
自定义认证类MyCustomBackend
| |
| from django.contrib.auth.backends import ModelBackend |
| |
| 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**
| |
| |
| AUTHENTICATION_BACKENDS = [ |
| "d4.views.MyCustomBackend", |
| ] |
| |
测试验证

14.4.4、两种方式的区别
在Django中,自定义认证通常涉及到修改或扩展默认的认证过程。当你想要自定义认证时,可以选择继承ModelBackend
或BaseAuthentication
。但是,这两个类服务于不同的目的,并位于Django的不同认证系统中,因此它们的用途和实现方式存在显著的区别。
- 继承
ModelBackend
:
ModelBackend
是Django默认的身份验证后端,它使用Django的用户模型(通常是auth.User
)来验证用户的凭证。这个类实现了authenticate
方法,该方法接收用户名和密码作为参数,并返回一个用户对象(如果凭证有效)或None
。
当你需要扩展或修改默认的基于模型的认证逻辑时,可以继承ModelBackend
并重写authenticate
方法。例如,你可能想要支持额外的认证字段,或者添加自定义的验证逻辑。
优点:
- 易于扩展默认的基于模型的认证逻辑。
- 可以保留Django用户模型及其关联的功能(如权限和组)。
缺点:
- 如果你的认证需求与Django用户模型差异很大,那么使用
ModelBackend
可能不够灵活。
- 继承
BaseAuthentication
:
BaseAuthentication
是Django REST framework(DRF)中的一个类,用于实现API的身份验证。DRF提供了一套强大的工具来构建Web API,包括认证和权限管理。BaseAuthentication
是一个基类,用于创建自定义的身份验证类。
当你需要为DRF API实现自定义的身份验证逻辑时,可以继承BaseAuthentication
并重写authenticate
方法。例如,你可能想要支持令牌认证、OAuth2或其他非标准的认证机制。
优点:
- 专为Web API设计,提供了与DRF的无缝集成。
- 支持多种认证机制,灵活性高。
缺点:
- 仅适用于DRF API,不适用于传统的Django视图或表单。
- 需要安装和使用DRF及其相关依赖。
总结:
- 如果你正在使用Django的默认用户模型,并且只需要扩展或修改基于模型的认证逻辑,那么继承
ModelBackend
是更合适的选择。
- 如果你正在使用DRF构建Web API,并且需要实现自定义的身份验证逻辑(如令牌认证、OAuth2等),那么继承
BaseAuthentication
是更好的选择。
十三、权限
13.1、全局配置权限
| |
| |
| INSTALLED_APPS = [ |
| 'rest_framework', |
| ] |
| |
| |
| |
| REST_FRAMEWORK = { |
| |
| "DEFAULT_PERMISSION_CLASSES": [ |
| |
| "rest_framework.permissions.IsAuthenticated", |
| |
| |
| ], |
| } |
| |
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、类试图配置权限
| |
| |
| |
| 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、配置登入页面路由
| |
| 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 = [ |
| |
| |
| path("", include(router.urls)), |
| |
| path(r"api-auth/", include("rest_framework.urls", namespace="rest_framework")), |
| |
| path("admin/", admin.site.urls), |
| |
| 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、自定义权限类
| |
| from rest_framework import permissions |
| |
| class IsOwnerOrReadOnly(permissions.BasePermission): |
| """ |
| 添加自定义权限,只允许对象的创建者才能编辑它。 |
| """ |
| def has_object_permission(self, request, view, obj): |
| |
| |
| if request.method in permissions.SAFE_METHODS: |
| return True |
| |
| return obj.author == request.user |
| |
| |
| from d4.permissions.is_owner_or_read_only import IsOwnerOrReadOnly |
| |
13.3.3、在视图中使用自定义权限类
| |
| |
| |
| 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、全局添加自定义权限类
| |
| from d4.permissions.is_owner_or_read_only import IsOwnerOrReadOnly |
| |
| |
| |
| "DEFAULT_PERMISSION_CLASSES": [ |
| |
| "rest_framework.permissions.IsAuthenticated", |
| |
| |
| "d4.permissions.IsOwnerOrReadOnly", |
| ], |
参见
https://www.cnblogs.com/tjw-bk/p/13952297.html
附录、D4Dic数据模型
| |
| 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' |
附录、Simple JWT
的默认设置
| |
| SIMPLE_JWT = { |
| 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), |
| 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), |
| |
| |
| |
| |
| 'ROTATE_REFRESH_TOKENS': False, |
| |
| 'BLACKLIST_AFTER_ROTATION': False, |
| 'ALGORITHM': 'HS256', |
| 'SIGNING_KEY': settings.SECRET_KEY, |
| |
| |
| "UPDATE_LAST_LOGIN": False, |
| |
| "VERIFYING_KEY": "", |
| "AUDIENCE": None, |
| "ISSUER": None, |
| "JSON_ENCODER": None, |
| "JWK_URL": None, |
| "LEEWAY": 0, |
| |
| |
| "AUTH_HEADER_TYPES": ("Bearer",), |
| |
| "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", |
| |
| "USER_ID_FIELD": "id", |
| |
| "USER_ID_CLAIM": "user_id", |
| |
| |
| "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule", |
| |
| |
| "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), |
| |
| "TOKEN_TYPE_CLAIM": "token_type", |
| |
| "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser", |
| |
| |
| "JTI_CLAIM": "jti", |
| |
| |
| "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp", |
| |
| "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5), |
| |
| "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", |
| |
| "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, |
| } |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异