## 内容回顾 ### 认证的使用,如果要使用多个认证类,那么需要返回None,在最后一个认证类中返回:user对象,token ```python 有些接口需要登录后才能访问 原生django使用auth的user表,auth自带认证 自己登录,使用自定义的用户表 认证类的使用步骤 1.写一个认证类,继承BaseAuthentication 2.重写authenticate,在方法中完成认证,认证成功返回当前登录user对象,token,或返回None token从地址栏中取 request.query_params.get('token') 原生django取前端传入的Cookie request.COOKIE.get('sessionid') 后期如果想从请求头中取 request.META.get('HTTP_TOKEN') 如果认证通过,返回两个值,第一个是当前登录用户,第二个是token,或者返回None 3.局部使用在视图类中配置 authentication_classes = [] 4.项目settings.py配置 REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': [ 'app01.authentication.CommenAuthentication' ], 5.drf内置 ``` ### 权限的使用 ```python 写的权限类不一样:继承BasePermission 重写的方法不一样:has_permission 逻辑可能比较复杂 ACL:访问控制类表 rbac:公司内部系统,基于角色的访问控制 abac:rabc升级版,加了属性认证 return True或False 中文提示:self.message='提示信息' get_字段名_display() 获取到字段的choice参数中的中文 如:choice=((1,'普通用户'),(2,'高级管理'),(3,'超级管理')) ``` ### 频率的使用 ```python 写类继承SimpleRateThrottle 重写get_cache_key,返回什么,就以什么做频率限制 类属性:scope='名字' 配合配置文件中:'DEFAULT_THROTTLE_RATES': { '名字':'5/m' # 频率限制s秒,m分中,h小时,d天,只要以这个几个其中的任意一个开头后面不论写多少都能识别,例如mmmmmmmmmmmmm识别的为一分钟内访问多少次不允许访问 }, ``` ### 过滤的使用(查询所有才有过滤) ```python 内置的:模糊匹配 filter_backends = [SearchFilter] search_fields = ['name','price'] 第三方:精准匹配 filter_backends = [DjangoFilterBackend] filterset_fields = ['name','price'] # 精准匹配,可以一个一个匹配,也可以两个一起匹配 自定义: filter_backends = [CommonFilter] from rest_framework.filters import BaseFilterBackend class CommonFilter(BaseFilterBackend): def filter_queryset(self, request, queryset, view): price_gt = request.query_params.get('price_gt',None) if price_gt: #在里面实现过滤,返回qs对象,就是过滤后的数据 return queryset.filter(price__gt=price_gt) return queryset ``` ### 排序的使用 ```python 内置排序OrderingFilter排序类 filter_backends = [OrderingFilter,CommonFilter] ordering_fields = ['price','id'] ``` ### 分页的使用 ```python 三种分页方式:重写三个类 但是一个接口只能有一种分页类 from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination # pc网页端经常使用 class CommonPageNumber(PageNumberPagination): page_size = 2 # 默认显示条数 page_query_param = 'page' # 分页参数名 page_size_query_param = 'size' # 分大小参数名一个页面显示条数 max_page_size = 5 # 最大一个页面显示条数 class CommonLimitOffset(LimitOffsetPagination): default_limit = 2 limit_query_param = 'limit' offset_query_param = 'offset' max_limit = 5 # 游标分页效率极高,手机app经常使用 class CommonCursor(CursorPagination): cursor_query_param = 'cursor' page_size = 3 ordering = 'id' pagination_class = CommonPageNumber # 方式一 pagination_class = CommonLimitOffset # 方式二可以和分页排序一起使用 pagination_class = CommonCursor # 方式三 ``` ### 原生django的Cookie+session认证底层原理  ## 今日内容 ### 断点调试使用 程序是debug模式允许,可以在任意位置停下,查看当前情况下变量数据的变化情况 pycharm调试程序 1. 以debug形式允许  2. 在左侧空白出,点击加入断点(红圈)  3. step over 单步调试,只运行单行  4. step into 进入到函数内部运行,可以进入函数或类内不中一行行执行 5. 快速调到下一个断点,绿色箭头  ### 认证,权限,频率源码分析(了解) ### 权限类的执行源码 权限的源码执行流程 写一个权限类,局部使用,配置在视图类的,就会执行权限的has_permission方法,完成权限校验 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证》dispatch方法 ```python 497行左右, self.initial(request, *args, **kwargs)---》执行3大认证 # APIView类的399行左右: def initial(self, request, *args, **kwargs): # 能够解析的编码,版本控制。。。。。 self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use. 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) ``` #### 读self.check_permissions(request)源码执行 ```python # APIView的326 左右 def check_permissions(self, request): # self.get_permissions()----》[CommonPermission(),] 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) ) # APIView的274行左右 get_permissions def get_permissions(self): #self 是自己写的视图类对象 # self.permission_classes 是咱们配置在视图类上的列表,里面是一个个的权限类,没加括号 # permission_classes = [CommonPermission] # [CommonPermission(),] 本质返回了权限类的对象,放到列表中 return [permission() for permission in self.permission_classes] ``` #### 总结: APIView–>dispatch–>initial–>倒数第二行》self.check_permissions(request) 里面取出配置在视图类上的权限类,实例化得到对象,一个个执行对象的has_permission方法,如果返回False,就直接结束,不在继续往下执行,权限就认证通过 如果视图类上不配权限类:permission_classes = [CommonPermission],会使用配置文件的 api_settings.DEFAULT_PERMISSION_CLASSES优先使用项目配置文件,其次使用drf内置配置文件 ### 认证源码分析 APIView中的request都是被包装过的restframework.request中的类产生的对象 ```python # 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的 -497行左右, self.initial(request, *args, **kwargs)---》执行3大认证 # APIView类的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) ``` #### 读self.perform_authentication(request)源码 ```python # APIView的316行左右 def perform_authentication(self, request): request.user #咱们觉得它是个属性,其实它是个方法,包装成了数据属性 # Request类的user方法 219行左右 @property def user(self): if not hasattr(self, '_user'): with wrap_attributeerrors(): self._authenticate() return self._user @user.setter # 用于修改类中私有成员变量 def user(self, value): self._user = value self._request.user = value # self 是Request的对象,找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() # self.authenticators 去Request类的init中找 152行左右 def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None): ..... self.authenticators = authenticators or () ..... # 什么时候调用Reqeust的__init__?---》APIVIew的dispatch上面的492行的:request = self.initialize_request(request, *args, **kwargs)-----》385行----》 def initialize_request(self, request, *args, **kwargs): return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context ) ``` #### 总结 1. 配置再视图上的认证类,会再执行视图类方法之前执行,再权限认证之前执行 2. 自己写的认证类,可以返回两个只或None 3. 后续可以从request.user取出当前登录用户(前提是你要在认证类中返回) ### 频率源码分析 ```python # 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的 -497行左右, self.initial(request, *args, **kwargs)---》执行3大认证 # APIView类的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) ``` #### 读self.check_throttles(request)源码 ```python # APIView 的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()) 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) # APIView 的280行 def get_throttles(self): # 从视图类中拿出频率类产生对象放到列表中 return [throttle() for throttle in self.throttle_classes] ``` #### 总结 我们写的频率类:继承BaseThrottle,重写allow_request,在内部判断,如果超频率了,就返回false,如果没超频率,就返回True ### 自定义频率类(了解) ```python 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方法,在里面实现频率控制 ```python # 写一个频率类,重写allow_request方法,在里面实现频率控制 # SimpleRateThrottle---》allow_request def allow_request(self, request, view): # 这里就是通过配置文件和scop取出 频率限制是多少,比如一分钟访问5此 if self.rate is None: return True # 返回了ip,就以ip做限制 self.key = self.get_cache_key(request, view) if self.key is None: return True # 下面的逻辑,跟咱们写的一样 self.history = self.cache.get(self.key, []) self.now = self.timer() 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() # SimpleRateThrottle的init方法 def __init__(self): if not getattr(self, 'rate', None): # self.rate= '5、h' self.rate = self.get_rate() # 5 36000 self.num_requests, self.duration = self.parse_rate(self.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 是 lqz 字符串 # return '5/h' return self.THROTTLE_RATES[self.scope] except KeyError: msg = "No default throttle rate set for '%s' scope" % self.scope raise ImproperlyConfigured(msg) # SimpleRateThrottle的parse_rate 方法 def parse_rate(self, rate): # '5/h' if rate is None: return (None, None) # num =5 # period= 'hour' num, period = rate.split('/') # 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编码分页 ```python # 分页功能,只有查询所有才有 class BookView(ViewSetMixin, APIView): def list(self, request): books = Book.objects.all() # 过滤函数正常使用 books = self.filter_queryset(books) # 使用步骤 # 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 }) ``` ### 分页器源码分析 ```python #切入点为listModelmixin # listModelmixin类中list方法 37行左右 def list(self, request, *args, **kwargs): # 条件不用管他过滤 queryset = self.filter_queryset(self.get_queryset()) # 这路调用分页找GenericAPIView 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) # GenericAPIView类中 165行左右 def paginate_queryset(self, queryset): # 这里self.paginator是一个伪装方法在GenericAPIView类中 153行左右 # 在伪装函数内拿到后分页器对象后调用paginate_queryset(queryset, self.request, view=self)然后去到分页器类中找例如我们写的分页器继承了PageNumberPagination就去这里面找 if self.paginator is None: return None return self.paginator.paginate_queryset(queryset, self.request, view=self) # GenericAPIView类中 153行左右 @property def paginator(self): # 反射拿视图类中分页器类 if not hasattr(self, '_paginator'): # 这里取视图中如果有分页器类则产生分页器对象返回出去 if self.pagination_class is None: self._paginator = None else: self._paginator = self.pagination_class() return self._paginator # PageNumberPagination类中 191行左右 # 这里面实现了分页功能返回了列表套对象的数据 def paginate_queryset(self, queryset, request, view=None): """ Paginate a queryset if required, either returning a page object, or `None` if pagination is not configured for this view. """ page_size = self.get_page_size(request) if not page_size: return None paginator = self.django_paginator_class(queryset, page_size) page_number = self.get_page_number(request, paginator) try: self.page = paginator.page(page_number) except InvalidPage as exc: msg = self.invalid_page_message.format( page_number=page_number, message=str(exc) ) raise NotFound(msg) if paginator.num_pages > 1 and self.template is not None: # The browsable API should display pagination controls. self.display_page_controls = True self.request = request return list(self.page) 这样就拿到了分好页的数据 最后调了在list方法中 return self.get_paginated_response(serializer.data) # 有调了GenericAPIView中的 173行左右 def get_paginated_response(self, data): # 断言有这个分页器对象 assert self.paginator is not None # 调了分页器对象里的方法 return self.paginator.get_paginated_response(data) # 在PageNumberPagination类中 224行 # 在这里面就把处理好的数据返回出去了 def get_paginated_response(self, data): return Response(OrderedDict([ ('count', self.page.paginator.count), ('next', self.get_next_link()), ('previous', self.get_previous_link()), ('results', data) ])) ``` ### 异常处理源码分析 ```python # APIView--->dispatch--->三大认证,视图类的方法,如果出了一场,会被一场捕获,捕获后统一处理 # drf 内置了一个函数,只要上面过程出了异常,就会执行这个函数,这个函数只处理的drf的异常 -主动抛的非drf异常 -程序出错了 都不会被处理 我们的目标,无论主动抛还是程序运行出错,都同意返回规定格式--》能记录日志 公司里一般返回 {code:999,'msg':'系统错误,请联系系统管理员'} ``` ```python # 切入点APIView中的dispatch方法中的 #APIView类中dispatch 485行左右 如果捕捉到异常了就会执行这个函数 self.handle_exception(exc) def dispatch(self, request, *args, **kwargs): try: 。。。。 except Exception as exc: 出错了会走这个函数 response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response # 找到APIView类中 448行 def handle_exception(self, exc): # 这里是只处理了属于NotAuthenticated,AuthenticationFailed与这个两个异常类 if isinstance(exc, (exceptions.NotAuthenticated, exceptions.AuthenticationFailed)): # WWW-Authenticate header for 401 responses, else coerce to 403 auth_header = self.get_authenticate_header(self.request) if auth_header: exc.auth_header = auth_header else: exc.status_code = status.HTTP_403_FORBIDDEN # 去哪项目配置文件中的异常错误函数名 exception_handler = self.get_exception_handler() context = self.get_exception_handler_context() # 执行异常函数名然后返回处理的数据 response = exception_handler(exc, context) if response is None: self.raise_uncaught_exception(exc) response.exception = True # 最后在返回出去 return response # 自己写的异常处理函数 # myxception.py # 两种一种不调用原来的了 from rest_framework.exceptions import APIException from rest_framework.response import Response def mytotalException(exc, context): # print(exc, context) # 这里查看是不是drf异常,如果是返回一种,不是则返回另外一种 if isinstance(exc, APIException): res = {'code': 666, 'msg': '请求出错', 'error': exc.detail} else: res = {'code': 999, 'msg': '系统出错请联系管理员', 'error': str(exc)} # 这里直接返回给前端不需要再执行了 return Response(res) # 这种是调用原来的 from rest_framework.exceptions import APIException from rest_framework.response import Response from rest_framework.views import exception_handler def mytotalException(exc, context): # print(exc, context) exc = exception_handler(exc, context) if exc: # 这里可能会出错 res = {'code': 666, 'msg': '请求出错', 'error': exc.data.get('detail')} else: res = {'code': 999, 'msg': '系统出错请联系管理员', 'error': str(exc)} return Response(res) # 在配置文件中配置 REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'app01.myexception.mytotalException', } ```   Last modification:February 9th, 2023 at 10:24 pm © 允许规范转载 Support 如果觉得我的文章对你有用,请随意赞赏 ×Close Appreciate the author Sweeping payments
Comment here is closed