[Django REST framework - 国际化语言配置、自动生成路由、action装饰器、认证、权限、频率、过滤、排序、异常处理]

[Django REST framework - 国际化语言配置、自动生成路由、action装饰器、认证、权限、频率、异常处理]

访问admin站点,先修改站点的语言配置

settings.py中配置以下信息即可

LANGUAGE_CODE = 'zh-hans'  # 中文

TIME_ZONE = 'Asia/Shanghai' # 时区是亚洲上海

USE_I18N = True # 国际化

USE_L10N = True # 本地化

USE_TZ = True # 数据库是否使用TIME_ZONE,True表示使用上海的时区,False表示不使用,使用UTC时间,然后转成上海,会差8个小时

1 路由组件

1.1 自动生成路由的基本使用

## 自动生成路由;视图类需要是ViewSetMixin子类
from rest_framework.routers import DefaultRouter,SimpleRouter

# router=DefaultRouter()
router=SimpleRouter()
router.register('books',views.BookView,'books')
urlpatterns = [
    path('', include(router.urls)),
]
------------------------------------------------------------------------------------
自动生成路由,在urls.py中
# 自动生成路由
from rest_framework.routers import SimpleRouter
router=SimpleRouter()
# 必须是继承ViewSetMixin的字类,才能自动生成路由
router.register('books',views.BookView)

# 方式一
urlpatterns = [
    path('api/', include(router.urls))
]

# 方式二
urlpatterns+=router.urls

action装饰器

在视图集中,如果想要让Router自动帮助我们为自定义的动作生成路由信息,需要使用rest_framework.decorators.action装饰器。
以action装饰器装饰的方法名会作为action动作名,与list、retrieve等同。

1.2 视图类中派生的方法,自动生成路由(action)

1 作用:给自动生成路由的视图类再定制一些路由
   methods 请求方式,detail是否带pk
    methods第一个参数,传一个列表,列表中放请求方式
2 用法一:detail布尔类型,detail=False
    
    # books/login/    # 自己扩展的方法(派生)
    @action(methods=['GET'], detail=False,url_path='login')   
    	# url_path 指定路径名字默认为方法名
    def login(self, request, *args, **kwargs):
        print(args)
        print(kwargs)
        return APIResponse(msg='发送成功')
    
3 方法二:detail布尔类型,detail=True
    # books/1/login/
    @action(methods=['GET'], detail=True)
    def login(self, request, *args, **kwargs):
        # pk=1
        print(args)
        print(kwargs)
        return APIResponse(msg='发送成功')

# 自动生成的路由是
books/login/   发送get请求,触发login方法的执行

# 如果detail=False,生成的路由是,在视图类的方法中,能够取到pk
books/5/login/



# 补充(了解),必须继承ViewSetMixin的视图类才有
视图类有action属性,是当此请求要执行的函数名
    

总结:

1 自动生成路由的视图类,
	-需要继承ViewSetMixin+9个视图字类
    -需要继承ViewSetMixin+视图类(APIView。。。)+5个视图扩展类
2 可以使用action的视图类,ViewSetMixin+视图类(APIView。。。)

2、认证

自定义认证类

"""
1)自定义认证类,继承 BaseAuthentication 类
2)必须重写 authenticate(self, request) 方法
	没有认证信息,返回None:匿名用户(游客) => 匿名用户request.user也有值,就是"匿名对象(Anonymous)"
	有认证信息,且通过,返回(user, token):合法用户 => user对象会存到request.user中
	有认证信息,不通过,抛异常:非法用户
"""

案例:

2.1 登录功能

# 表模型中两个表
class User(models.Model):
    username=models.CharField(max_length=32)
    password=models.CharField(max_length=32)

class UserToken(models.Model):
    user=models.OneToOneField(to='User',on_delete=models.CASCADE)
    token=models.CharField(max_length=64)
# 登录视图类
class UserView(APIView):
    def post(self, request, *args, **kwargs):
        res = {'code': 100, 'msg': None}
        username = request.data.get('username')
        password = request.data.get('password')
        user = models.User.objects.filter(username=username, password=password).first()

        if user:  # 登录成功再生成token
            token = str(uuid.uuid4())  # 生成一个不重复的随机字符串
            # 原来有,就更新,原来没有就新增
            # models.UserToken.objects.create(user=user,token=token)
            # 根据user=user去查询,如果有,使用defaults更新,如果没有,新增一条
            models.UserToken.objects.update_or_create(defaults={'token': token}, user=user)
            res['msg'] = '登录成功'
            res['token'] = token
        else:
            res['code'] = '101'
            res['msg'] = '用户名或密码错误'
        return Response(res)
# 路由配置
urlpatterns = [
    path('login/', views.UserView.as_view()),]

2.2 认证类的编写

# 新建一个py文件auth.py
from rest_framework.authentication import BaseAuthentication  # 认证模块
from rest_framework.exceptions import AuthenticationFailed  # 异常模块
from . import models
class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 写认证规则
        # 取出用户携带的token
        token=request.query_params.get('token')
        # 去数据库查询,token是否存在,如果存在,说明这个人登录了,如果不存在,表示没有登录
        user_token=models.UserToken.objects.filter(token=token).first()
        if user_token:
            # 登录了,认证通过,继续往后走
            '''
            如果返回user,token,后面视图类中通过request对象,可以取到当前登录用户
            '''
            return
        else:
            # 没有登录,不能继续往后走,抛认证失败异常
            raise AuthenticationFailed('您没有登录,认证失败')

            
            
            
# 对某个接口生效
class BookView(ViewSetMixin,ListAPIView):
    # 以后,必须登录以后才能访问这个视图类
    authentication_classes = [LoginAuth,]

2.3 认证类的全局使用和局部使用

# 全局使用
REST_FRAMEWORK={

    # 全局使用写得认证类
    'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.LoginAuth']

}

# 局部禁用(在视图类中)
class UserView(APIView):
    authentication_classes = []

# 认证类的查找顺序
先找视图类自己中有没有:authentication_classes,如果没有----》项目的配置文件DEFAULT_AUTHENTICATION_CLASSES------》drf的配置文件(有两个,这两个,等于无)

认证组件源码分析


# APIView--->dispathch方法的-----》self.initial(request, *args, **kwargs)(497行)---》APIView的initial方法----》有三句话
self.perform_authentication(request)  # 认证 
self.check_permissions(request)       # 权限
self.check_throttles(request)         # 频率



# self.perform_authentication(request)----》新的request对象的.user(是个方法,包装成了数据数属性)---》新的Request类中找到了user方法---》self._authenticate()-----》Request类中的_authenticate
    def _authenticate(self):
        for authenticator in self.authenticators: # 配置在视图类中认证类列表,实例化得到的对象
            # self 此时是新的 request 调用,所以 self 是 request
            # authenticators 是 Request 实例化的时候,传入的列表套对象,对象是认证类实例化的对象
            
            try:
                user_auth_tuple = authenticator.authenticate(self) 
                # 执行 认证类 实例化的对象 调用authenticate方法。
                # 可以返回元组,也可以不满足条件抛出异常
                
            except exceptions.APIException: # 捕获上面抛出的异常
                self._not_authenticated() # 异常之后,调用_not_authenticated方法。
                raise # 然后把捕获到的抛出的异常再直接抛出,可以被后面的异常捕获捕获到

            if user_auth_tuple is not None: # 如果认证类对象authenticate有返回值,则走这里
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple # 返回值,解压赋值给request
                return
            
            
 #  self.authenticators,self是Request类的对象,authenticators属性是Request这个类在实例化的时候,传入的

# Request类在实例化的时候代码
    return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),  # 这个时候的self是视图类的实例化对象,调用get_authenticators这个方法,去会找自己的authentication_classes,循环,实例化对象,放在列表里面返回。 authenticators 最后得到的就是列表套 认证类的对象,实例化后的request.authenticators 就是 列表套 认证类的对象
            negotiator=self.get_content_negotiator(), 
            parser_context=parser_context
        )


# self.get_authenticators()是APIView的方法
def get_authenticators(self):
      # 列表推导式
        return [auth() for auth in self.authentication_classes]
     # 返回结果是我们配在视图类中自己写的认证类列表的对象
        # 最后返回的就是 return [LoginAuth(),]
  

3、权限

自定义权限类

"""
1)自定义权限类,继承 BasePermission 类
2)必须重写 has_permission(self, request, view): 方法
	设置权限条件,条件通过,返回True:有权限
	设置权限条件,条件失败,返回False:有权限
	
3)drf提供的权限类:
AllowAny:匿名与合法用户都可以
IsAuthenticated:必须登录,只有合法用户可以
IsAdminUser:必须是admin后台用户
IsAuthenticatedOrReadOnly:匿名只读,合法用户无限制
"""

编写权限类

from rest_framework.permissions import BasePermission   # 导入BasePermission权限基类

class MyPermission(BasePermission): # 自定义权限类继承BasePermission
    message='你没有权限'   # 自定义返回给前端的访问错误信息
    
    def has_permission(self, request, view):
        # 写权限的逻辑(有权限访问就是True,没有权限就是False)
        if request.user.user_type == 1:

            return True
        else:
            self.message='你是%s用户,你没有权限'%request.user.get_user_type_display()
            return False
        

权限类的使用

# 局部使用(在视图类中加)
permission_classes = [MyPermission,]

# 全局使用(在配置文件中配置)
REST_FRAMEWORK={
    "DEFAULT_PERMISSION_CLASSES":["app01.auth.MyPermission",],
}

权限类源码分析

APIView---》dispatch-self.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),
                    code=getattr(permission, 'code', None)
                )
  ----------------------------------------------------------------------------------              

BasePermission 基类

has_permission 相当于判断功能权限,如:看是否有操作这个功能权限。

has_object_permission 相当于判断数据权限,如:看是否有操作这条数据的权限。

详细:has_object_permission 是用户过了 has_permission 判断有权限以后,再判断这个用户有没有对一个具体的对象有没有操作权限

原理:

在APIView的dispatch方法中,执行initial方法

再执行check_permissions方法,再调用get_permissions方法

会循环实例化permission_classes中的权限校验类,

再去调用权限校验类中的has_permission方法,传request、APIview 进行权限判断,如果不通过,抛出 permission_denied 错误

如果不抛错再进行has_object_permission方法,传request、APIview、obj(当前操作的对象) 进行权限判断,如果不通过,抛出 permission_denied 错误

4、频率

# 限制用户的访问次数,根据ip,根据用户id

# 写个类,继承基类,重写某个方法

from rest_framework.throttling import BaseThrottle,SimpleRateThrottle
class MyThrottle(SimpleRateThrottle):
    scope = 'ip_throttle'  # 一定要写
    def get_cache_key(self, request, view):
        # 返回谁,就以谁做限制
        # 按ip限制
        # print(request.META)
        return request.META.get('REMOTE_ADDR')
    
    
# 在视图类中配置
class BookView(ViewSetMixin,ListAPIView):
    throttle_classes = [MyThrottle,]
    
    
# 在配置文件中配置
REST_FRAMEWORK = {
    # 频率限制的配置信息
    'DEFAULT_THROTTLE_RATES': {
        'ip_throttle': '3/m'  # key要跟类中的scop对应,1分钟只能访问3此
    }

}


# 局部使用
class BookView(ViewSetMixin,ListAPIView):
    throttle_classes = [MyThrottle,]
    
    
# 全局使用(在配置文件中)
 # 全局使用频率类
'DEFAULT_THROTTLE_CLASSES': ['app01.auth.MyThrottle'],

从根上自定义频率类(了解)

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


from rest_framework.throttling import BaseThrottle  # 导入BaseThrottle频率基类

# {'127.0.0.1':[],'192.168.1.1':['8:31:13',],'192.168.1.2':[]}

class MyThrottling(BaseThrottle):
    VISIT_RECORD = {}  # 记录访问者的大字典

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # (1)取出访问者ip
        # print(request.META)
        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 = 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])
        return 1

# 全局使用
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES':['app01.utils.MyThrottles',],
}

# 局部使用
#在视图类里使用
throttle_classes = [MyThrottles,]
 

频率SimpleRateThrottle源码分析

class SimpleRateThrottle(BaseThrottle):
    
    def __init__(self):
        
        if not getattr(self, 'rate', None): 
            # 1、反射获取对象中的rate,如果不存在则是False,not False则是True,运行下面
            # 9、也可以直接设置类属性 rate = '频率',则不需要配置文件配置,也不需要写scope
            
            self.rate = self.get_rate()
            # 2、调用 get_rate() 方法
            
        self.num_requests, self.duration = self.parse_rate(self.rate)
        # 10、调用parse_rate(self.rate),返回值解压赋值
        # 11、self.num_requests则表示次数,self.duration表示访问的时间段范围
        # 19、还是以'3/m'举例,现在self.num_requests=3,self.duration=60
        
    def get_rate(self):
        
        if not getattr(self, 'scope', None):
            # 3、反射找到取对象的 scope ,存在为True,not True,则是False,不会运行
            # 4、如果没有写对象属性 scope 则会抛出异常 ↓
            
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            # 5、THROTTLE_RATES 拿到的django配置文件中自己设置的 DEFAULT_THROTTLE_RATES 字典   'DEFAULT_THROTTLE_RATES': {'ip':'3/m'}    ← 例如
            
            # 6、通过字典用对象的 scope 取值,拿到频率,返回给rate,所以rate代表的就是频率
            # 7、这也是为什么类属性scope要和配置文件中的字典 key 值必须保持一致的原因
            return self.THROTTLE_RATES[self.scope]
        
        except KeyError:
            
            # 8、如果类属性 scope 和 配置文件中的不一致,抛出异常
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)
            
    def parse_rate(self, rate):
        
        if rate is None:
            # 12、判断rate是否是None,如果是则返回(None, None),表示没有任何验证
            
            return (None, None)
        num, period = rate.split('/')
        # 13、切分 rate ,用/作为分隔依据
        # 14、比如 '3/m',最后num=3,period=m
        
        num_requests = int(num)
        # 15、把字符串类型转换成整型,赋值给num_requests,此时num_requests=3
        
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        # 16、period=m,也可以是m开头的mxxx,因为最后会用字符串索引取值方式取第一个
  		# 17、通过字典来取值,duration=60
  		return (num_requests, duration)
     # 18、返回 (3,60) 前者表示访问次数,后者是时间段。
                
    def allow_request(self, request, view):
        # 20、如果设置了频率组件,则会调用 allow_request 这个方法
        # APIView----》dispatch---》self.initial---》self.check_throttles(request)中可查看频率内调用的就是此方法
        if self.rate is None:
            # 21、肯定有,否则等于没有频率要求。要么在自定义的频率类中写,要么配置scope和配置文件
            
            return True
        
        self.key = self.get_cache_key(request, view)
        # 22、返回一个唯一的值,用来频率的校验,比如 ip 地址,唯一
        
        if self.key is None:
        # 23、判断是否设置了频率校验使用的值,如果没有,则直接返回True,表示不进行频率校验
        
            return True
        self.history = self.cache.get(self.key, [])
        # 24、cache是缓存模块。可以暂时理解为字典。取值使用get()赋值使用set()
        # 25、get(self.key, [])取ip地址值,取不到则是[],然后给self.history属性
        # 26、所以self.history要么是空的列表,要么就是个列表,里面都是时间数据
        
        self.now = self.timer()
        # 27、self.now 获取当前的时间
        
        while self.history and self.history[-1] <= self.now - self.duration:
        # 28、while循环,self.history不为空和后面条件满足,则为True,运行下面代码
        # 29、后面的条件:访问时间如果小于当前时间减去限制的时间段时间,则不在限制时间内,可以去除
        self.history.pop()
            # 30、满足了上面2个条件,说明列表有值,并且最后一个时间不在限制时间内,删除掉
            
       if len(self.history) >= self.num_requests:
        # 31、判断列表的长度是否大于等于3,如果大于等于3,则超过了限制时间段内的访问次数,返回False
        
            return self.throttle_failure()
       return self.throttle_success()
       # 32、列表长度小于3,则没有超过限制时间段内的访问次数,返回True
    
    def wait(self):
        # 33、在31返回False的时候,会执行这个方法,就是返回距离下次访问还需要多久时间。
        
        if self.history:
            remaining_duration = self.duration - (self.now - self.history[-1])
        else:
            remaining_duration = self.duration

        available_requests = self.num_requests - len(self.history) + 1
        if available_requests <= 0:
            return None

        return remaining_duration / float(available_requests)

5、过滤、排序

过滤Filtering

对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展来增强支持。

pip install django-filter
# 第三方pip3 install django-filter


# 视图类中
from django_filters.rest_framework import DjangoFilterBackend  # 导入模块
class BookView(ViewSetMixin,ListAPIView):

    # 在视图类中配置,最顶层的类至少是GenericAPIView
    filter_backends = [DjangoFilterBackend,]
    # 按名字过滤
    filter_fields=['name','price']
    
    
# 查询时
http://127.0.0.1:8000/books/?name=红楼梦
http://127.0.0.1:8000/books/?name=四方达&price=15

img

内置过滤器

# 查询所有才需要过滤(根据条件过滤),排序(按某个规则排序)

# 内置过滤类使用,在视图类中配置
from rest_framework.filters import SearchFilter
class BookView(ViewSetMixin,ListAPIView):
    # 在视图类中配置,最顶层的类至少是GenericAPIView
    filter_backends = [SearchFilter,]
    # 过滤条件,按名字过滤
    search_fields=['name','publish']
    
# 查询使用
http://127.0.0.1:8000/books/?search=达   # 出版社中或名字中有 达 就能查询出来
 

img

自定义过滤器

1 写一个类CourseNameFilter,继承BaseFilterBackend
2 重写filter_queryset,在内部实现过滤逻辑,返回queryset对象
3 在视图类中配置
filter_backends = [CourseNameFilter]

借助django-filter实现过滤功能

#########方案一(弱点是只能使用课程(course表)中的字段)#######
### 1 在视图类中
filter_backends = [DjangoFilterBackend]
filter_fields=['course_category','name']
## 使用
http://127.0.0.1:8000/api/course/actual_course/?course_category=2
  
------------------------------------------------------------------------------
#########方案二:可以自定义字段,及规则实现过滤#######   

filter_backends = [DjangoFilterBackend]   # 正常写入django过滤类
#  filter_fields=['course_category','name']   # 上述我们是这样注册的  这次不是  注意

filter_class = CourseFilterSet  # 而是这样 写一个类 给filter_class 也可以写多个放入列表

#### 过滤类 创建一个py文件 名字自定义
from django_filters.filterset import FilterSet
from django_filters import filters
class CourseFilterSet(FilterSet):
    # 实现了区间过滤
    min_price = filters.NumberFilter(field_name='price', lookup_expr='gte')
    # 最小价格   筛选出 price 大于等于min_price 的对象
    max_price = filters.NumberFilter(field_name='price', lookup_expr='lte')
    # 最大价格   筛选出 price 小于等于max_price 的对象
    '''
   	# min_price 为我们需要传入的值
    # filters.NumberFilter()  固定写法
    # field_name  是我们需要参照的字段为 Course表中的price字段
    # lookup_expr  条件 大于或者小于....
    # 总体就是说  例如 min_price 传入值为 30  
      传入的值大于表中price字段的值的话 就是符合条件
     上述为大于min_price 并且小于 max_price 的对象
   '''
    class Meta:
        model = models.Course
        fields = ['course_category','max_price','min_price']

-----------------------------------------------------------------------------

# 第三种方式 
from rest_framework.filters import BaseFilterBackend
class CourseNameFileter(BaseFilterBackend):  # 继承BaseFilterBackend  不继承也一样
	# 重写filter_queryset方法
    def filter_queryset(self, request, queryset, view):
        name = request.query_params.get('name')  # 获取到get请求对象传过来的值
        # post请求也可以~ 
        if name:  # 判断是否name是否传入
            queryset = queryset.filter(name__contains = name) 
            # 根据name获取来实现过滤
            return queryset  # 返回queryset对象
        return queryset  # 如果没有传入  相当于没有过滤直接返回

排序

对于列表数据,REST framework提供了OrderingFilter过滤器来帮助我们快速指明数据按照指定字段进行排序。

使用方法:

在类视图中设置filter_backends,使用rest_framework.filters.OrderingFilter过滤器,REST framework会在请求的查询字符串参数中检查是否包含了ordering参数,如果包含了ordering参数,则按照ordering参数指明的排序字段对数据集进行排序。

前端可以传递的ordering参数的可选字段值需要在ordering_fields中指明。

示例1:
class StudentListView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    filter_backends = [OrderingFilter]
    ordering_fields = ('id', 'age')

# 127.0.0.1:8000/books/?ordering=-age
# -id 表示针对id字段进行倒序排序
# id  表示针对id字段进行升序排序

如果需要在过滤以后再次进行排序,则需要两者结合!

from rest_framework.generics import ListAPIView
from students.models import Student
from .serializers import StudentModelSerializer
from django_filters.rest_framework import DjangoFilterBackend

class Student3ListView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    filter_fields = ('age', 'sex')
    # 因为局部配置会覆盖全局配置,所以需要重新把过滤组件核心类再次声明,
    # 否则过滤功能会失效
    filter_backends = [OrderingFilter,DjangoFilterBackend]
    ordering_fields = ('id', 'age')
示例2:
1 在views.py 视图类中
from app01 import models
from app01 import serializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.filters import OrderingFilter # 导入过滤器排序类
​
class BookView(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = serializer.BookModelSerializer
    ###排序
    # 配置排序类(局部使用)
    filter_backends = [OrderingFilter, ] 
    # 配置排序字段
    ordering_fields=['price','name']
    
    
2 请求方式:(postman中测试)
     http://127.0.0.1:8000/book/?ordering=price  # 默认升序
    http://127.0.0.1:8000/book/?ordering=-price  # 降序
   http://127.0.0.1:8000/book/?ordering=-price,-name # 配多个按,分隔
        
        
3 全局使用:在settings.py 配置文件中配置
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.OrderingFilter',),
​
}

img

6、异常处理

1 全局统一捕获异常,返回固定的格式  {code:999,msg:'未知错误'}

2 使用步骤
	-写一个函数
    -在配置文件中配置
    
    
    
#  写函数
from rest_framework.views import exception_handler
from rest_framework.response import Response
def common_exception_handler(exc, context):
    # 记录日志
    print(context['request'].META.get('REMOTE_ADDR'))
    response=exception_handler(exc, context)
    if response:  # 处理了drf的异常
        data = {'code': 999, 'msg': '错误','detail':response.data}
        return Response(data)
    else: # 不知道的异常
        data = {'code':999,'msg':'未知错误'}
        return Response(data)
    
    
# 配置文件配置

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.utils.common_exception_handler',
}

案例2:

# 在views.py 视图类中写一个带错误的视图类
from rest_framework.views import APIView
​
class TestView(APIView):
    throttle_classes = [MyThrottling, ]
​
    def get(self, request):
        a=[1,3,4]
        # 4/0
        print(a[100])
        return Response('test')
    
# urls.py  路由
from django.urls import path
from app01 import views
​
urlpatterns = [
    path('test/', views.TestView.as_view()),
  
]

img

REST framework提供了异常处理,我们可以自定义异常处理函数。
1 手动创建一个名为exception的py文件,名字随意,在文件中写一个函数
​
from rest_framework.views import exception_handler
from rest_framework.response import Response
​
def custom_exception_handler(exc, context):
    # 先调用REST framework默认的异常处理方法获得标准错误响应对象
    response=exception_handler(exc, context) 
    # 捕获异常后端窗口信息打印
    print(response) # 只要出错,这个就是None
    print(exc) # 错误信息在exc里
    print(context.get('view')) # 视图类
    print(context.get('request').get_full_path()) # 当次请求的request对象
    # 在此处补充自定义的异常处理
    if not response: # 更细粒度的对异常做一个区分
        if isinstance(exc,IndexError):
            response=Response({'status':5001,'msg':'越界异常'})
        elif isinstance(exc,ZeroDivisionError):
            response = Response({'status': 5002, 'msg': '越界异常'})
        else:
            response= Response({'status': 5000, 'msg': '没有权限'})
    # 在这可以记录日志:一旦出错就记录日志
    return response
​
2 在settings.py 配置文件中配置
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER':'app01.excepiton.custom_exception_handler',
}
3 以后只要视图中执行出了错误,就会捕获异常,记录日志,并返回统一的格式给前端

img

img

REST framework定义的异常

  • APIException 所有异常的父类
  • ParseError 解析错误
  • AuthenticationFailed 认证失败
  • NotAuthenticated 尚未认证
  • PermissionDenied 权限决绝
  • NotFound 未找到
  • MethodNotAllowed 请求方式不支持
  • NotAcceptable 要获取的数据格式不支持
  • Throttled 超过限流次数
  • ValidationError 校验失败

也就是说,很多的没有在上面列出来的异常,就需要我们在自定义异常中自己处理了。

posted @ 2021-07-06 23:27  刘较瘦丫  阅读(216)  评论(0编辑  收藏  举报