Welcome to kimi's blog

drf之认证组件、权限组件、频率组件、过滤、排序、分页

认证组件

访问某个接口,需要登录后才能访问

编写模型表

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    user = models.OneToOneField(to='UserToken', on_delete=models.CASCADE, null=True)


class UserToken(models.Model):  # 跟User是一对一
    token = models.CharField(max_length=32)
    # user :反向,表名小写,所有有user字段

登录接口

路由

router.register('user', views.UserView, 'user')  # /api/v1/user/login     post 

视图类

from .models import User, UserToken
import uuid

#  登录接口  自动生成路由+由于登录功能,不用序列化,继承ViewSet
class UserView(ViewSet):
    @action(methods=['POST'],detail=False)
    def login(self, request):
        print(request.data)
        username = request.data.get('username')
        password = request.data.get('password')
        user = User.objects.filter(username=username, password=password).first()
        print(user)
        if user:

            # 用户存在,登录成功
            # 生成一个随机字符串---uuid
            token = str(uuid.uuid4())  # 生成一个永不重复的随机字符串
            """
            # 在userToken表中存储一下,
            1.从未登录过>>插入一条数据   新增
            2.登录过>>修改数据    可用if判断
            根据kwargs,传入的东西查找,能找到就使用defaults的更新,否则就新增一条          
            """
            UserToken.objects.update_or_create(user=user, defaults={'token': token})
            return Response({'code': '100', 'msg': '登录成功', 'token': token})
        else:
            return Response({'code': '101', 'msg': '用户名或密码错误'})

认证功能

五个视图子类如何自动生成路由

ViewSetMixin+List+update+GenericAPIView 可以写到一个视图类中

自动生成路由+5个视图扩展类+GenericAPIView------>一个视图类

登录认证限制

登录认证的限制:有些接口需要登录之后才能访问,开发的接口不需要登录就能访问。

需求

编写一个登录接口,返回token,只要带着token过来,就是登录了,不带就是没有登录。

1.查询所有不需要登录就能访问

2.查询单个,需要登录才能访问

使用步骤

  1. 写一个认证类,继承BaseAuthentication

  2. 重写authenticate方法,在该方法中实现登录认证,

    思考token在哪里携带的,如何认证是否登录过?

  3. 如果认证成功,返回两个值【返回None或两个值】

  4. 认证不通过,抛异常AuthenticationFailed

  5. 局部使用和全局使用

    # 局部使用:只在某个视图类中使用【当前视图类管理的所有接口】
        class BookDetailView(ViewSetMixin,RetrieveAPIView):
            authentication_classes = [LoginAuth]
    
    
    # 全局使用:全局所有接口都生效(登录接口可以局部禁用)
        REST_FRAMEWORK = {
            'DEFAULT_AUTHENTICATION_CLASSES':
            ['app01.authenticate.LoginAuth']
        }
    
    # 局部禁用
        class BookDetailView(ViewSetMixin,RetrieveAPIView):
            authentication_classes =[]
    

代码实现

路由

# 自动生成路由
router.register('books',views.BookView,'books')
router.register('books',views.BookDetailView,'books')

认证类代码

# 认证类代码
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken

class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
    """
     def authenticate(self, request):
        '''Authenticate the request and return a two-tuple of (user, token)'''
            raise NotImplementedError(".authenticate() must be overridden.")
        """
        # 在这里实现认证,如果是登录的,最后返回两个值,报错直接抛异常
        # 请求中是否携带token,判断是否登录,放在地址栏中
        token = request.query_params.get('token')
        if token:  # 前端传入token了,去表中查,如果能查到,登录了,返回两个值[固定的:当前登录用户,token]
            user_token = UserToken.objects.filter(token=token).first()
            if user_token:
                return user_token.user,token
            else:
                # 没有登录抛异常
                raise AuthenticationFailed('token认证失败')
        else:
            raise AuthenticationFailed('token没传')

视图

# 查询所有
class BookView(ViewSetMixin,ListAPIView):  # 不需要认证
    queryset = Book.objects.all()
    serializer_class = BookSerializer


class BookDetailView(ViewSetMixin,RetrieveAPIView):  # 需要认证才能访问
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # authentication_classes=[LoginAuth]  # 需要写一个认证类
    

注意,不要再配置文件乱导入不使用的东西,否则没有加载过就使用会直接报错。

权限组件

用户登录成功了,有些接口还是不能访问,因为没有权限去访问。
三种权限:

        -ACL:访问控制列表
        -rbac:公司内部系统,基于角色的访问控制
        -abac:rbac升级版,加了属性认证

需求

  1. 查询单个和查询所有,都需要登录后才能访问---->使用全局认证

​ 查询单个需要超级管理员才能访问,查询所有是所有登录用户都能访问的,权限是一个字段,在user表中,加入user_type字段

使用权限的步骤

  1. 写一个权限类,继承BasePermission

  2. 重写has_permission方法,在该方法中实现权限认证,在这方法中,request.user就是当前登录用户

  3. 如果有权限,返回True

  4. 没有权限,返回False,定制返回的中文:self.message='中文'

  5. 局部使用和全局使用

    # 局部使用:只在某个视图类中使用【当前视图类管理的所有接口】
        class BookDetailView(ViewSetMixin,RetrieveAPIView):
            permission_classes = [CommonPermission]
    
    # 全局使用:全局所有接口都生效(登录接口可以局部禁用)
        REST_FRAMEWORK = {
            'DEFAULT_PERMISSION_CLASSES':
            ['app01.permissions.CommonPermission']
        }
    
    # 局部禁用
        class BookDetailView(ViewSetMixin,RetrieveAPIView):
            permission_classes =[]
    

代码展示

权限类代码

from rest_framework.permissions import BasePermission
class CommonPermission(BasePermission):
    def has_permission(self,request,view):
        """def has_permission(self, request, view):
        '''
        Return `True` if permission is granted, `False` otherwise.
        '''
        这个函数返回值是TRUE或是False
        """
        # 实现权限的控制---》知道当前登录用户是谁,当前登录用户request.user
        if request.user.user_type==1:
            return True
        else:
            # 没有权限,向对象放一个属性message
            # 如果表模型中,使用了choice,就可以通过,get_字段名_display()
            self.message = '您是【%s】,您没有权限' % request.user.get_user_type_display()
            return False

视图

from app01.permission import CommonPermission
# 查询所有
class BookView(ViewSetMixin,ListAPIView):  
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    authentication_classes = []  # 局部禁止

class BookDetailView(ViewSetMixin,RetrieveAPIView):  
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = [CommonPermission]  # 需要编写一个权限

频率组件

控制某个接口访问频率(次数)

需求

  1. 查询所有接口,同一个ip一分钟只能访问5次

使用频率组件的步骤

  1. 写一个频率类,继承SimpleRateThrottle

  2. 重写get_cache_key方法,返回什么就以什么做限制--用户id做限制

  3. 配置一个类属性:scope='book_5_m'

  4. 在配置文件配置

    'DEFAULT_THROTTLE_RATES':{
        'book_5_m':'5/m'
    }  # h-小时  m-分钟  s-秒
    
  5. 局部使用和全局使用

    # 局部使用:只在某个视图类中使用【当前视图类管理的所有接口】
        class BookDetailView(ViewSetMixin,RetrieveAPIView):
            throttle_classes = [Commonthrottle]
    
    # 全局使用:全局所有接口都生效(登录接口可以局部禁用)
        REST_FRAMEWORK = {
            'DEFAULT_PERMISSION_CLASSES':
            ['app01.throttling.CommonThrottle']
        }
        
        REST_FRAMEWORK = {
        # Throttling
            'DEFAULT_THROTTLE_RATES': {
            'kiki': '5/m',  # 一分钟只能访问5次
        },
        'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.CommonThrottle'],
    }
    
    # 局部禁用
        class BookDetailView(ViewSetMixin,RetrieveAPIView):
            permission_classes =[]
    

代码展示

# 频率类,不继承BaseThrottle,继承SimpleRateThrottle
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle

class CommonThrottle(SimpleRateThrottle):
    # 类属性,属性值随便写
    # 配置文件中配置
    scope = 'kiki'  # kiki和配置的uer要一致

    def get_cache_key(self, request, view):
        # 返回什么,就以什么做频率限制【可以返回ip  或用户ID】
        # 客户端的ip地址从哪里拿?
        return request.META.get('REMOTE_ADDR')  # 以ip做限制
        # return request.user.pk  # 以用户id做限制

过滤排序

restful规范中,要求请求地址中带过滤条件,五个接口中,只有查询所有接口需要过滤和排序。

需求

1.查询所有图书接口,查以 ’ 三 ‘ 开头的所有图书

三种过滤器

继承APIView 自己补齐

class BookView(APIView):
    def get(self,request):
        search=request.query_params.get('search')
        books=Book.objects.filter()

内置过滤器的使用(继承GenericAPIView)

视图类

 # 过滤,必须继承GenericAPIView及其子类,才能使用这种方法---》配置过滤类的方式
from rest_framework.filters import SearchFilter
class BookView(ViewSetMixin,ListAPIView):
	queryset = Book.objects.all()
	serializer_class = BookSerializer
    """ 前面配置了权限,认证,频率,这里需要取消掉"""
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
	# SerchFilter内置的,固定用法,模糊匹配
	# 就有了过滤功能了,指定哪个字段过滤
    # search_fields = ['name']  # 可以按名字模糊匹配
	filter_fields =['name','price']  # 可以按名字模糊匹配或价格模糊匹配
	
    
    # 可以使用的搜索方法
    1.http://127.0.0.1:8000/api/v1/books/?name=红  # name只要有红就会搜出来
	2.http://127.0.0.1:8000/api/v1/books/?search=红  # name或price中只要有红就会搜出来

使用第三方django-filter实现过滤

安装:django-filter

pip3.8 install django-filter -i https://pypi.tuna.tsinghua.edu.cn/simple/

视图类

# 使用第三方djagno_filter过滤器
from django_filters.rest_framework import DjangoFilterBackend
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    # 第三方过滤器
    filter_backends = [DjangoFilterBackend]
    # 就有了过滤功能了,指定哪个字段过滤
    # filterset_fields = ['price']
    filterset_fields =['name','price']  # 支持完整匹配 name=红楼梦$price=345
    
    # 支持的查询方式
http://127.0.0.1:8000/api/v1/books/?price=345
http://127.0.0.1:8000/api/v1/books/?price=345&name=红楼梦

自己定制过滤实现过滤

查询价格大于100的所有图书

http://127.0.0.1:8000/api/v1/books/?price_gt=100
  1. 定义一个过滤器,继承BaseFileterBackend,重写filter_queryset方法,

    from rest_framework.filters import BaseFilterBackend
    class CommonFilter(BaseFilterBackend):
        def filter_queryset(self, request, queryset, view):
            # 在里面实现过滤,返回qs对象,就是过滤后的数据
            price_gt = request.query_params.get('price_gt', None)
            if price_gt:
                qs = queryset.filter(price__gt=int(price_gt))  # 注意price字段类型
                return qs
            else:
                return queryset
    
    

    注意:下载django-filter会将django版本升为最新版本

  2. 配置在视图类上

    from .filter import CommonFilter
    from rest_framework.filters import OrderingFilter
    class BookView(ViewSetMixin, ListAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        permission_classes = []
        authentication_classes = []
        throttle_classes = []
        # 第三方过滤器
        filter_backends = [OrderingFilter,CommonFilter]  # 可以定制多个,从左到右依次执行
        ordering_fields =['price']
    

排序的使用

from rest_framework.filters import OrderingFilter
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    filter_backends = [OrderingFilter]  # 可以定制多个,从左到右依次执行
    ordering_fields =['price']
    
# 支持查询方法
    http://127.0.0.1:8000/api/v1/books/?ordering=price  # 升序
    http://127.0.0.1:8000/api/v1/books/?ordering=-price #降序
   http://127.0.0.1:8000/api/v1/books/?ordering=-id,price 

分页

分页只针对查询所有的接口,其他四个接口不需要分页。drf内置了三个分页器,对应三种分页方式,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用。一个接口只能有一种分页方式,不能混合分页方式

分页类

from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
# 网页用它
class CommonPageNumberPagination(PageNumberPagination):
    page_size = 2 # 每页显示2条
    page_query_param = 'page'  # page=10  查询10页的数据,每页显示2条
    page_size_query_param = 'size'  # page=10&size=5
    max_page_size = 5 # 每页最大显示10条

# LimitOffset
class CommonLimitOffsetPagination(LimitOffsetPagination):
    default_limit=3 # 每页显示2条
    limit_query_param = 'limit'  # limit=3  取三条
    offset_query_param = 'offset' # offset=1  从第一个位置开始,取limit条
    max_limit = 5
    # offset=3&limit=2    0 1 2 3 4 5 /


# app 用下面
class CommenCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'  # 查询参数
    page_size = 2  # 每页多少条
    ordering = 'id'  # 排序字段

视图类

from .page import CommonPageNumberPagination as PageNumberPagination  # 取别名
from .page import CommonLimitOffsetPagination
from .page import CommenCursorPagination
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    # 之前的东西一样用 ,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用
    # pagination_class=PageNumberPagination
    # 基本分页方式(基本是这种,网页端):http://127.0.0.1:8000/api/v1/books/?page=2&size=1

    # pagination_class = CommonLimitOffsetPagination
    #   偏移分页  http://127.0.0.1:8000/api/v1/books/?Iimit=2&offset=1
    # 从第一条开始,取2条

    pagination_class = CommenCursorPagination
    # 游标分页,只能下一页,上一页,不能跳到中间,但它的效率很高,大数量分页,使用这种最好



认证、权限、频率源码分析

权限类的执行源码

权限的执行流程

写一个权限类,局部使用,配置在视图类的,就会执行权限类的has_permission方法,完成权限校验。

权限的源码执行流程

drfAPIView三大认证是在执行视图类的方法之前就执行了三大认证,也就是APIView中的dispatch方法中的self.initial

image

image

1. APIView类dispatch方法
    497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
    
2.  APIView类的initial方法,三大组件执行位置
    399行左右
    def initial(self, request, *args, **kwargs):
        # 能够解析的编码,版本控制。。。。
        self.format_kwarg = self.get_format_suffix(**kwargs)
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

     	# 认证组件的执行位置
        self.perform_authentication(request)
        # 权限组件  [读它]
        self.check_permissions(request)
        # 频率组件
        self.check_throttles(request)
        
        
3.APIView的 check_permissions方法    
# APIView的326 左右
    def check_permissions(self, request):
        # self.get_permissions()----》[CommonPermission(),]
        # permission 是我们配置在视图类上权限类的对象
        for permission in self.get_permissions():
            # 权限类的对象,执行has_permission,这就是为什么我们写的权限类要重写has_permission方法	
            # self 是视图类的对象,就是咱们自己的的权限类的has_permission的view参数
            if not permission.has_permission(request, self):
                # 如果return 的是False,就会走这里,走这里是,没有权限
                # 如果配了多个权限类,第一个没过,直接不会再执行下一个权限类了
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )
                
4. 解析--->>for permission in self.get_permissions()  
# APIView的274行左右  get_permissions
    def get_permissions(self):
        # self.permission_classes  是咱们配置在视图类上的列表,里面是一个个的权限类,没加括号
        # permission_classes = [CommonPermission]
        # [CommonPermission(),]   本质返回了权限类的对象,放到列表中
        return [permission() for permission in self.permission_classes]

总结

  1. 权限类源码的执行流程:APIView的dispatch方法(self.initial)---->APIView的initial方法中的self.check_permissions(request)---->l里面取出配置在视图类上的权限类,实例化得到对象,for循环一个个执行对象的has_permission方法(重写的方法),如果返回False就直接结束,不再往下执行,权限就认证通过

  2. 如果视图类上不配做权限类,permission_classes = [CommonPermission],会默认使用配置文件的api_settings.DEFAULT_PERMISSION_CLASSES

    执行的顺序:优先使用项目配置文件----->其次使用drf内配置文件

认证类的执行源码

drfAPIView三大认证是在执行视图类的方法之前就执行了三大认证,也就是APIView中的dispatch方法中的self.initial

1. APIView类dispatch方法
    497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
    
2.  APIView类的initial方法,三大组件执行位置
    399行左右
    def initial(self, request, *args, **kwargs):
        # 能够解析的编码,版本控制。。。。
        self.format_kwarg = self.get_format_suffix(**kwargs)
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

     	# 认证组件的执行位置 【读它】
        self.perform_authentication(request)
        # 权限组件  
        self.check_permissions(request)
        # 频率组件
        self.check_throttles(request)
        
        
3.APIView的 perform_authentication    
# APIView的316 左右
   def perform_authentication(self, request):
        request.user #咱们觉得它是个属性,其实它是个方法,包装成了数据属性
        # request 是新生的request
        
4. # Request类的user方法   219行左右
    @property
    def user(self):  # self 是Request的对象
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
        
        
                
5. 找Request类的self._authenticate()   373 行  
    def _authenticate(self):
        # self.authenticators 我们配置在视图类上认证类的一个个对象,放到列表中
        # Request类初始化的时候,传入的
        for authenticator in self.authenticators:
            try:
                # 返回了两个值,第一个是当前登录用户,第二个的token,只走这一个认证类,后面的不再走了
                # 可以返回None,会继续执行下一个认证类
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                # 解压赋值:
                #self.user=当前登录用户,self是当次请求的新的Request的对象
                #self.auth=token
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()
        
 6.解析for authenticator in self.authenticators
# self.authenticators  去Request类的init中找     152行左右
    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        .....
        self.authenticators = authenticators or ()
		.....
        
        
7. # 什么时候调用Reqeust的__init__?---》APIVIew的dispatch上面的492行的:request = self.initialize_request(request, *args, **kwargs)-----》385行----》def initialize_request
    def initialize_request(self, request, *args, **kwargs):
        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            # get_authenticators() 得到的是我们认证类产生的对象
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
            
8.解析---authenticators=self.get_authenticators()
    def get_authenticators(self):
        # self.authentication_classes  是咱们配置在视图类上的列表,里面是一个个的认证类,没加括号
        # authentication_classes=[LoginAuth]
        # [LoginAuth(),]   本质返回了类的对象,放到列表中
        return [auth() for auth in self.authentication_classes]

总结

  1. 配置在视图类上的认证类,会执行视图类方法之前执行认证,在权限类之前就执行认证类的认证。
  2. 自己写的认证类,可以返回两个值或None
  3. 后续可以从request.user取出当前登录用户(前提要在认证类中返回user_token.user和token),如果没有返回是拿不到当前登录用户的

频率类的执行源码

drfAPIView三大认证是在执行视图类的方法之前就执行了三大认证,也就是APIView中的dispatch方法中的self.initial

1. APIView类dispatch方法
    497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
    
2.  APIView类的initial方法,三大组件执行位置
    399行左右
    def initial(self, request, *args, **kwargs):
        # 能够解析的编码,版本控制。。。。
        self.format_kwarg = self.get_format_suffix(**kwargs)
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

     	# 认证组件的执行位置 
        self.perform_authentication(request)
        # 权限组件  
        self.check_permissions(request)
        # 频率组件  【读它】
        self.check_throttles(request)
        
        
3. APIView中的check_throttles方法-----> 352行
    def check_throttles(self, request):
        throttle_durations = []
        #self.get_throttles() 配置在视图类上的频率类的对象,放到列表中
        # 每次取出一个频率类的对象,执行allow_request方法,如果是False,频率超了,不能再走了
        # 如果是True,没有超频率,可以直接往后
        for throttle in self.get_throttles():
            if not throttle.allow_request(request, self):
                throttle_durations.append(throttle.wait())
                # SimpleRateThrottle中已经帮我们写好了,直用

        if throttle_durations:
            # Filter out `None` values which may happen in case of config / rate
            # changes, see #1438
            durations = [
                duration for duration in throttle_durations
                if duration is not None
            ]

            duration = max(durations, default=None)
            self.throttled(request, duration)
                   
            
4.解析  for throttle in self.get_throttles()  
# APIView中的get_throttles方法
    def get_throttles(self):
	   # self.get_throttles  是咱们配置在视图类上的列表,里面是一个个的频率类,没加括号
        # throttle_classes = [CommonThrottle]
        # [CommonThrottle(),]   本质返回了类的对象,放到列表中
        return [throttle() for throttle in self.throttle_classes]
    
5.解析if not throttle.allow_request(request, self):
                throttle_durations.append(throttle.wait())
 # SimpleRateThrottle中allow_reques方法,
        def allow_request(self, request, view):
        if self.rate is None:
            return True

        self.key = self.get_cache_key(request, view)  # 频率类重写的方法---返回的是用户id
        if self.key is None:
            return True
		# [时间3,时间2,时间1] 
        self.history = self.cache.get(self.key, [])
        """ SimpleRateThrottle提前将timer = time.time装成类的属性,当timer()就是时间戳"""
        self.now = self.timer()  # 获取当前时间
		#history[-1]是从列表后面取
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()
         

总结

自己写的频率类:继承BaseThrottle,重写allow_request,在内部做判断,如果超频了,就返回False,如果没有超频率,就返回True

自定义频率类(了解)

class SuperThrottle(BaseThrottle):
    VISIT_RECORD = {}

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # 自己写逻辑,判断是否超频
        # (1)取出访问者ip
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 {ip地址:[时间1,时间2,时间3,时间4]}
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        # (1)取出访问者ip
        ip = request.META.get('REMOTE_ADDR')
        import time
        ctime = time.time()
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
        if ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[ip] = [ctime, ]
            return True
        # self.history  = [时间1]
        self.history = self.VISIT_RECORD.get(ip,[])
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        while self.history and ctime - self.history[-1] > 60:
            self.history.pop()
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        if len(self.history) < 3:
            self.history.insert(0, ctime)
            return True
        else:
            return False

    def wait(self):
        import time
        ctime = time.time()
        return 60 - (ctime - self.history[-1])

SimpleRateThrottle

写一个频率类,重写allow_request方法,在里面实现频率控制

1.# SimpleRateThrottle -- > allow_request
    def allow_request(self, request, view):
		# 这里就是通过配置文件和scop取出 频率限制是多少,比如一分钟访问5此  '5/m'
        if self.rate is None:  # 属性就要去双下init查找-第2点拿到( #5    36000)
            return True
		
        # 返回了ip,就以ip做限制  
        self.key = self.get_cache_key(request, view) # 自定频率类返回的
        if self.key is None:
            return True
		# 获取缓存的ip和时间 【时间3,时间2,时间1】
        self.history = self.cache.get(self.key, [])
        # 获取当前执行时间
        self.now = self.timer()  # SimpleRateThrottle的属性
        while self.history and self.history[-1] <= self.now - self.duration:  # self.duration=3600
            self.history.pop()
        if len(self.history) >= self.num_requests: # num_requests=5
            return self.throttle_failure()
        return self.throttle_success()
    
2.# 解析if self.rate is None: 
SimpleRateThrottle的init方法
    def __init__(self):
        if not getattr(self, 'rate', None):
            # self.rate= '5/m'
            self.rate = self.get_rate()
        # 5    36000
        self.num_requests, self.duration = self.parse_rate(self.rate)
        
3.解析 self.rate = self.get_rate()//
# SimpleRateThrottle的get_rate() 方法
    def get_rate(self):
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            #  self.scope='kiki'='5/m' 是 kiki 字符串 
            # return '5/m'
            return self.THROTTLE_RATES[self.scope] # 配置里写的'5/m'
        """  1.self.THROTTLE_RATES优先查询项目配置里 'DEFAULT_THROTTLE_RATES': {
        'kiki': '5/m',
    },
       2.如果项目配置没有就使用drf项目配置里的 THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES """
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)
            
            
4.解析self.num_requests, self.duration = self.parse_rate(self.rate)
# SimpleRateThrottle的parse_rate 方法
	def parse_rate(self, rate): # rate传过来的self.rate
        # rate=self.rate='5/m'
        if rate is None:
            return (None, None)
        # num =5
        # period= 'minute'
        num, period = rate.split('/') #['5','m']
        # num_requests=5
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        # (5,36000)
        return (num_requests, duration)

基于APIView编写分页

分页功能: 只有查询所有才有分页功能

""" 基于APIView编写分页"""
from rest_framework.views import APIView
from rest_framework.generics import ListAPIView
class BookView(ViewSetMixin,APIView):
    authentication_classes = []
    permission_classes = []
    # 自动路由是可以映射五个接口,get---list   post----create  put---update.....
    """
        def list(self, request, *args, **kwargs):
            queryset = self.filter_queryset(self.get_queryset())
            # 过滤得到的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)
    """
    def list(self,request):
        books = Book.objects.all()
        # 使用步骤
        # 1.实例化得到一个分页类的对象
        paginator = CommonLimitOffsetPagination()
        # 2.调用分页类对象的paginate_queryset方法来完成分页,返回的page是序列化的数据,分页好的’
        page = paginator.paginate_queryset(books,request,self)
        if page is not None:
            serializer = BookSerializer(instance=page,many=True)
            # 3.返回数据,调用paginator的get_paginated_response方法
            # return paginator.get_paginated_response(serializer.data) # 返回给前端是默认的格式
            # 可以自己定制
            return Response({
                'total':paginator.count,
                'next':paginator.get_next_link(),
                'previous':paginator.get_previous_link(),
                'results':serializer.data
            })
            """
                def get_paginated_response(self, data):
        return Response(OrderedDict([
            ('count', self.count),
            ('next', self.get_next_link()),
            ('previous', self.get_previous_link()),
            ('results', data)
        ]))
            """
posted @ 2023-02-06 22:07  魔女宅急便  阅读(36)  评论(0)    收藏  举报
Title