DRF-内置(认证类、权限类、频率类)、过滤类的其他使用、全局异常处理

内置之认证类

# 内置的认证类:
    -BasicAuthentication
    -RemoteUserAuthentication
    -SessionAuthentication:session认证,建议自己写
        -如果前端带着cookie过来,经过session的中间件,如果登录了,在request.user中就有当前登录用户
        -drf没有限制是否登录
        -加了这个认证,如果没有登录,就不允许往后访问了
    -TokenAuthentication:jwt认证,建议自己写
    -建议自己写

内置之权限类

# 内置权限类
    -BasePermission:所有的基类
    -AllowAny:允许所有
    -IsAuthenticated:是否登录,登录了才有权限
        -认证类,如果登录了,返回(当前登录用户,None), 如果没有登录返回 None
    -IsAdminUser:auth的user表中is_staff字段是否为True,对后台管理有没有权限

内置之频率类

# 内置的频率类:
    -BaseThrottle:所有的基类
    -SimpleRateThrottle:咱们写频率类都继承它,少写代码
    -AnonRateThrottle:如果你想按ip地址限制,只需要在配置文件中配置
      'DEFAULT_THROTTLE_RATES': {
        'anon': '3/m',
        }
    -UserRateThrottle:如果你想按用户id限制,只需要在配置文件中配置
     'DEFAULT_THROTTLE_RATES': {
        'user': '3/m',
        }

认证类源码分析

####### 不要无脑按住ctrl建点击查找,因为方法,属性的查找顺序是按照 mro列表查找顺序来的

#  研究第一个点:三大认证的执行顺序---》入口---》APIView的dispathc----》三大认证
    -dispatch中第 497行 --》self.initial(request, *args, **kwargs)
    -self是谁?self是个对象,是谁的对象? 是视图类的对象---》BookView这些视图类
    -APIView的initial方法的第414行
        self.perform_authentication(request)  # 认证
        self.check_permissions(request)     # 权限
        self.check_throttles(request)      # 频率
        
        
        
# 为什么写了认证类,配置在视图类上,就会走认证?---》入口-->self.perform_authentication(request)
    -第一步:self.perform_authentication源码
    def perform_authentication(self, request):
         request.user
     -第二步:新的request类中 user(属性?方法?)
        from rest_framework.request import Request
         @property
        def user(self):
            if not hasattr(self, '_user'):
                with wrap_attributeerrors():
                    self._authenticate()  # 最开始,是没有_user的,执行这句
            return self._user
    -第三步:self._authenticate()  --》self是Request的对象---》Request的_authenticate方法
        def _authenticate(self):
            # authenticator配置的认证类的对象
            for authenticator in self.authenticators: # 你配置在视图类上authentication_classes = [你写的认证类,]----》[你写的认证类(),你写的认证类2()]
                try:
                    # 调用认证类对象的authenticate方法,传了几个?2个,一个认证类自己,一个是self:Request类的对象
                    user_auth_tuple = authenticator.authenticate(self)
                except exceptions.APIException: # 抛的是AuthenticationFailed,捕获的是APIException
                    self._not_authenticated()
                    raise

                if user_auth_tuple is not None:
                    self._authenticator = authenticator
                    # 如果返回了两个值,第一个值给了request.user,第二个值给了request.auth
                    self.user, self.auth = user_auth_tuple
                    return

    -第四步: self.authenticators到底是啥---》你配置在视图类上authentication_classes = [你写的认证类,]----》[你写的认证类(),你写的认证类2()]
        -Reqeust这个类实例化的时候,传入的,如果不传就是空元组
        -找Request的实例化---》dispatch中包装了新的Request
            -request = self.initialize_request(request, *args, **kwargs)
            -authenticators=self.get_authenticators(),
            
        -APIView中get_authenticators---》268行
          def get_authenticators(self):
             # self.authentication_classes配在视图类上的认证类列表---》认证类对象列表
            return [auth() for auth in self.authentication_classes]

过滤类的其他使用

内置的过滤类 只能通过?search=搜索条件 这种搜索,不能指定 name=三国&publish=出版社

# 第三方:django-filter
    -1 pip3 install django-filter
    -2 配置在视图类中配置:filter_backends = [DjangoFilterBackend, ]
    -3 配置搜索的字段:filterset_fields = ['name', 'publish']
    -4 支持的搜索方式:http://127.0.0.1:8008/books/?name=三国演义&publish=南京出版社
        -精准搜索,条件是并且的关系

	from django_filters.rest_framework import DjangoFilterBackend  # 导入的模块

	class BookView(ViewSetMixin, GenericAPIView, ListModelMixin):
		queryset = Book.objects.all()
		serializer_class = BookSerializer
		# 配置DjangoFilterBackend
		filter_backends = [OrderingFilter, DjangoFilterBackend]
		ordering_fields = ['price', ]
		pagination_class = CommonPageNumberPagination
		# search_fields = ['name', ]
		# 过滤条件,name与publish是并且的关系
		filterset_fields = ['name', 'publish']

自定义第三方模糊查询,实现可以通过图书名或作者模糊匹配

Myfilter.py

import django_filters
from rest_framework.filters import BaseFilterBackend

from app01.models import Book


class BookFilter(django_filters.FilterSet):
    class Meta:
        model = Book
        fields = {'name': ['icontains'], 'publish': ['icontains']}

view.py

from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin

from app01.models import Book
from app01.serializer import BookSerializer
from .Myfilter import BookFilter
from rest_framework.filters import OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend
class BookView(ViewSetMixin, GenericAPIView, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [OrderingFilter, DjangoFilterBackend]
    # 这里一定要配置DjangoFilterBackend,和filterset_class一起使用
    filterset_class = BookFilter
    ordering_fields = ['price', ]
    pagination_class = CommonPageNumberPagination
    search_fields = ['name', ]

自定义一个过滤类,实现可以通过图书名或作者模糊匹配

Myfilter.py

class BaseFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        name = request.query_params.get('name', None)
        publish = request.query_params.get('publish', None)
        if name:
            qs = queryset.filter(name__icontains=name)
        elif publish:
            qs = queryset.filter(publish__icontains=publish)
        else:
            qs = queryset.filter(name__icontains=name, publish__icontains=publish)
        return qs

view.py

from .Myfilter import BaseFilter
class BookView(ViewSetMixin, GenericAPIView, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [BaseFilter,]

image
image
image
多个过滤类和排序类可以共用,filter_backends=[],可以配置多个,执行顺序是从做往右,所以,放在最左侧的尽量先过滤掉大部分数据
额外补充

# 面试官问你,你在工作中遇到的问题及如何解决的?
    -我在写xx接口的时候,因为我们过滤条件很多,搜索数据库很慢,写了很多搜索类,之前时候搜索类的排序是随意的,搜索效率比较低,后来,读了drf搜索类的源码,发现执行顺序是从左往右,我就想到了,最左侧的搜索类,尽量搜索速度快,并且过滤掉的数据多,后续再搜索就会更快,所有我调整了一下搜索类的配置顺序,发现该接口的效率有所提升

全局异常处理

drf中无论在三大认证还是视图类的方法中执行,只要报错(主动抛异常),都能执行一个函数(异常处理的函数)
只要除了异常在APIView的dispatch中捕获了,执行了配置文件中配置的:'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
在三大认证以及视图函数中的错误在源码中会被 try 捕获

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

其异常返回的格式已经在 DRF 配置文件中配置了,但是并不符合我们的要求,我们可以重写其方法,看源码可以发现其执行了 handle_exception 方法,

def handle_exception(self, exc):
    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

exception_handler = self.get_exception_handler()
response = exception_handler(exc, context)
上面俩句源码说明处理的是 get_exception_handler 方法

def get_exception_handler(self):
    """
    Returns the exception handler that this view uses.
    """
    return self.settings.EXCEPTION_HANDLER

在 get_exception_handler 方法中返回异常处理程序。
这里默认返回的是 settings 下的 EXCEPTION_HANDLER 如下所示

'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',

也就是说,我们只要重写 exception_handler 方法即可
exception_handler 源码如下

def exception_handler(exc, context):
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()

    if isinstance(exc, exceptions.APIException):
        headers = {}
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait

        if isinstance(exc.detail, (list, dict)):
            data = exc.detail
        else:
            data = {'detail': exc.detail}

        set_rollback()
        return Response(data, status=exc.status_code, headers=headers)

    return None

if isinstance(exc, exceptions.APIException): isinstance 方法判断 ecx 是否为 APIException 的实例对象,所有继承了 APIException 的异常都会被处理。也就是说,DRF 只处理自己的异常。

自定义一个全局异常处理

# 写一个函数
def common_exception_handler(exc, context):
    # 记录日志:只要进入到这里面,一定是出了异常,只要出异常就要记录日志
    request = context.get('request')  # 当此请求的request
    try:
        username = request.user.username
    except:
        username = '没有登录'
    ctime = time.time()
    path = request.path
    method_type = request.method
    print('%s用户,在%s时间,访问%s接口,通过%s请求访问,出了错误:%s' % (username, str(ctime), path, method_type, str(exc)))
    # 后期以上的代码要写到日志中
    
    
    response = exception_handler(exc, context)  # 内置的这个异常处理函数,只处理了drf自己的异常(继承了了APIException的异常)
    if response:  # 如果response有值,说明错误被处理了(Http404,PermissionDenied,APIException)
        return Response({'code': 888, 'msg': 'drf错误,错误原因是:%s' % response.data.get('detail', '未知错误')})
    else:  # 如果是None,这个错误没有被处理,说明这个错误不是drf的错误,而是django的或代码的错误
        return Response({'code': 999, 'msg': '系统错误,错误原因是:%s' % str(exc)})
    
    
 # 配置在配置文件中
    REST_FRAMEWORK = {
        'EXCEPTION_HANDLER': 'app01.exctptions.common_exception_handler'
    }
posted @ 2022-10-11 21:05  张张包~  阅读(42)  评论(0编辑  收藏  举报