[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
内置过滤器
# 查询所有才需要过滤(根据条件过滤),排序(按某个规则排序)
# 内置过滤类使用,在视图类中配置
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=达 # 出版社中或名字中有 达 就能查询出来
自定义过滤器
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',),
}
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()),
]
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 以后只要视图中执行出了错误,就会捕获异常,记录日志,并返回统一的格式给前端
REST framework定义的异常
-
APIException 所有异常的父类
-
ParseError 解析错误
-
AuthenticationFailed 认证失败
-
NotAuthenticated 尚未认证
-
PermissionDenied 权限决绝
-
NotFound 未找到
-
MethodNotAllowed 请求方式不支持
-
NotAcceptable 要获取的数据格式不支持
-
Throttled 超过限流次数
-
ValidationError 校验失败