文章目录
- 一、路由组件
- REST framework提供了两个router
- action装饰器
- 二、认证组件(Authentication)
- 三、权限组件(Permissions)
- 内置权限类
- 四、频率组件(Throttling)
- 五、权限组件源码分析
- 六、认证组件源码分析
一、路由组件
对于
视图集ViewSetMixin
,我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息。
REST framework提供了两个router
- SimpleRouter
- DefaultRouter
也就是通过路由组件帮助我们自动生成路由,
它会根据URL及请求匹配对应的视图方法,而这些方法则是来自视图集
,如果我们需要自定义方法来处理请求的话,后续可以搭配action装饰器实现。
SimpleRouter为每个URL添加一个斜杠后缀,可以在初始化的时候提供trailing_slash参数,并设置为False
创建router对象,并注册视图集
'导入'from rest_framework.routers import SimpleRouter,DefaultRouter'导入的模块不是继承就是实例化'router = SimpleRouter() 或者DefaultRouter() # 创建对象'''DefaultRouter会生成一个根路径/的配置项DefaultRouter生成的每个配置项后都可以跟上.json,直接返回json数据还可以显示注册过的路由以及美化的页面SimpleRouter和DefaultRouter用法一致,功能几乎一样''''注册路径,可以注册多个'router.register('publish',views.PublishView) # 注册路由,并选择视图函数'注册:第一个参数是路径,第二个参数为视图类,第三个参数起别名用得少所有这里没用'urlpatterns = []'把生成的路由添加到urlpatterns路由列表中,有两种方式:'# 将生成的路由加入到Django需要调用的路由列表内'方式一:直接添加+='urlpatterns += router.urls'方式二:直接添加到urlpatterns里面使用include'from django.urls import path,includeurlpatterns = [path('',include(router.urls))]
def register(self, prefix, viewset, basename=None):
注册参数说明:
- prefix:路由的前缀
- viewset:视图集(内部必须继承了ViewSetMixin类)
- basename:路由的别名
上序代码会生成如下路由:
path('publish/',views.PublishView.as_view()),path('publish/<int:pk>',views.PublishView.as_view()),''''^publish/$' [name='publish-list^publish/(?P<pk>[^/.]+)/$' [name='publish-detail']'''
每个路由对应的接口功能
publish/:get请求的话则会执行视图集里面的list方法publish/:post请求的话则会执行视图集里面的create方法publish/<int:pk>/:get请求执行视图集里面的retrieve方法publish/<int:pk>/:put请求执行视图集里面的update方法publish/<int:pk>/:delete请求执行视图集里面的destroy方法
实际展示
视图类
from rest_framework.viewsets import ModelViewSet'必须是继承了ViewSetMixin类的视图类才能使用这种自动生成路由的方法'class PublishView(ModelViewSet):queryset = models.Publish.objects.all()serializer_class = PublishSerializer
路由
from rest_framework.routers import SimpleRouterrouter = SimpleRouter()router.register('publish',views.PublishView)urlpatterns = []urlpatterns += router.urls
此时上面代码就可以自动生成路由了,完成了增、删、改、查(一条或多条数据)的接口了,但是不包括在视图集里面自定义的方法。
如果要给我们自定义的方法也加上路由,那么则需要使用action装饰器来声明。
SimpleRouter生成URL的方式
DefaultRouter生成URL的方式
action装饰器
在视图集中,如果想要让Router自动帮助我们为自定义的方法生成路由信息,需要使用
rest_framework.decorators.action
装饰器。
使用action装饰器的方法名会作为路由的后缀,例如:
/publish/使用action装饰器的方法名/
并且action装饰器会接收两个参数:
- methods:声明该action对应的请求方式,列表传递:
['get','post']
表示该路由get请求与post请求。 - detail:声明该action的路由是否与单一资源
(就是单条数据)
对应,如果需要的话设置True。
/publish/<int:pk>/使用action装饰器的方法名/True:表示路径格式是:/publish/pk/action方法名/False:表示路径格式是:/publish/action方法名/
- url_path:控制生成的/使用action装饰器的方法名/后面的路径是什么,如果不写默认以方法名
- url_name:别名,用于反向解析
实际案例
视图类
from rest_framework.viewsets import ModelViewSetfrom rest_framework.response import Responsefrom rest_framework.decorators import actionclass PublishView(ModelViewSet):queryset = models.Publish.objects.all()serializer_class = PublishSerializer@action(methods=['post'], detail=False)def login(self,request):return Response({'message':'登录成功'})@action(methods=['get'],detail=True)def test(self,request,pk):return Response({'message':'测试成功'})
效果展示
此时可以从浏览器上看到自动生成的路由
api/v1/ ^publish/$ [name='publish-list']api/v1/ ^publish/login/$ [name='publish-login']api/v1/ ^publish/(?P<pk>[^/.]+)/$ [name='publish-detail']api/v1/ ^publish/(?P<pk>[^/.]+)/test/$ [name='publish-test']
它使用的是
正则来匹配
,中间使用了有名分组,以关键字:pk=xx
的形式传给视图。
二、认证组件(Authentication)
在DRF中,我们要进行登录认证的话需要使用DRF内部的认证组件,为什么不用auth组件?因为DRF重新封装了request方法,而当我们使用原来
reqeust.use
r时,则会调用_authenticate
方法,然后在调用我们编写的认证类里面的authenticate
方法进行认证。
开启认证有两种方法:
- 局部开启
- 全局开启
全局开启方式:在settings.py文件里面进行DRF配置
REST_FRAMEWORK={"DEFAULT_AUTHENTICATION_CLASSES":["app01.auth.LoginAuth"]# value值是我们认证组件类在当前项目的路径}
局部开启:在视图类里面指定认证类
'导入登录认证类'from .auth import LoginAuthauthentication_classes = [test.LoginAuth, ]# 认证可以有多个,在调用_authenticate方法时会将列表里面类实例成对象,然后执行对象里面的authenticate方法。# 执行顺序从前至后,如果某个对象返回了正确结果则后面对象不会执行。'如果全局认证了但是又不想让某一个不认证,需要再视图类里面写入authenticate_classes = []清空即可'
登录认证类
from rest_framework.authentication import BaseAuthenticationfrom . import modelsfrom rest_framework.exceptions import AuthenticationFailed'''通过认证类完成,使用步骤1 写一个认证类,继承BaseAuthentication2 重写authenticate方法,在内部做认证3 如果认证通过,返回2个值4 认证不通过抛AuthenticationFailed异常5 只要返回了两个值,在后续的request.user 就是当前登录用户'''class LoginAuth(BaseAuthentication):def authenticate(self, request): 重写authenticate方法,做内部认证# 完成对用户的校验# 当次请求的requesttoken = request.query_params.get('token') 获取tokenuser_token = models.UserToken.objects.filter(token=token).first()if user_token:user = user_token.user # 如果通过认证返回两个值,不通过则抛异常'return后,在后续的视图类的方法中,就可以通过reqeust.user取到当前登录的用户''如果不按照这个规则来写,后续视图类的request.user是取不到当前登录用户'return user,user_token'质押返回了两个值,在后续的request.user就是当前登录用户'else:raise AuthenticationFailed('您没有登录!')
三、权限组件(Permissions)
权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。
- 在执行视图的dispatch()方法前,会先进行视图访问权限的判断
- 在通过get_object()获取具体对象时,会进行模型对象访问权限的判断
开启权限有两种方法:
- 局部开启
- 全局开启
全局开启方式:在settings.py文件里面进行DRF配置
REST_FRAMEWORK = {'DEFAULT_PERMISSION_CLASSES': ['app01.permission.CommonPermission',],}'注意,不要再配置文件乱导入不使用的东西,否则没有加载过就使用会直接报错。'
局部开启:在视图类里面指定权限类
'导入权限类'from .permission import CommonPermissionpermission_classes = [CommonPermission]'如果全局设置权限了但是又不想让某一个不设置权限,需要再视图类里面写入permission_classes = []清空即可'
补充
如需自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部-'.has_permission(self,request,view)'是否可以访问视图,view表示当前视图对象-'.has_object_permission(self,request,view,obj)'是否可以访问数据对象,view表示当前视图,obj为数据对象
权限类的定义
'models.py定义的用户表'class User(models.Model):username = models.CharField(max_length=64)password = models.CharField(max_length=64)user_type = models.IntegerField(default=1, choices=((1, '普通用户'), (2, '管理员'), (3, '超级管理员')))from rest_framework.permissions import BasePermission'''权限类其实跟认证类一样只需要继承类修改方法使用步骤1 写一个类,继承BasePermission2 重写has_permission方法(也可以不继承BasePermission,但是重写这个方法,一样有用,因为python是鸭子类型)3 在方法中校验用户是否有权限(request.user就是当前登录用户)4 如果有权限,就返回True,没有权限,返回False5 self.message 是显示给前端的中文提示信息''''我这里创建是一个只有超级管理员才能访问其他人都不能访问的例子'class CommonPermission(BasePermission): # 随意定义一个类继承BasePermissiondef has_permission(self, request, view): # 重写has_permission方法'''到了这里,正常步骤应该登录完了,可以使用reqeust.user来查看当前登录用户然后拿到用户后判断用户是否有权限,去用户表中查看用户类型字段user_type,然后根据类型判断是否有权限-ACL:访问控制列表-rbac:公司内部系统,基于角色的访问控制-abac:rbac升级版,加了属性认证'''try: # 这里进行异常捕捉,因为有可能有匿名用户所以需要进行捕捉user_type = request.user.user_type print(f"用户:{request.user.get_user_type_display()}")print(f"编号:{user_type}")if user_type == 3: # 获取用户的管理员信息,如果有权限返回True,否则Falsereturn Trueelse:'报错信息渲染,这里的reqeust.user.get_user_type_display()是返回这个字段的choice对应的文字'self.message = f"你是{request.user.get_user_type_display()}。没有权限进行操作!"return Falseexcept Exception as e: # 当是匿名用户或者说是没有登录的用户,走这里# print(request.path) '这里的判断是为了给登录和注册页进行解除设限'if 'login' in request.path: # 获取return Trueelif 'register' in request.path:return True'渲染报错信息渲染'self.message = "您没有登录,没有权限进行操作!"return False
内置权限类
当我们通过
create_user
或者createsuperuser
也可以具备权限,DRF提供了以下权限类。
from rest_framework.permissions import AllowAny,IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly- AllowAny 允许所有用户- IsAuthenticated 仅通过认证的用户- IsAdminUser 仅管理员用户- IsAuthenticatedOrReadOnly 以及登录认证的用户可以对数据进行增删改查操作,没有登录认证的只能查看数据
我们可以根据哪个视图的需要来进行引入。
全局使用:这就表示所有视图都只能通过认证的用户访问
REST_FRAMEWORK = {'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated',],}
如果需要某几个视图不想使用这种效果的话,则可以进行局部禁用
permission_classes = [] # 设置空即可,或者指定别的权限类
如果未指定的话,则使用DRF默认的
REST_FRAMEWORK = {'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.AllowAny',]}
四、频率组件(Throttling)
我们也可以称其为:限流。其主要作用:
- 可以对接口访问的次数进行限制,以减轻服务器压力
- 一般用于付费购买次数,投票等场景使用。
开启频率有两种方法:
- 局部开启
- 全局开启
全局开启方式:在settings.py文件里面进行DRF配置
REST_FRAMEWORK = {'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.CommonThrottle'],}'注意,不要再配置文件乱导入不使用的东西,否则没有加载过就使用会直接报错。'
局部开启:在视图类里面指定权限类
'导入权限类'from .throttling import CommonThrottlethrottle_classes = [CommonThrottle]'如果全局设置权限了但是又不想让某一个不设置权限,需要再视图类里面写入permission_classes = []清空即可'
频率类的定义
from rest_framework.throttling import BaseThrottle,SimpleRateThrottlefrom . import models'''使用步骤:1.写一个类继承SimleRateThrottle2.重写get_cache_key方法,返回一个唯一的字符串,会以这个字符串做频率限制3.有两种写法3.1 写一个类属性 scope='随意命名',使用这个方法的必须要与配置文件中定义使用配置文件中写REST_FRAMEWORK = {'DEFAULT_THROTTLE_RATES': {'key要跟类中的scop定义的名字对应': '5/m',},# 表示该接口一分钟只能被访问5次,key值就是我们频率类里面的scope属性值这个字典后面value值里面的/前面的是访问次数,后面是超过次数后等待的时间,可以根据下面参数来进行选择即可。{'s':1,'m':60,'h':3600,'d':86400} # 都是使用秒来计算我们也可以直接使用英文单词来:秒:second,分:minute,时:hour,天:day}3.2 写一个类属性 rate = '5/minute' 这种写法就无需配合配置文件中写了,相当于直接整合了''''这里我以一个登录用户id进行限制一分钟访问三次'class CommonThrottle(SimpleRateThrottle):rate = '3/minute' # second minute hour daydef get_cache_key(self, request, view):user = request.userobj = models.User.objects.filter(username=user.username).first()'''返回什么,频率就可以做什么限制比如也可以通过IP地址进行限制,ip = request.META.get('REMOTE_ADDR')'''return obj.pk # ip
在settings中使用匿名用户、普通用户的限制
REST_FRAMEWORK = {'DEFAULT_THROTTLE_CLASSES': ('rest_framework.throttling.AnonRateThrottle', # 匿名用户限流'rest_framework.throttling.UserRateThrottle', # 普通用户限流),'DEFAULT_THROTTLE_RATES': {'anon': '3/m', # 设置匿名用户每分钟只能访问3次'user': '5/m', # 设置普通用户每分钟只能访问5次}}
五、权限组件源码分析
'''我们知道在DRF的APIView中有三大认证是在执行视图类的方法之前就执行了三大认证,也就是APIView中的dispatch方法中的self.initial。在之前几篇DRF博客中,以及介绍解读过APIView的源码了所以这里我就不重头开始解读了,直接从dispatch里面开始了'''def dispatch(self, request, *args, **kwargs):try:'就是在这里执行了三大认证:认证、权限、频率'self.initial(request, *args, **kwargs)'执行视图类的方法开始'if request.method.lower() in self.http_method_names:handler = getattr(self, request.method.lower(),self.http_method_not_allowed)else:handler = self.http_method_not_allowedresponse = handler(request, *args, **kwargs)'执行视图类的方法结束'except Exception as exc: 全局异常捕捉response = self.handle_exception(exc)self.response = self.finalize_response(request, response, *args, **kwargs)return self.response'''我们找到了这个dispatch里面执行三大认证的地方后,我们知道,这个self是视图类的对象,而视图类是继承了APIView这个类所以我们先去APIView这个类中找一下有没有这个initial的方法,结果是有的'''找到了APIView里的initialdef initial(self, request, *args, **kwargs):self.perform_authentication(request) # 认证组件self.check_permissions(request) # 权限组件self.check_throttles(request) # 频率组件'因为我们这里是分析权限组件的,所以在这里只看一下权限的,跟上面一样,这个self还是视图类的对象'在APIView里面找到了check_permissions方法# 检查请求是否具有所需的权限def check_permissions(self, request):# 遍历视图或视图集的权限类列表'这里的self还是视图类的对象,然后我们又得去APIView里面找看有没有get_permissions方法''''def get_permissions(self):return [permission() for permission in self.permission_classes]在这个get_permissions里面直接返回了一个列表生成式,我们得去看看这个self.permission_class是什么permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES从上面点击查看到的可以得知,self.permission_class就是咱们配置在视图类中的列表,里面是一个个权限类,并且没加括号执行。然后我们可以知道它每次从我们自己的视图类的permission_class列表中拿出一个值出来所以这里的返回值就是每次拿到一个值然后加括号执行,其实就是我们配置的一个个权限类的对象'''所以这里self.get_permissions()就是拿到一个个我们配置的权限类的对象的列表,然后在这里又进行循环一个个拿出for permission in self.get_permissions(): 所以这里的permission就是我们写的一个个的权限类的对象# 调用每个权限类的 has_permission 方法进行权限检查'''这里看到has_permission,这个就是为什么我们需要再权限类中重写这个方法,因为在这里它在调用它并且传入了两个参数,这个request就是APIView包装后的新的request,而这个self它还是我们视图类的对象因为还是在APIView中。这就是为什么我们在权限类中重写has_permission方法括号中加入了一个view参数,就是接收了这个的self也就是视图类对象。所以我们这个权限类中的view其实就可以直接拿到view.request,也可以直接拿到view.action等参数'''if not permission.has_permission(request, self):'而这里在权限类中has_permission方法如果返回值是True就不会执行这里的代码,反之就会走这里'# 如果权限检查失败,调用 permission_denied 方法,拒绝访问self.permission_denied('这里的self还是视图类的对象,当执行了这个permission_denied就是说明没有权限''''这里注意一个点,如果配置了多个权限类,如果第一个权限类就没过,就不会执行后面的权限类,所以得把想要过的放最前面'''request,# 获取权限类中可能定义的消息和代码message=getattr(permission, 'message', None),'这里的message和code都是从我们权限类的对象中反射过来的'code=getattr(permission, 'code', None))'在这里我们还可以看看,这个permission_denied,它还是在APIView中'def permission_denied(self, request, message=None, code=None):if request.authenticators and not request.successful_authenticator:raise exceptions.NotAuthenticated()raise exceptions.PermissionDenied(detail=message, code=code)'''可以看到它执行抛了异常,因为抛了异常,最后会被APIView中的全局异常捕捉到并且把上面的message和code都传入过去message会放在响应体中,code会放在响应状态码中'''
总结:
权限类源码的执行流程:1.先去视图类继承的APIView中的dispatch方法中执行self.initial方法2.然后又执行APIView的initial方法中的self.check_permissions(request)3.然后从里面取出配置在视图类中的权限类,实例化得到对象。4.然后for循环一个个执行对象的has_permission方法(重写的方法),如果返回False就直接结束,不再往下执行权限就认证通过'小细节'1.为什么写一个类继承BasePermission,重写has_permission方法-也可以不继承这个类,只重写这个方法也可以(因为python是鸭子类型)2.权限类中 self.message 会返回给前端3.局部配置:放在视图类中:permission_classes = [权限类名]4.全局配置,配置在配置文件中也可以,那么视图类中就没有-如果视图类上不配做权限类,permission_classes = [],会默认使用配置文件的api_settings.DEFAULT_PERMISSION_CLASSES-执行的顺序:优先使用项目配置文件----->其次使用drf内配置文件
六、认证组件源码分析
'因为也是继承了APIView,从上面权限组件源码分析中我们知道在它的dispatch方法中有一个initial方法''里面也有一个认证组件的'def initial(self, request, *args, **kwargs):self.perform_authentication(request) # 认证组件self.check_permissions(request) # 权限组件self.check_throttles(request) # 频率组件'因为我们这里是分析只分析认证组件的,然后self还是视图类的对象'在APIView里面找到了perform_authentication方法def perform_authentication(self, request):'''可以看到里面只写了一句代码,因为这里还是APIView所以这里的self还是视图类的,这里的reqeust就是APIView包装的新的request。'''request.user'这里可以看到request.user,可以会觉得它是一个属性,但是其实它是一个方法,是伪装成数据属性的''这样我们就得回到APIView源码中找了,因为我们这个新的reqeust是在Request中,所以我们得去看它了'我们直接在视图中导入这个然后找到这个user位置:from rest_framework.request import Request@propertydef user(self): 这里的self就是Request的对象,因为它并没有继承任何一个类。所以就是它自己的对象if not hasattr(self, '_user'):'然后这里是看有没有这个_user,但是我们确实是没有,所以是False,但是又if了not所以变成True'with wrap_attributeerrors(): # 上下文管理器self._authenticate() 所以会执行这一句return self._user'sel_authenticate()这里的self就是Resquest对象,所以我们找找Resquest中有没有这个方法'def _authenticate(self):'''这里循环跟权限组件那块差不多self.authenticators,就是我们配置在视图类中认证类的一个个对象,放在列表中所以这里循环出来的authenticator就是一个个我们配置的认证类对象'''for authenticator in self.authenticators:此处对上面几句的解释'''这个self还是Resquest对象,我们在Resquest中找authenticators,而它不是一个方法,而是一个属性,所以我们直接按住Ctrl+鼠标左键点击跳转即可def __init__(self, request, parsers=None, authenticators=None,negotiator=None, parser_context=None):self.authenticators = authenticators or ()从我们点击跳转找到的我们可以看到这个是Resquest类实例化后得到的对象的初始化方法,会把对象的authenticators传入给了,self.authenticators对象,所以我们在类实例化调用这个初始化方法是什么时候实例化类的,我们得去APIView中找找,在我们之前博客中有写过APIView的源码分析中知道,在它的dispatch中request = self.initialize_request(request, *args, **kwargs)这里实例化得到对象了,所以我们去看看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)从上面代码中可以看到返回了authenticators=self.get_authenticators(),这里的self又是谁?现在是在APIView中,所以这里的self是视图类的对象然后直接跳转到这个self.get_authenticators()def get_authenticators(self):return [auth() for auth in self.authentication_classes]self.self.authentication_classes,self就是视图类的对象,而authentication_classes就是在视图类中配置的认证类中的一个个对象,它是一个列表'''try:'''因为上面我们知道了authenticator就是我们配置的一个个认证类,而这里的authenticate就是我们在认证类中重写的方法,因为在这里调用它了。而这里的self就还是APIView包装的新的Resquest对象,所以在认证类中重写这个方法后面接收了它然后执行认证类的这个方法,后返回值就赋值给了user_auth_tuple这里在我们认证类的这个方法中返回了两个值:第一个是当前登录用户,第二个的token,只走这一个认证类,后面的不再走了也可以返回None,这样就会继续执行下一个认证类'''user_auth_tuple = authenticator.authenticate(self)'''如果这里没有重写这个方法就抛出异常,它并没有捕获AuthenticationFailed,而是APIException但是AuthenticationFailed是继承了APIException。所以我们在认证类抛出异常使用AuthenticationFailed'''except exceptions.APIException:self._not_authenticated()raise'这里如果user_auth_tuple不是空的话,就走这里,就是正常返回了认证类方法的两个值'if user_auth_tuple is not None:self._authenticator = authenticator'''这里的self就还是Resquest的对象,并且使用了解压赋值,然后把user_auth_tuple的两个值分别赋值这就是为什么后续在视图类中,reqeust.user就是当前登录的用户,request.auth就是另一个参数当认证类没有返回值,那么这里就是空,如果有第二个认知类那么就会进行第二次循环,如果有返回值就解压赋值完毕后,直接结束了。'''self.user, self.auth = user_auth_tuple'''所以这里我们可以知道,在视图类中配置认证类是可以配置多个的,如果第一个认知类有返回值就不会再执行第二个认证类,如果没有返回值,则会执行第二个认证类。而返回的两个值,第一个给了reqeust.user,第二个给了reqeust.auth,这样在后续视图类中就可以取到'''returnself._not_authenticated()
总结
1.在认证类中,重写authenticate方法(不重写就无法调用)2.为什么校验失败抛异常,因为我们写了AuthenticationFailed,又AuthenticationFailed是继承了APIException所以能捕获3.通过认证就会返回两个值或者None(如果没有返回值就会执行下一个认证类,或者没有在后续视图类中request.user和reqeust.auth就拿不到值)4.视图类上局部配置和配置文件全局配置跟权限类的一模一样。