• Django REST Framework(DRF)_第三篇


     

    DRF版本控制

    • 介绍

    我们在看APIView源码时可以看到,版本和版本控制类是通过determine_version的返回值获取的

    version, scheme = self.determine_version(request, *args, **kwargs)

    并且将版本和版本控制类放入了request中,request.versioning_scheme就是控制类的实例化对象

    request.version, request.versioning_scheme = version, scheme

    所以视图中获取版本就需要用request.version

    # 下面是rest_framework.versioning里所有的版本控制方法
    
    # 最基础的版本控制类, 其他类继承并重写determine_version方法
    class BaseVersioning(object):pass
    
    # 在accept请求头中配置版本信息,Accept: application/json; version=1.0
    class AcceptHeaderVersioning(BaseVersioning):pass
    
    # 在url上携带版本信息,url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
    class URLPathVersioning(BaseVersioning):pass
    
    # 把版本信息放在路由分发中, 把namespaces配置成版本,url(r'^v1/', include('users.urls', namespace='v1')
    class NamespaceVersioning(BaseVersioning):pass
    
    # 在host上配置版本信息, Host: v1.example.com
    class HostNameVersioning(BaseVersioning):pass
    
    # 在url过滤条件上配置版本信息,GET /something/?version=0.1 HTTP/1.1
    class QueryParameterVersioning(BaseVersioning):pass
    • 使用

    以上面通过url携带版本信息为例进行版本控制使用,

    # 在settings下进行版本信息配置
    REST_FRAMEWORK = {
        # 默认使用的版本控制类,这里使用url携带版本信息
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
        # 允许的版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],
        # 版本使用的参数名称, 版本的参数名与urls中 ?P<version>中名称一致
        'VERSION_PARAM': 'version',
        # 默认使用的版本
        'DEFAULT_VERSION': 'v1',
    }
    
    # 在urls中配置如下
    urlpatterns = [
        re_path(r'^(?P<version>[v1|v2]+)/demo', VersionView.as_view()),
    ]
    
    # 视图类中使用如下
    class VersionView(APIView):
        def get(self, request, *args, **kwargs):
            # request.version获取版本
            versions = request.version
            if versions == "v1":
                return Response("版本1的信息")
            elif versions == "v2":
                return Response("版本2的信息")
            else:
                return Response("无此版本")

     

    DRF认证

    • 说明

    在写认证前我们需要去看下源码,看下DRF是怎么做的,在initial方法里面有self.perform_authentication(request),这个就是认证,再深入查找源码,发现下面这句话,

    for authenticator in self.authenticators:

      ser_auth_tuple = authenticator.authenticate(self)

    self.authenticators是一个认证类的实例化列表,这里是循环调用每一个实例化中的authenticate方法,并传入了self,这里的self指的就是request,因此我们如果要使用自定义认证就必须重新authenticate的方法.

    • 使用

    准备基本工作:

    # 在models下创建一张用户表,登录成功后可以保存token
    # 在models下创建一张用户表,登录成功后可以保存token
    class User(models.Model):
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=32)
        token = models.UUIDField(null=True, blank=True)
        
    # 在视图中编写登录,登录成功则插入token
    class LoginView(APIView):
        def post(self, request):
            user = request.data.get('user')
            pwd = request.data.get('pwd')
            # 检验用户名和密码是否正确
            user_obj = User.objects.filter(username=user, password=pwd).first()
            if user_obj:
                # 生成uuid并赋值给token,保存至数据库
                user_obj.token = uuid.uuid4()
                user_obj.save()
                return Response(user_obj.token)
            return Response("用户名或密码错误")

    准备工作完成后,我们就可以编写自定义认证类来进行认证

    单独新建一个auth.py文件

    from rest_framework.exceptions import AuthenticationFailed
    from rest_framework.authentication import BaseAuthentication
    
    from .models import User
    
    
    # 必须继承BaseAuthentication并重写authenticate方法
    class MyAuth(BaseAuthentication):
        def authenticate(self, request):
            # 假设前端是通过请求参数传递过来的
            token = request.query_params.get("token", "")
            if not token:
                # 认证失败就抛出该异常
                raise AuthenticationFailed({"code":1001, "error_msg": "缺少token"})
            user_obj = User.objects.filter(token=token).first()
            if not user_obj:
                raise AuthenticationFailed({"code": 1001, "error_msg": "无效的token"})
            # 成功则返回一个元组,从源码中user_auth_tuple = authenticator.authenticate(self)
            # 然后self.user, self.auth = user_auth_tuple,这两句可以知道返回元组并赋值
            return (user_obj, token)

    然后写一个index页面,进行访问,访问前需要认证,下面属于局部视图认证

    class IndexView(APIView):
        # 如果没有配置全局认证,可以单独指定首页需要经过自定义的认证
        authentication_classes = [MyAuth, ]
        def get(self, request):
            return Response("首页")

    当然如果需要所有的都要通过自定义的认证,可以在settings下面进行配置,下面属于全局注册:

    REST_FRAMEWORK = {
        "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
        'DEFAULT_VERSION': "v1",
        'ALLOWED_VERSIONS': ["v1", "v2"],
        'VERSION_PARAM': 'version',
        # 配置全局认证
        'DEFAULT_AUTHENTICATION_CLASSES': ["authTest.auth.MyAuth", ]
    }

     

    DRF权限

    • 说明

    还是老步骤,在写之前我们需要去看下源码,看下DRF是怎么做的,在initial方法里面有self.check_permissions(request),这个就是权限,再深入查找源码,发现下面这句话,

    for permission in self.get_permissions():

      if not permission.has_permission(request, self):

        self.permission_denied(

          request, message=getattr(permission, 'message', None)

        )

    self.get_permissions返回的是一个权限实例化列表,这里是循环调用每一个实例化中的has_permission方法,并传入了request和self,这里的self指的是我们视图对象.self.permission_denied则是抛出异常,错误信息则是message

    • 使用

    准备基本工作,在认证表的基础上,加一个权限字段:

    role = models.IntegerField(choices=((1, '普通用户'),(2, 'vip用户'), (3, '管理员')), default=1)

    新建一个permissions.py文件

    from rest_framework.permissions import BasePermission
    
    
    class MyPermission(BasePermission):
        message = "无此权限访问"
    
        def has_permission(self, request, view):
            """自定义该权限只有管理员才有
            注意我们初始化时候的顺序是认证在权限前面的,所以只要认证通过~
            我们这里就可以通过request.user,拿到我们用户对象
            """
            if request.auth and request.user.role == 3:
                return True
            else:
                return False
    class PermissionView(APIView):
        # 经过自定义认证
        authentication_classes = [MyAuth, ]
        # 经过自定义权限
        permission_classes = [MyPermission]
    
        def get(self, request):
            return Response("管理员才能看到")

    上面是局部组件配置,settings中可以全局配置

    REST_FRAMEWORK = {
        "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
        'DEFAULT_VERSION': "v1",
        'ALLOWED_VERSIONS': ["v1", "v2"],
        'VERSION_PARAM': 'version',
        # 配置全局认证
        # 'DEFAULT_AUTHENTICATION_CLASSES': ["authTest.auth.MyAuth", ],
        # 配置全局权限
        # "DEFAULT_PERMISSION_CLASSES": ["authTest.permissions.MyPermission"]
    }

     

    DRF频率

    • 说明

    依旧先去看源码,实际上版本,认证,权限,频率都在initial方法里面,self.check_throttles这个就是频率组件,

    for throttle in self.get_throttles():

        if not throttle.allow_request(request, self):

          throttle_durations.append(throttle.wait())

    看了那么多遍,一看就知道self.get_throttles()肯定返回的是频率类实例化的一个列表,然后调用allow_request方法,也就是说我们要是自定义的话也必须要写该方法,不然肯定报错,这里还有个throttle.wait(),所以还得写一个无参的wait方法.

    • 自定义频率

      新建throttles.py文件,编写MyThrottle类

      from rest_framework.throttling import BaseThrottle
      
      import time
      
      VISIT_USER = {}
      # 一分钟限制访问5次
      class MyThrottle(BaseThrottle):
          """自定义频率类"""
      
          def __init__(self):
              self.history = None
      
          def allow_request(self, request, view):
              """必须重写该方法
              实现思路:
                  1. 通过ip进行限制,所以先获取ip
                  2. 以ip为key,讲每次访问的时间计入到value中,value为列表
                  3. 访问时间列表最小和最大值相差1分钟以上的进行循环剔除,只留下有效的时间
                  4. 判断访问次数是否超过了5次,也就是时间列表长度是否大于5
              """
              # 1.先获取访问用户的ip地址
              remote_addr = request.META.get('REMOTE_ADDR')
              # 2.判断ip是否在字典中,也就是判断用户是否第一次访问
              now = time.time()   # 获取当前时间戳
              if remote_addr not in VISIT_USER:
                  VISIT_USER[remote_addr] = [now]
                  # 从源码中可以知道,返回真则是不限制,因为才第一次所以不限制
                  return True
              # 3.走下面逻辑证明用户不是第一次访问,所以先取出当前访问的ip时间列表
              history = VISIT_USER[remote_addr]
              # 4.插入用户最新访问的时间
              history.insert(0, now)
              # 5.循环时间列表,剔除最新最老时间差大于一分钟的记录
              while history[0] - history[-1] > 60:
                  history.pop()
              # 保存最新的列表数据到self.history,因为wait方法中需要
              self.history = history
              # 6.判断时间列表长度是否大于最大允许的次数
              if len(history) > 5:
                  return False
              else:
                  return True
      
          def wait(self):
              """返回需要多久时间才能再次访问"""
              rest_time = 60 - (self.history[0] - self.history[-1])
              return rest_time

      在视图中可以进行配置throttle_classes = [MyThrottle]:

      class PermissionView(APIView):
          # 经过自定义认证
          authentication_classes = [MyAuth, ]
          # 经过自定义权限
          permission_classes = [MyPermission]
          # 自定义频率访问次数
          throttle_classes = [MyThrottle]
      
          def get(self, request):
              return Response("管理员才能看到")
    • 使用DRF自带的频率组件

    使用之前,必须先看下源码,看下需要怎么使用,from rest_framework import throttling,进入throttling查看SimpleRateThrottle,首先代码肯定先走allow_request,首先有self.rate,而这个又是self.get_rate()的返回值,该方法又要去拿scope属性的值,没有则抛异常,所以我们需要定义scope,从下面两行代码我们看下:

    return self.THROTTLE_RATES[self.scope] THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES

    从上面可以看到scope的值是DEFAULT_THROTTLE_RATES字典中的key,目的就是要取出value值,再来进一步看,该值返回给的是self.rate,而在初始化的时候源码如下:

    self.num_requests, self.duration = self.parse_rate(self.rate)

    我们再来看下parse_rate方法干了啥,因为接收了DEFAULT_THROTTLE_RATES的value值,我们得去看下该值怎么配置才行,从源码中得知,我们需要 以 次数/单位时间的方式配置,所以我们可以配置5/m,也就是每分钟五次,在settings里面配置如下:,这里把所有配置都写上去了

    REST_FRAMEWORK = {
        # 版本控制
        "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
        'DEFAULT_VERSION': "v1",
        'ALLOWED_VERSIONS': ["v1", "v2"],
        'VERSION_PARAM': 'version',
        # 配置全局认证
        'DEFAULT_AUTHENTICATION_CLASSES': ["authTest.auth.MyAuth", ],
        # 配置全局权限
        "DEFAULT_PERMISSION_CLASSES": ["authTest.permissions.MyPermission"],
        # 频率限制的配置
        "DEFAULT_THROTTLE_CLASSES": ["authTest.throttles.MyThrottle"],
        "DEFAULT_THROTTLE_RATES":{
                'Num':'5/m',         #速率配置每分钟不能超过5次访问,Num是scope定义的值,
            }
    }

    虽然做了配置,但是我们继承SimpleRateThrottle也没有写方法啊,那它怎么知道我们用什么做限制呢?所以继续看源码,我们看到要执行get_cache_key方法,而get_cache_key需要被重新,不然就报错,所以我们需要重写,那我们要写啥呢?结合我们自定义的实现和源码中可以看出get_cache_key方法的返回值应该就是用户访问的ip地址,而且我们从BaseThrottle的get_ident方法可以知道,该方法就是返回ip地址的.所以我们就可以直接写了,见下图:

    class DRFThrottle(SimpleRateThrottle):
        scope = "Num"        # 定义key值,从settings中通过该key取值
    
        def get_cache_key(self, request, view):
            return self.get_ident(request)

  • 相关阅读:
    @support浏览器兼容判断 以及 @media媒体查询
    关于BFC的总结
    JS—二维数组的创建
    JS—操作符优先级
    JS—事件对象
    JS—事件
    DOM—addEventListener() & removeEventListener()
    高级算法——动态规划(斐波那契函数实例)
    对象字面量的使用
    小程序日历 IOS真机预览问题
  • 原文地址:https://www.cnblogs.com/leixiaobai/p/11244713.html
Copyright © 2020-2023  润新知