【16.0】DRF大总结

发布时间 2023-08-01 12:14:40作者: Chimengmeng

【一】DRF入门规范

  • 前后端开发模式:

    • 混合:
      • 前后端代码交织在一起,同一份代码中既包含前端逻辑又包含后端逻辑。
      • 这种模式通常在小型项目或者简单的页面中使用,便于快速开发和维护。
    • 分离:
      • 前后端代码分离开发,前端专注于用户界面设计和交互逻辑,后端则负责数据处理和业务逻辑。
      • 这种模式通常在大型项目或者需要高度可扩展性、可维护性的项目中使用,可以更好地实现前后端分工合作。
  • API接口

    • 地址:
    • 请求方法:
      • HTTP协议定义了多种请求方法,常见的有GET、POST、PUT、DELETE等。
      • 请求方法用于指定对资源的操作类型。
      • 例如:GET表示获取资源,POST表示创建资源,PUT表示更新资源,DELETE表示删除资源。
    • 请求参数:
      • 请求参数用于传递给后端的数据
      • 可以是查询参数(在URL中拼接)、请求头参数或请求体参数。
      • 请求参数可以包括筛选条件、分页信息、身份验证信息等。
      • 例如:GET /api/users?name=John&age=25
    • 返回值:
      • API接口响应的数据,通常以JSON格式返回。
      • 返回值可以包含请求的结果、错误信息以及其他元数据。
      • 例如:
  • postman的使用

    • Postman是一款常用的API接口测试工具,可以用于发送HTTP请求、查看响应结果、调试接口等。
    • 使用Postman可以轻松创建请求、添加请求参数、设置请求头,还可进行断言和验证。
    • 此外,Postman还提供了协作和文档化的功能,方便团队合作和接口文档编写。
  • 序列化与反序列化

    • 序列化:
      • 将对象的状态转换为可以存储或传输的形式的过程称为序列化。
      • 在Web开发中,常见的序列化格式包括JSON、XML等。
      • 通过序列化,对象的属性值可以被转换为字符串或字节流进行传输。
    • 反序列化:
      • 将已序列化的数据恢复为对象的过程称为反序列化。
      • 反序列化过程将字符串或字节流转换为对象,使得可以读取对象的属性值和调用对象的方法。
  • restful规范

    • 使用HTTP协议定义请求方法:GET、POST、PUT、DELETE等。
    • 使用URL地址标识资源:每个资源都有一个唯一的URL地址。
    • 使用合适的HTTP状态码:响应请求时,使用正确的HTTP状态码表示请求的结果。
    • 使用合适的HTTP动词:HTTP动词用于表示对资源的操作类型。
    • 使用标准的数据格式:通常使用JSON格式传输数据。
  • 在Django中写符合规范的接口

    • Django Rest Framework(DRF)是一个用于构建Web API的强大工具包。
      • 通过DRF,可以方便地编写符合RESTful规范的接口。
    • 在Django中使用DRF,可以通过定义序列化器(Serializer)来指定API接口的输入输出格式,并通过视图集(ViewSet)或通用视图类(GenericAPIView)定义API接口的逻辑。
    • DRF还提供了丰富的验证、权限控制、分页和过滤等功能,可以帮助开发者快速构建高质量的API接口。

【二】CBV源码分析

  • 路由中
    • 路由中的视图类.as_view():
      • 在路由中,我们通常将视图类作为视图处理函数来处理请求。
      • 但是,路由系统需要将类视图转换成可调用的函数。
      • 因此,我们使用.as_view()方法将视图类转换为可调用的函数。
      • 这样,当请求匹配到该路由时,将调用该视图类的.as_view()方法。
    • Django的View的as_view():
      • Django的View类是CBV的基类。
      • as_view()View类中的一个类方法,用于生成可调用的视图函数。
      • 它实际上返回一个闭包函数,该函数在调用时会创建视图类的实例,并调用其dispatch()方法进行请求处理。
    • 执行结果的 view 内存地址:
      • 调用视图类的.as_view()方法后,返回的闭包函数表示视图函数。
      • 执行该闭包函数时,会创建视图类的实例,并调用其dispatch()方法处理请求。
      • 所以得到的执行结果是视图类的实例在内存中的地址。
    • 请求过来,路由匹配成功:
      • 当HTTP请求到达Django应用程序时,URL路由系统会根据请求的路径查找匹配的路由。
      • 如果路由匹配成功,就会找到与之对应的视图处理函数或视图类。
    • view(request):
      • 当路由匹配成功后,会将HTTP请求对象作为参数传递给视图处理函数或视图类。
      • 在CBV中,这个参数通常被命名为request
      • 所以view(request)的意思就是将获取到的HTTP请求对象传递给视图类。
    • return self.dispatch():
      • 在视图类的代码中,通常会调用self.dispatch()方法。
      • 该方法实际上是View类中的一个方法,用于根据不同的HTTP请求方法来分发请求到对应的方法处理函数。
      • 例如,对于GET请求,会调用视图类中的get()方法进行处理。
    • View的dispatch:
      • dispatch()方法负责根据不同的HTTP请求方法调用对应的方法处理函数。
      • 它会通过Python的反射机制,根据请求的方法动态获取并执行对应的方法。
    • 通过反射,不同的请求会执行视图类中的不同方法:
      • 反射是Python的一种特性,通过字符串的形式来调用对象的属性或方法。
      • 在CBV中,当请求到达时,dispatch()方法会通过反射来调用视图类中对应请求方法的处理函数
      • 例如get()post()等。
  • 综上所述,当请求到达时,Django的路由系统会匹配对应的路由,并通过.as_view()方法将视图类转换为可调用的函数。
  • 然后,传递请求对象给该函数并执行,最终调用视图类的dispatch()方法来根据不同的请求方法调用对应的处理函数。
  • 通过反射机制,不同的请求会执行视图类中不同的方法。

【三】APIView的执行流程

  • APIView继承了View:

    • APIView是Django REST Framework(DRF)提供的一个类,用于处理API请求。
    • 它继承了Django的View类,既包含了Django的视图功能,又增加了一些处理API请求的特性。
  • 视图类的class BookView(APIView):

    • 在使用DRF构建API时,我们可以创建一个继承自APIView的自定义视图类。
    • 这里的BookView就是一个自定义的视图类,它继承自APIView,用于处理关于图书的API请求。
  • 路由中视图类.as_view():

    • 类视图无法直接用于路由系统,因此需要将视图类转换为可调用的函数。
    • 在路由中,我们通常会调用视图类的.as_view()方法来将其转换为可调用的函数。
  • drf的APIView的as_view():

    • 在DRF中,APIView.as_view()方法是将视图类转换为可调用函数的入口。
    • 在调用APIView.as_view()方法时,它会调用父类View.as_view()方法。
  • 调用父类的as_view去除csrf:

    • 父类View.as_view()方法负责创建一个闭包函数,并在创建前通过装饰器去除了Django的CSRF保护机制。
    • 这是因为在API开发中,很少使用Django的CSRF保护。
  • View的as_view内部的view闭包函数 (相当于加了个装饰器):

    • View.as_view()方法内部,会创建一个闭包函数(wrapper function)。
    • 这个闭包函数实际上包装了视图类的实例化和请求处理过程。
    • 通过闭包函数的形式,可以将视图类中的方法转换为可调用的函数,并将请求传递给这些方法进行处理。
  • return self.dispatch():

    • 在视图类的闭包函数中,会调用self.dispatch()方法。
    • 该方法实际上是APIView类中的一个方法,用于处理请求的分发过程。
  • APIView的dispatch():

    • dispatch()方法是APIView类中定义的主要方法,负责根据不同的HTTP请求方法调用对应的方法处理函数。
    • 它通过检查请求方法(GET、POST、PUT、DELETE等)来决定调用视图类中的哪个方法来处理请求。
  • 综上所述,当使用DRF编写API时,我们可以创建一个继承自APIView的自定义视图类。
  • 在路由中,调用视图类的.as_view()方法将其转换为可调用的函数。
  • APIView.as_view()方法会调用父类View.as_view()方法生成一个闭包函数,并去除了Django的CSRF保护。
  • 通过调用闭包函数,会实例化视图类,并调用dispatch()方法进行请求分发。
  • dispatch()方法根据不同的HTTP请求方法,调用视图类中对应的方法处理函数。

【四】Request类的源码分析

  • request.data:

    • request.data是一个属性,用于获取请求的数据。
    • 它是一个字典对象,包含了请求中的表单数据、JSON数据或其他非文件类型数据。
  • request.query_params

    • request.query_params也是一个属性,用于获取请求的查询参数。
    • 它是一个字典对象,包含了从URL中提取的查询参数键值对。
  • request.FILES

    • request.FILES是一个属性,用于获取请求中的文件数据。
    • 它是一个类似字典的对象,包含了上传的文件数据。
  • request.method

    • request.method是一个属性,用于获取HTTP请求的方法。
    • 例如,GET、POST、PUT、DELETE等。
  • request.path

    • request.path是一个属性,用于获取请求的路径部分。
    • 它是一个字符串,表示了请求的URL中的路径部分。
  • ...

  • 重写了魔法方法

    • 除了上述属性之外,Request类还重写了魔法方法__getattr__
      • 这个魔法方法在对象.属性访问时会自动触发,当访问的属性不存在时,会执行__getattr__方法中的逻辑。
    • 在Request类中,__getattr__方法通过反射技术实现了对self._request属性的访问。
      • self._request属性是一个低级的HttpRequest实例,它包含了更多原生的请求数据。
    • 通过重写了__getattr__方法,Request类可以在访问不存在的属性时,将请求委托给self._request对象,并从中获取对应的属性值。

【五】序列化组件

  • 序列化组件主要提供了两个功能:

    • 反序列化和序列化校验。
      • 反序列化指的是将传入的数据转换为Python对象的过程,通常用于处理用户提交的数据。
      • 序列化校验则是对反序列化后的数据进行验证,确保数据的完整性和准确性。
  • Serialzier

    • Serializer是序列化组件的基类,用于定义序列化类的结构和行为。
    • 在示例中,定义了一个名为BookSerializer的序列化类,通过继承Serializer类来创建自定义的序列化器。
  • 定义序列化类

    class BookSerializer(Serializer):
        name = serializers.CharField()
    
    • 在示例中,BookSerializer是一个简单的序列化类,仅包含一个名为name的字段。
    • 字段是序列化类的基本组成部分,用于定义要序列化和反序列化的数据字段。
  • 使用序列化类

    # 多个数据
    ser = BookSerializer(instance=queryset,many=True)
    
    # 单个数据
    ser = BookSerializer(instance=queryset)
    
    # 序列化后的数据
    ser.data # 字典
    
    • 通过实例化序列化类并传入instance参数来使用序列化类进行序列化。
    • 当需要序列化多个对象时,可以将many参数设置为True,并传入一个查询集。序列化后的数据可以通过访问ser.data属性来获取,该属性返回一个字典对象。
  • 序列化字段

    • 序列化字段定义了如何处理特定类型的数据。
    • 在示例中未详细说明具体的序列化字段类型
      • 但常用的一些字段类型包括:CharFieldIntegerFieldBooleanField等。
    • 此外,还有一些复杂的字段类型,如ListFieldDictField
      • 用于处理列表和字典类型的数据。
  • 字段参数

    • 字段参数用于对序列化字段进行配置和校验。
    • 其中
      • read_only参数指定字段只用于序列化而不可用于反序列化
      • write_only参数指定字段只用于反序列化而不可用于序列化。
  • 反序列化校验

    • 序列化组件提供了多种校验方式,包括字段自身的校验、局部钩子函数以及全局钩子函数。
    • 局部钩子函数使用validate_字段名的形式定义,用于对特定字段进行校验。
    • 全局钩子函数命名为validate,用于对整个数据进行校验。
  • 反序列化保存

    • 进行完反序列化校验后,可以通过调用ser.save()方法将数据保存到数据库
      • 判断instance是否存在,如果不存在,就是调用ser的create方法
    • 在序列化类中重写 create 方法
  • 反序列化更新

    • 进行完反序列化校验后,可以通过调用ser.save()方法将数据保存到数据库
      • 判断instance是否存在,如果存在,就是调用ser的update方法
    • 在序列化类中重写 update方法
  • ModelSerializer的使用

    • ModelSerializerSerializer的子类,用于简化与模型的序列化和反序列化。
      • 与普通序列化器相比,ModelSerializer不需要写明每个字段
      • 它将自动映射到关联的模型字段
      • 并提供了一些默认行为。
    • extra_kwargs携带其他参数
    • 其他跟Serializer一样
  • 序列化,定制返回格式

    • 通过source参数可以定制序列化字段的来源
      • 在源数据中使用不同的名称或路径来匹配特定字段。
      • SerializerMethodField
        • 可以在序列化类中定义一个名为get_字段名的方法
        • 根据业务逻辑生成特定的返回值。
    • 在表模型中写:
      • 写方法,返回字典,列表,字符串
      • 在序列化类中可以使用ListField,DictField
  • 总结来说,序列化组件在Web开发中扮演着重要的角色,能够帮助开发者处理数据的序列化和反序列化,并提供了灵活的配置和校验机制。
  • 开发者可以根据实际需求定义序列化类,使用不同的字段和参数,以及利用钩子函数进行数据校验和处理。
  • 同时,ModelSerializer进一步简化了与模型之间的序列化操作。

【六】请求与响应

  • Request类的请求
    • 接受前端传入的编码格式:json,urlencoded,form-data
      • 局部配置
      • 全局配置
    • Request的源码
  • Response类的响应
    • 前端看到的形式(浏览器,json)
    • 源码分析
      • data
      • headers
      • status_code
  • Request 类用于处理客户端发起的请求。
    • 编码格式(json、urlencoded、form-data),Request 类能够接收和解析前端传入的数据。
    • 这些数据可以通过局部配置或全局配置进行处理。
      • 局部配置:指的是在每个请求中针对特定的数据源进行配置,如指定请求头信息、编码格式等。
      • 全局配置:指的是在整个应用中设置默认的请求配置,以便在每个请求中自动使用这些配置。
    • Request 类的源码负责解析请求中的各个部分
      • 包括 URL、方法、请求体以及用户代理等等
      • 并提供了相应的属性和方法供开发者使用。
  • Response 类用于构建服务器端响应并发送给客户端。
    • 响应的形式可以是浏览器可直接显示的内容,也可以是序列化为 JSON 格式的数据。
    • 前端看到的形式:
      • 通常情况下,前端会在浏览器中看到 HTML 内容。
      • 此外,还可以返回 JSON 格式的数据,前端可以通过 JavaScript 进行解析和处理。
    • Response 类的源码包含几个关键部分
      • data:
        • 响应内容的主体。这通常是通过模板引擎或其他方式生成的 HTML 内容。
      • headers:
        • 响应头部的配置。
        • 可以设置 Content-Type、Cache-Control 等信息。
      • status_code:
        • 响应状态码,用于表示请求的处理结果。
        • 常见的状态码有 200(成功)、404(找不到资源)等。
    • Response 类提供了一系列方法来构建和发送响应,最终将响应发送给客户端。
      • 通过设置正确的状态码、响应头和返回内容,开发者可以自定义响应的形式和内容。

【七】视图组件

【1】两个视图基类

APIView

  • APIView 是 Django REST Framework 提供的一个基类,用于创建基于函数或基于类的视图。
  • 使用 APIView 可以根据需求自定义请求处理逻辑,对于简单的接口逻辑,可以直接继承 APIView 类。

GenericAPIView

  • GenericAPIViewAPIView 的一个子类,提供了一些常用的通用方法和属性,使开发者能够更方便地编写视图。
  • 通常与 Mixin 类一起使用,通过继承 GenericAPIView 和混入相应的功能类可以快速建立功能完善的视图。

【2】5 个视图扩展类

视图扩展类用于与 GenericAPIView 或其子类配合使用,提供常用的 CRUD(创建、读取、更新和删除)操作。

ListAPIView

  • 继承自 GenericAPIViewListMixin,实现获取多条记录的功能。

CreateAPIView

  • 继承自 GenericAPIViewCreateModelMixin,实现创建记录的功能。

DestroyAPIView

  • 继承自 GenericAPIViewDestroyModelMixin,实现删除记录的功能。

UpdateAPIView

  • 继承自 GenericAPIViewUpdateModelMixin,实现更新记录的功能。

RetrieveAPIView

  • 继承自 GenericAPIViewRetrieveModelMixin,实现获取单条记录的功能。

【3】9个视图子类

视图子类用于与 GenericAPIView 或其子类配合使用,提供特定的功能和集成多个操作。

CreateModelMixin

  • 该混入类提供 create() 方法
  • 用于根据传入的 POST 请求数据创建模型的新对象实例

ListModelMixin

  • 该混入类提供 list() 方法
  • 用于检索模型的对象列表,并将其序列化为响应数据

RetrieveModelMixin

  • 该混入类提供 retrieve() 方法
  • 根据提供的标识符或查找字段检索模型的单个对象实例

DestroyModelMixin

  • 该混入类提供 destroy() 方法
  • 根据提供的标识符或查找字段处理删除模型的单个对象实例

UpdateModelMixin

  • 该混入类提供 update() 方法
  • 根据提供的标识符或查找字段处理更新模型的单个对象实例
  • 上面5个混入类并不是独立的视图,而是用于与其他基于类的视图结合使用
  • 例如 APIViewGenericAPIViewViewSet
  • 通过继承这些混入类和适当的视图,开发人员可以轻松地实现所需的功能,避免编写重复的代码。

ListCreateAPIView

  • 继承自 GenericAPIViewListCreateModelMixin,实现获取多条记录和创建记录的功能。

RetrieveUpdateDestroyAPIView

  • 继承自 GenericAPIViewRetrieveModelMixinUpdateModelMixinDestroyModelMixin,实现获取单条记录、更新记录和删除记录的功能。

RetrieveDestroyAPIView

  • 继承自 GenericAPIViewRetrieveModelMixinDestroyModelMixin,实现获取单条记录和删除记录的功能。

RetrieveUpdateAPIView

  • 继承自 GenericAPIViewRetrieveModelMixinUpdateModelMixin,实现获取单条记录和更新记录的功能。

【4】视图集

  • 视图集是一种组织和管理视图的方式,它包括了多个接口,并允许使用相同的 URL 前缀来映射这些接口。
  • ModelViewSet

    • 继承自 GenericViewSetModelMixin,提供了常用的 CRUD 操作(创建、读取、更新和删除)。
    • 5个接口
  • ReadOnlyModelViewSet

    • 继承自 GenericViewSetListModelMixin,只提供读取操作(查询所有和查询单条)。
    • 2个接口
    • list和retrieve (查询所有和查询单条)
  • ViewSetMixin

    • 是一个"魔法"混入类,不能单独使用,必须与视图类一起使用,用于改变路由的写法。
  • ViewSet

    • 继承自 ViewSetMixinAPIView
    • 用于继承 APIView 并改变路由的写法
    • 在视图类中的方法可以任意命名。
  • GenericViewSet

    • 继承自 ViewSetMixinGenericAPIView
    • 用于继承 GenericAPIView 并改变路由的写法
    • 在视图类中的方法可以任意命名。

【5】图解各个视图类之间的关系

【1】总图

【2】视图类

【3】视图集

【6】补充和扩展

  • 5 个视图扩展类(配合 GenericAPIView 使用):

    • 这 5 个视图扩展类是根据公司规范封装的,并且必须配合 GenericAPIView 使用。

    • 它们的功能是返回格式符合公司规范的数据。

    • 示例:

    pythonfrom rest_framework import generics
    
    class SuccessView(generics.GenericAPIView):
        def get(self, request):
            data = {'code': 100, 'msg': '成功'}
            return self.success_response(data)
    
  • 9 个视图子类:

    • 5 个视图扩展类 + GenericAPIView
    • List 和 Create 的组合:
      • 在 GenericAPIView 的基础上
      • 结合 ListAPIView 和 CreateAPIView,实现同时支持列表展示和创建对象的功能。
    • 其他组合:
      • 根据具体需求,可以自由组合以上基类和其他扩展类来定制视图功能。
  • 视图集(ViewSets):

    • 视图集是将多个视图组合到一个类中,用于简化 URL 配置。以下是常见的视图集类别和特点:
    • ViewSetMixin:
      • 继承 ViewSetMixin 可以修改路由配置,默认使用动词方法名作为路由。
    • ViewSet:
      • 继承 ViewSetMixin 和 views.APIView,允许直接定义处理请求的方法。
    • GenericViewSet:
      • 继承 ViewSetMixin 和 generics.GenericAPIView,结合了通用视图和动态路由匹配。
    • ModelViewSet:
      • 继承 GenericViewSet 的基础上,提供了默认实现的增删改查功能。
    • 重写 perform_create:在每次创建记录时执行特定操作。
      • 序列化使用一个序列化类,反序列化使用配置的那个序列化类。
      • 自定义方法:通过使用 action 装饰器,可以自定义额外的方法,这些方法的查询对象和序列化类与配置的不一样。
  • 视图类:

    • 在视图类中,self 内部有 request 对象和 action 属性。

    • request 对象表示当前请求,在视图类的方法中可通过 self.request 引用。

    • action 是指当前调用的方法字符串名。

    • 示例:

    pythonfrom rest_framework.views import APIView
    
    class MyView(APIView):
        def get(self, request):
            data = {'message': 'Hello, World!'}
            return Response(data)
    

【八】认证,频率,权限

  • 认证:

    • 创建一个自定义的类来继承Django框架中的BaseAuthentication。
    • 重写authenticate方法来执行认证过程
    • 如果认证成功,你可以返回一个包含用户和认证信息的元组;
    • 如果认证失败,你可以选择抛出一个异常。
    • 可以在全局或局部配置文件中设置认证方式
  • 权限:

    • 创建一个与BaseAuthentication相似的类,继承自它来进行权限控制。
    • 重写其中的方法来定义你的权限逻辑
    • 如果用户拥有足够的权限,】允许他们访问某些资源;反之,抛出一个异常或返回错误信息。
  • 频率:

    • 创建一个自定义的类来继承SimpleRateThrottle。
    • 重写get_cache_key方法来确定在缓存中存储频率限制所用的键值。
      • 这个键值可以基于不同的因素,比如IP地址或用户ID。
      • 返回什么,就以什么做限制[ip,用户id]
    • 类属性:
      • 可以使用类属性scope来指定频率限制的范围。
      • scope
    • 对于频率限制,可以在全局或局部的配置文件中进行设置。
  • 频率类:

    • 可以创建一个自定义的类,并继承Django框架中的BaseThrottle。
    • 在这个类中,你需要重写allow_request方法来确定是否允许请求。
    • 如果请求的频率在设定的限制范围内,你可以返回True;否则,返回False。

【补充】自己写频率,同样的ip,一分钟只能访问3次---》频率类

自定义的逻辑

  • (1)取出访问者ip
  • (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
  • (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间
  • (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
  • (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
import time

class CustomThrottle:
    access_records = {}  # 存储访问记录的字典,格式为{ip: [timestamp1, timestamp2, ...]}

    def allow_request(self, request):
        ip = self.get_client_ip(request)
        
        if ip not in self.access_records:
            # 如果当前ip不在访问记录字典中,则将其添加并返回True(第一次访问)
            self.access_records[ip] = []
            return True

        # 保留60s内的访问记录,即删除超过60s的时间戳
        current_time = int(time.time())
        self.access_records[ip] = [timestamp for timestamp in self.access_records[ip] 
                                   if current_time - timestamp <= 60]

        if len(self.access_records[ip]) < 3:
            # 如果60s内的访问不足3次,则将当前时间戳插入到列表的第一个位置,返回True通过验证
            self.access_records[ip].insert(0, current_time)
            return True
        
        # 如果60s内的访问超过3次,则返回False验证失败
        return False

    def get_client_ip(self, request):
        """
        从请求中获取客户端IP地址
        """
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            ip = x_forwarded_for.split(',')[0]
        else:
            ip = request.META.get('REMOTE_ADDR')
        return ip
  • 上述代码是一个自定义的频率限制类CustomThrottle,其中使用access_records字典来存储每个访问者的访问记录。
    • allow_request方法用于判断是否允许当前请求进行访问。
  • 现在,我将为你展示一个简单的案例来使用这个自定义的频率限制类:
throttle = CustomThrottle()

# 模拟访问情况

request1 = {'REMOTE_ADDR': '192.168.0.1'}  # 第一次访问
print(throttle.allow_request(request1))  # True

request2 = {'REMOTE_ADDR': '192.168.0.1'}  # 第二次访问(仅相隔1秒)
time.sleep(1)
print(throttle.allow_request(request2))  # True

request3 = {'REMOTE_ADDR': '192.168.0.2'}  # 第一次访问
print(throttle.allow_request(request3))  # True

request4 = {'REMOTE_ADDR': '192.168.0.1'}  # 第三次访问(相隔不到60s)
print(throttle.allow_request(request4))  # False,访问超过频率限制

request5 = {'REMOTE_ADDR': '192.168.0.1'}  # 第四次访问(相隔60s)
time.sleep(60)
print(throttle.allow_request(request5))  # True,60s后访问通过验证
  • 在上述案例中,我们首先创建了CustomThrottle的实例throttle,并模拟了一些请求情况。
  • 根据这个频率限制逻辑,前三次访问都被允许通过,第四次访问由于超过频率限制而失败,但在等待60秒之后,第五次访问又通过了验证。

【九】排序

  • 我们可以使用内置的排序功能来对查询结果进行排序。

    • 为了实现排序功能,我们需要继承GenericAPIView
    • 并在视图类中配置filter_backends=[OrderingFilter]属性。
  • 在视图类中,配置filter_backends=[OrderingFilter],还需要配置属性:ordering_fields=[id,price]

from rest_framework.generics import GenericAPIView
from rest_framework.filters import OrderingFilter


class MyView(GenericAPIView):
    """
    示例视图类
    """
    queryset = MyModel.objects.all()  # 指定查询集
    serializer_class = MySerializer  # 指定序列化器
    filter_backends = [OrderingFilter]  # 配置排序过滤器
    ordering_fields = ['id', 'price']  # 配置允许排序的字段
 def get(self, request, *args, **kwargs):
        """
        GET请求处理逻辑
        """
        return self.list(request, *args, **kwargs)
  • 在上述代码中,我们定义了一个名为MyView的视图类,并指定了查询集queryset和序列化器serializer_class
  • 接下来,我们将filter_backends属性设置为[OrderingFilter],以启用排序功能。
  • 然后,通过ordering_fields属性指定了允许排序的字段,例如'id''price'
  • 如果是继承APIView写排序,自己写

  • 【示例】

  • 假设我们有一个模型Car表示汽车,具有idprice两个字段。

    • 我们将创建一个视图类来展示所有车辆并根据price字段进行排序。
  • 以下是一个基于上述内容的案例代码:

pythonfrom rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.generics import get_object_or_404
from rest_framework.filters import OrderingFilter
from .serializers import CarSerializer
from .models import Car

class CarListView(APIView):
    """
    汽车列表视图
    """
    filter_backends = [OrderingFilter]
    ordering_fields = ['price']

    def get(self, request):
        cars = Car.objects.all()
        serializer = CarSerializer(cars, many=True)
        return Response(serializer.data)
  • 在上述代码中,我们创建了一个名为CarListView的视图类,并继承了APIView
    • 然后,我们配置了filter_backends属性为[OrderingFilter]来启用排序功能,并指定了ordering_fields属性为['price'],以允许根据price字段进行排序。
  • get方法中,我们查询所有汽车信息,并使用序列化器将结果序列化后返回。
  • 这样,我们就可以通过发起GET请求,获取所有汽车列表并按照价格进行排序了。

【十】过滤

【1】简解

  • 在Django中,我们可以使用内置的SearchFilter来进行模糊查询。

  • 此外,还可以使用第三方库django-filter实现更强大的过滤功能。

  • 如果需要自定义过滤逻辑,我们可以自己编写一个类,并继承BaseFilterBackend,然后重写filter_queryset方法来实现过滤操作。

【2】步骤

  • 内置过滤器SearchFilter(模糊查询):

    • 首先,导入相关模块和类:
      from rest_framework.filters import SearchFilter
      
    • 在视图类中配置filter_backends=[SearchFilter],并设置search_fields属性为要进行模糊查询的字段列表。例如:
      filter_backends = [SearchFilter]
      search_fields = ['name', 'description']
      
  • 第三方过滤器库django-filter

    • 首先,安装django-filter库:
      pip install django-filter
      
    • 在视图类中配置filter_backends=[filters.DjangoFilterBackend],并设置filterset_class属性为自定义的过滤器类。
    • 编写自定义的过滤器类,并继承filters.FilterSet,在其中定义需要进行过滤的字段和过滤方式。
  • 自定义过滤器类:

    • 首先,导入相关模块和类:
      from rest_framework.filters import BaseFilterBackend
      
    • 编写自定义过滤器类,并继承BaseFilterBackend
    • 在自定义的过滤器类中重写filter_queryset方法,实现自定义的过滤逻辑,并返回过滤后的查询集对象。

【3】示例

  • 假设我们有一个模型Product表示商品,具有namedescription两个字段。
    • 我们将创建一个视图类来展示所有商品,并根据name字段进行模糊查询。
  • 以下是一个基于上述内容的案例代码:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.filters import SearchFilter
from .serializers import ProductSerializer
from .models import Product

class ProductListView(APIView):
    """
    商品列表视图
    """
    filter_backends = [SearchFilter]
    search_fields = ['name']  # 设置要进行模糊查询的字段

    def get(self, request):
        query = request.GET.get('query')  # 获取查询参数
        products = Product.objects.all()

        if query:
            products = products.filter(name__icontains=query)  # 使用模糊查询过滤商品

        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)
  • 在上述代码中,我们创建了一个名为ProductListView的视图类,并继承了APIView
    • 然后,我们配置了filter_backends属性为[SearchFilter],以启用模糊查询功能,并通过search_fields属性指定要进行模糊查询的字段,例如['name']
  • get方法中,我们获取查询参数query,然后查询所有商品信息并存储在products中。
    • 如果存在查询参数,我们将根据name字段使用icontains进行模糊查询过滤。
    • 最后,我们使用序列化器将过滤后的结果序列化并返回。

【十一】分页

  • 三种分页方式:

    • PageNumberPagination
      • 这是一种基于页码的分页方式。
      • 它将结果集按照每页显示的数量进行分页,并提供了相关的类属性来控制分页效果,如page_size(每页显示的数量)、max_page_size(最多显示的数量)等。
      • 在视图类中继承PageNumberPagination,并在类属性中配置相关参数来控制分页效果。
    • LimitOffsetPagination
      • 这是一种基于偏移量的分页方式。
      • 它通过指定偏移量和限制数量来获取结果集的片段,并提供了类属性来控制分页效果,如default_limit(默认每页显示的数量)、max_limit(最多显示的数量)等。
      • 在视图类中继承LimitOffsetPagination,并在类属性中配置相关参数来控制分页效果。
    • CursorPagination
      • 这是一种基于游标的分页方式。
      • 它使用特定的游标值作为参考点来获取结果集,并提供了类属性来控制分页效果,如cursor_query_param(表示当前游标值的查询参数)等。
      • 在视图类中继承CursorPagination,并在类属性中配置相关参数来控制分页效果。
  • 每个有些类属性控制:每页显示多少条,最多显示多少条,第几页。。。

  • 写个类,继承三个之一,配置在视图类上(必须继承GenericAPIView)

  • 继承APIView写分页

【示例】

  • 以下是一个基于上述内容的案例代码:
pythonfrom rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from .serializers import ProductSerializer
from .models import Product

class CustomPagination(PageNumberPagination):
    """
    自定义分页类
    """
    page_size = 10       # 每页显示的数量
    max_page_size = 100  # 最多显示的数量
    page_query_param = 'page'      # 表示页码的查询参数
    page_size_query_param = 'size' # 表示每页显示数量的查询参数

class ProductListView(APIView):
    """
    商品列表视图
    """
    pagination_class = CustomPagination

    def get(self, request):
        paginator = self.pagination_class()
        products = Product.objects.all()
        paginated_products = paginator.paginate_queryset(products, request)

        serializer = ProductSerializer(paginated_products, many=True)
        return paginator.get_paginated_response(serializer.data)
  • 在上述代码中,我们创建了一个名为CustomPagination的自定义分页类,并继承了PageNumberPagination

    • 然后,在自定义分页类中配置了page_size(每页显示的数量)、max_page_size(最多显示的数量)、page_query_param(表示页码的查询参数)和page_size_query_param(表示每页显示数量的查询参数)等相关参数。
  • 接下来,我们创建了一个名为ProductListView的视图类,并继承了APIView

    • 在该视图类中配置了pagination_class属性为我们自定义的分页类CustomPagination
  • get方法中,我们初始化了分页器(paginator),然后查询所有商品信息并存储在products中。

    • 接着,使用分页器的paginate_queryset方法对查询结果进行分页处理并获取分页后的商品列表(paginated_products)。
    • 最后,我们使用序列化器将分页后的结果序列化,并通过分页器的get_paginated_response方法返回分页的响应数据。
  • 这样,我们就可以根据需求选择不同的分页方式,并在视图类中配置相应的分页类来实现分页功能。

【十二】异常处理

  • 在Django REST Framework(DRF)中,可以通过全局异常处理来统一处理异常并返回一致的格式。

  • 创建自定义的异常处理函数:

    • 首先,我们需要创建一个函数来处理异常,并定义它的输入参数为exccontext,其中exc代表捕获的异常对象,context表示异常发生的上下文信息。
    • 在函数内部,调用DRF中默认的异常处理函数exception_handler,将exccontext作为参数传递给它。
  • 处理异常并返回响应:

    • 根据实际需求,在自定义的异常处理函数中对异常进行处理。
    • 如果成功处理了异常且无需返回特定的响应内容,则返回None
    • 如果需要返回自定义的响应内容,可以通过创建一个Response对象,并设置相应的状态码、错误信息等。
  • 配置全局异常处理函数:

    • 在DRF的配置文件中,配置全局异常处理函数,以便在出现异常时能够调用该函数进行处理。
    • 可以通过设置EXCEPTION_HANDLER参数,并指定为自定义的异常处理函数。

【示例】

以下是一个基于上述内容的案例代码:

pythonfrom rest_framework.views import exception_handler
from rest_framework.response import Response

def custom_exception_handler(exc, context):
    """
    自定义全局异常处理函数
    """

    # 调用DRF默认异常处理函数,获取默认的响应内容
    response = exception_handler(exc, context)

    if response is None:
        # 如果异常未被处理且无需返回特定响应内容,则返回None
        return None

    # 自定义响应内容,可以根据实际需求进行组织
    custom_response = {
        'error_code': response.status_code,
        'message': 'An error occurred while processing the request.',
        'detail': str(exc),
    }

    # 创建自定义的响应对象
    response.data = custom_response

    return response
  • 在上述代码中,我们创建了一个名为custom_exception_handler的自定义全局异常处理函数。
    • 在函数内部,我们首先调用DRF默认的异常处理函数exception_handler,将异常和上下文信息作为参数传递给它,并获取默认的响应内容。
  • 接着,我们对异常进行处理。如果成功处理了异常且无需返回特定的响应内容,我们直接返回None
    • 如果需要返回自定义的响应内容,我们可以根据实际需求进行组织,并创建一个Response对象,将自定义的响应内容赋值给response.data
  • 最后,我们将自定义的异常处理函数配置在DRF的配置文件中。
    • 可以通过设置EXCEPTION_HANDLER参数,并指定为自定义的异常处理函数,以便在出现异常时能够调用该函数进行处理。

【十三】接口文档编写

  • 接口文档编写是一个重要的任务,而自动生成工具能够简化这个过程并提高效率。以下是对自动生成接口文档的概念进行详解:

  • 使用CoRAPI(Code-Reading Assistant for API)等自动生成工具,可以根据代码的注释和结构生成接口文档。

    • 这些工具通常会扫描项目中的代码,并解析注释中包含的特定标记和信息,以生成接口文档的相关内容。
    • 通过使用这些工具,我们可以减少手动编写文档的工作量,并确保文档与实际代码一致性强。
  • 下面是一个基于CoRAPI的自动生成接口文档的案例:

    from django.shortcuts import render
    from django.http import JsonResponse
    
    def hello(request):
        """
        这个接口用于返回欢迎消息
        """
    
        message = "Hello, welcome to our API!"
    
        return JsonResponse({"message": message})
    
  • 在上述代码中,我们定义了一个hello函数作为一个API接口,用于返回一个欢迎消息。函数内部的注释提供了对接口的描述。

  • 使用CoRAPI等自动生成工具,在项目中执行相应的命令或配置,可以生成接口文档。根据我们的案例代码,生成的接口文档可能如下所示:

接口名称

  • /hello

描述

  • 这个接口用于返回欢迎消息。

请求方法

  • GET

请求参数

该接口不需要请求参数。

响应

  • 状态码:200 OK
  • 返回类型:JSON

JSON示例

{
  "message": "Hello, welcome to our API!"
}
  • 在实际项目中,我们需要根据项目的具体情况和要求来配置和使用自动生成工具。
    • 通常情况下,我们需要在代码中添加规范的注释,并运行相应的命令或配置工具以生成接口文档。

【十四】JWT认证

  • JWT(JSON Web Token)是一种用于身份认证和授权的开放标准。

    • 它由三部分组成,即Header、Payload和Signature,通常以Base64编码的形式传输。
  • JWT是什么

    • JWT是一种基于JSON的安全令牌,用于在客户端和服务器之间传输信息。
    • JWT可以通过数字签名保证传输的信息不被篡改,并且可以使用密钥进行验证和解码。
  • 三段式

    • Header:包含算法类型和令牌类型等信息。
    • Payload:包含自定义的用户信息或声明等。
    • Signature:根据Header和Payload以及密钥生成的签名,用于验证令牌的完整性。
  • 开发重点:签发,认证

    • 签发:在用户认证成功后,将用户信息和其他需要的声明信息生成JWT并返回给客户端。
    • 认证:客户端将JWT发送给服务器,在服务器验证JWT的合法性并解析出其中的用户信息进行认证和授权。
  • 使用djangorestframework-jwt实现快速签发和认证:

    • 基于auth的user表快速签发:
      • djangorestframework-jwt提供了ObtainJSONWebToken视图,可以基于Django的内置User模型直接进行用户认证并签发JWT。
    • 基于内置的认证类认证,配合权限:
      • djangorestframework-jwt还可以与Django的内置认证类和权限系统结合使用,进行JWT的验证和用户权限的控制。
  • 登录成功返回格式

    • 自定义用户表 -> 签发 -> 登录接口:

      • 在自定义用户表中,可以添加额外的字段用于存储用户信息。

      • 在用户登录成功后,根据用户信息生成JWT,并将其加入到响应中返回给客户端。

      • 响应的格式可以自定义,一般包含JWT信息和其他用户相关信息。

        from rest_framework_jwt.settings import api_settings
        
        def generate_jwt(user):
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
        
            return token
        
        # Login function
        def login(request):
            # 用户验证逻辑
            # ...
        
            user = User.objects.get(username=username)
            token = generate_jwt(user)
        
            response_data = {
                'token': token,
                'username': user.username,
                'email': user.email,
                # 其他用户相关信息
            }
        
            return JsonResponse(response_data)
        
    • 自定义用户表 -> 认证 -> 认证类:

      • 在自定义用户表中,可以添加额外的字段用于存储用户信息。

      • 使用自定义的认证类进行用户认证,验证通过后生成JWT。

      • 认证类可以与Django的认证机制结合使用,例如配合使用AuthenticationBackend类来实现多方式登录(例如用户名密码登录、手机号码验证码登录等)。

        from django.contrib.auth.backends import BaseBackend
        from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
        
        class CustomAuthenticationBackend(BaseBackend):
            def authenticate(self, request, username=None, password=None):
                # 实现自定义的认证逻辑
                # ...
        
                user = User.objects.get(username=username)
                return user
        
        # settings.py
        AUTHENTICATION_BACKENDS = [
            'my_app.backends.CustomAuthenticationBackend',
        ]
        
        JWT_AUTH = {
            'JWT_SECRET_KEY': SECRET_KEY,
            'JWT_AUTH_HEADER_PREFIX': 'Bearer',
        }
        
        # Login function
        def login(request):
            # 用户验证逻辑
            # ...
        
            user = CustomAuthenticationBackend().authenticate(request, username=username, password=password)
            if user:
                token = generate_jwt(user)
        
                response_data = {
                    'token': token,
                    'username': user.username,
                    'email': user.email,
                    # 其他用户相关信息
                }
        
                return JsonResponse(response_data)
        
  • 扩写了auth的user表

    • 快速签发---》只能使用用户名密码,不能多方式登录
    • 自定义登录接口---》实现多方式登录

【示例】

  • 以下是一个简单的示例代码,展示了如何使用djangorestframework-jwt进行用户登录和JWT认证:
python# 安装djangorestframework-jwt: pip install djangorestframework-jwt

# serializers.py
from rest_framework import serializers

class UserSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings

class LoginView(APIView):
    def post(self, request):
        serializer = UserSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # 在此处进行自定义用户表的验证逻辑
        # ...

        # 在验证通过后签发JWT
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

        payload = jwt_payload_handler(serializer.validated_data)
        token = jwt_encode_handler(payload)

        return Response({"token": token})

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ],
}

JWT_AUTH = {
    'JWT_SECRET_KEY': SECRET_KEY,
    'JWT_AUTH_HEADER_PREFIX': 'Bearer',
}
  • 在上述代码中,通过自定义的UserSerializer对用户输入进行校验,并在LoginView中进行用户的认证和JWT签发。
    • settings.py中配置了使用JWT_AUTH来处理JWT的认证。
  • 登录成功后,将返回一个包含JWT的响应,例如:{"token": "xxxxxxxxxxx"}

【十五】自动注册路由的两种方式

第一步:导入一个路由类

from rest_framework.routers import SimpleRouter
  • 两个路由类
    • SimpleRouter
    • DefaultRouter

第二步:实例化得到对象

router = SimpleRouter()

第三步:注册路由(视图类)

router.register("books", BookView, "books")

# router.register("自定义前缀", 需要注册视图类, "别名")

第四步:加入到路由中

方式一

  • 将路由对象的URL配置添加到现有的urlpatterns中。
  • 使用urlpatterns += router.urls将路由对象的URL配置列表添加到现有的URL配置中。
from django.urls import path

urlpatterns = [
    ...
]
urlpatterns += router.urls
print("urlpatterns:>>>", urlpatterns)
# urlpatterns:>>> [<URLResolver <URLPattern list> (admin:admin) 'admin/'>, <URLPattern 'book/'>]
print("router:>>>", router)
# router:>>> <rest_framework.routers.SimpleRouter object at 0x000001EB24D63130>

方式二

  • 使用include()函数创建URL配置
  • 并将路由对象作为参数传递给include()函数。
  • 例如,使用path("api/v1/", include(router.urls))将路由对象的URL配置嵌套在前缀为"api/v1/"的URL路径下。
from django.urls import path, include

urlpatterns = [
    path("api/v1/", include(router.urls))
]

【补充】序列化类源码分析之many参数的作用

  • 如果传了many=True,序列化多条

    • 得到的对象不是BookSerializer对象
    • 而是ListSerialzier的对象中套了一个个的BookSerializer对象
  • 如果传了many=False,序列化单条

    • 得到的对象就是BookSerializer的对象

【补充】类实例化得到对象,是谁控制的

def __new__(cls, *args, **kwargs):
    if kwargs.pop('many', False):
        return cls.many_init(*args, **kwargs)
    return super().__new__(cls, *args, **kwargs)

在类实例化会触发,它比__init__

  • __new__造出裸体的人
  • __init__给裸体的人穿衣服

参考博客:【3.0】基础串联之魔法方法 - Chimengmeng - 博客园 (cnblogs.com)