drf04--路由、认证与权限

昨日回顾

# 1.序列化类钩子函数执行位置的源码
    -反序列化的校验:字段自己的校验,局部钩子校验,全局钩子校验
    
# 2.drf的请求
    -Request类的对象
    -data:put,post提交的数据都在里面
    -query_params:get请求提交的数据
    -用起来跟django的request一样用
        
    -指定可以解析的编码格式
    	-默认3种
        -局部配置和全局配置
        -优先级:视图类---》项目配置文件---》drf配置问题
        
    -json模块是否执行反序列化bytes格式
        -Python3.6以后才支持;之前的版本需要先encode编码成字符串,再load()转json格式
        
    -视图类的方法中:self.request,就是当次请求的request
	-好处:在视图类中,定义 其他不是请求方式的 函数时,
        虽然没有request参数,但是也可以通过self.request取到当前请求的request

# 3.drf的响应
    -Response类的对象
    -data:返回的数据,字符串,列表,字典
    -status:http响应的状态码
    -header:http的响应头
    -指定响应的格式
    	-默认两种:json,浏览器
        -局部配置和全局配置
        
    -原生django中添加数据到响应头
    	res = HttpResponse('ok')
        res['name'] = 'egon'  # 往响应头中写入 name=egon 的键值对
        
# 4.drf的视图类
    -两个视图基类
        -APIView:继承了django的View
            renderer_classes :配置的响应类
    	    parser_classes :配置的解析类
    	    authentication_classes :配置的认证类
    	    throttle_classes :配置的频率类
    	    permission_classes :配置的权限类
            
        -GenericAPIView:继承了APIViews
            queryset: 当前表所有的数据
            serializer_class: 序列化类
            get_queryset(): 获取所有数据
            get_serializer(): 获取序列化的类
            get_object: 获取单个,路由分出pk
                -源码解析
                queryset = self.filter_queryset(self.get_queryset()) # 返回所有数据queryset对象
                # lookup_url_kwarg就是pk,路由中有名分组分出来的pk
                lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
                # {pk:4}  4 浏览器地址中要查询的id号http://127.0.0.1:8000/books6/4/
                filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
                # 根据pk=4去queryset中get单个对象
                obj = get_object_or_404(queryset, **filter_kwargs)
                self.check_object_permissions(self.request, obj)
                return obj
            
            # 目前了解
            filter_backends:过滤类
            pagination_class:分页类
            
    -5个视图扩展类(不是视图类)
        ListModelMixin: 继承object,有个list方法,原理写在get中获取所有
        CreateModelMixin: 有个create方法,原来的新增post中的代码
        RetrieveModelMixin: 有个retrieve
        UpdateModelMixin: 有个update
        DestroyModelMixin: 有个destroy
        
  	-9个视图子类
        ListAPIView: GenericAPIView+ListModelMixin+get方法
        CreateAPIView: GenericAPIView+CreateModelMixin+post方法
        ListCreateAPIView: GenericAPIView+ListModelMixin+CreateModelMixin+post方法+get方法
        RetrieveAPIView: 
        UpdateAPIView:
        DestroyAPIView: 
        RetrieveUpdateAPIView:
        RetrieveDestroyAPIView: 
        RetrieveUpdateDestroyAPIView:
    
 	-视图集
        -ViewSetMixin:继承object,重写了as_view,继承它及它的子类,路由写法就变了  .as_view({'get':'方法'})
        -ViewSet: ViewSetMixin + APIView
        -GenericViewSet: ViewSetMixin + GenericAPIView
        -ReadOnlyModelViewSet: ViewSetMixin+GenericAPIView+ListModelMixin+RetrieveModelMixin
        -ModelViewSet: ViewSetMixin+GenericAPIView+视图扩展类

今日内容

1.drf之路由

1.1 基本使用

# 自动生成路由:

# 第一步:导入
from rest_framework.routers import DefaultRouter, SimpleRouter

# 第二步:实例化得到对象
router = SimpleRouter()  # 使用较多
# router = DefaultRouter()  # 它自动生成的路由多一条,多个根路径 会显示所有可以访问的路径 
			    # rest_framework自带的根路径页面,故需要进行rest_framewor设置注册

# 第三步:注册路由
# 第一个参数是路径(后面不用再跟 '/',会自动生成)
# 第二个参数是视图类(必须继承ViewSet及子类,且不用.as_view)
# 第三个参数是别名(反向解析使用,最好同名写上) 
# 注意:在你的view接口若是没有queryset,不指定basename 会报错 
router.register('books', views.BookView, basename='books')
router.register('publish', views.PublishView, basename='publish')
print(router.urls)

urlpatterns = [
    path('admin/', admin.site.urls),

    # 第四步:把自动生成的路由添加到总路由中(方式二:路由分发方式)  使用较多
    path('', include(router.urls)),
]

# 第四步:把自动生成的路由添加到总路由中(方式一:列表相加)
# urlpatterns += router.urls

1.2 actions

# 针对自动生成路由时,action参数怎么指定设置
    -使用action装饰器
 
# 导入action装饰器
from rest_framework.decorators import action


# 1.action装饰器的基本使用
class TestView(ViewSet): 
    '''
    action参数
        methods: ['get','post']  做一个映射,就是哪些http的请求方式映射到当前方法  
        detail:True或False       表示多条还是单条 是否带查询条件(默认pk)
            -False:test/login/
            -True: test/pk/login   # 注意pk的位置,是在两者之间
            
        url_path:访问的路径,可以不写  默认以方法名作为路径 (记得要加上 url.py的路由前缀)	
        url_name: 别名 (该方法的别名,用作反向解析)
    '''
    
    # 此处表示:通过get请求访问这个路径'test/login' 就能触发login的执行
    # @action(methods=['GET'],detail=False,url_path='login',url_name='login')
    @action(methods=['GET'],detail=True)
    def login(self, request,pk):
        print(pk)
        return Response('登录成功')
    
    
# 2.action装饰器的拓展使用
# 利用action属性,再重写get_serializer_class() 从而可以给不同的方法,使用不同的序列化类
    -原理:使用action装饰器时,当前视图类的对象中就有action属性,可以通过该属性判断 当前请求会执行视图类中的哪个方法

class TestView(GenericViewSet): 
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get_serializer_class(self):
        if self.action=='login':
            return PublishSerializer
        else:
            return self.serializer_class
	
    @action(methods=['GET'],detail=True)
    def login(self, request,pk):
        print(self.action)
        self.get_serializer()   # 此时该方法,使用的不是BookSerializer类,而是PublishSerializer类
        return Response('登录成功')
    
    
# 路由
router.register('test', views.TestView, basename='testview')

urlpatterns = [
    path('', include(router.urls)),
]


# 3.action装饰器的拓展使用--给同一个视图view下,不同的接口,添加不同的类设置
    可用action装饰器 参数:6大类_classes=[对应的类]
    例:throttle_classes=[SMSThrottle]

    class MobileView(ViewSet):
        @action(methods=['GET'],detail=False,throttle_classes=[SMSThrottle])
        def send_sms(self, request):
            pass

# 注意:action的第三项,可以直接替代第二项了

2.drf之认证功能

判断用户是否登录
# 例:某个接口登录后才能访问,就需要使用认证功能

2.1 登录功能

# 如果全局使用了认证类,那么登录也需要认证了,故登录接口需要局部禁用掉

class LoginView(ViewSet):
    authentication_classes = []  # 局部配置:为空  表示不使用任何认证类  
    
    @action(methods=['POST'], detail=False)
    def login(self, request):
        # 1.获取前端传入的数据
        name = request.data.get('name')
        password = request.data.get('password')
        # 2.根据数据,在User表查询
        user=User.objects.filter(name=name,password=password).first()
        if user:
            # 3.生成一个随机字符串
            # 通过伪随机数生成一个重复概率极低的字符串
            token=str(uuid.uuid4())
            # 4.把随机字符串存入UserToken表,如果存在,就只更新token值,如果不存在,就新增数据
            # .update_or_create(defaults={更新的数据},查询条件)  # 更新或新增数据
            UserToken.objects.update_or_create(defaults={'token':token},user=user)  
            # UserToken.objects.create(token=token,user=user)  # create() 会一直新增数据
            return Response({'code':100,'msg':'登录成功','token':token})
        else:
            return Response({'code':101,'msg':'用户名或密码错误'})

2.2 认证类定义

# 自定义认证类,要继承BaseAuthentication,且重写authenticate
    # 如果认证通过,返回两个值(user, token),如果认证失败,抛认证失败的异常:APIException或者AuthenticationFailed
    
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken

class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 校验用户是否登录:看它有没有带token来
        token = request.query_params.get('token')  # 等于 token = request.GET.get('token')
        # 校验token是否合法(是不是我给的): 根据token去UserToken表中查询是否存在,如果存在,就是登陆了,放行
        user_token = UserToken.objects.filter(token=token).first()
        if user_token:
            return user_token.user, token  # 当前登录用户(传给了当前请求对象:request.user)和token值
        else:
            raise AuthenticationFailed('token不合法或没有传token')  # 认证异常

2.3 认证类使用

# 可以有多个认证,从左到右依次执行

# 局部使用(在视图类中加入)
# 认证类的局部配置
from app01.auth import LoginAuth
class PublishView(ModelViewSet):
    # 局部配置
    authentication_classes = [LoginAuth]
    # 局部禁用
    authentication_classes=[]
    

# 全局使用
# 认证类的全局配置
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.auth.LoginAuth",]
}

3.drf之权限功能

判断登录用户是否有相应的权限
# 例:某个接口需要超级用户的权限,就需要使用权限功能

注意:权限类一定是在认证类之后执行(根据源码执行顺序),那么走到权限类代码,就一定是认证通过了

3.1 权限类定义

# 继承BasePermission,重写has_permission,如果有权限,就返回True,没有权限就返回False
from rest_framework.permissions import BasePermission

class PermissionUser(BasePermission):
    # message = '你没有访问的权限!'
    def has_permission(self, request, view):
        # 如果有权限,返回True
        if request.user.user_type == 1:
            return True  # 超级用户允许访问
        else:
            self.message = '你没有访问的权限!'
            return False

# 权限认证失败,返回自定义的中文
    -在权限类中配置message属性即可(给对象,类都可以)

3.2 权限类使用

# 局部和全局使用

# 局部配置
from app01.auth import LoginAuth
class PublishView(ModelViewSet):
    # 权限类:publish的5个接口,必须超级用户才能访问
    permission_classes = [PermissionUser]
    
# 全局配置
REST_FRAMEWORK={
    "DEFAULT_PERMISSION_CLASSES":["app01.auth.PermissionUser",]
}

3.3 内置权限(了解)

# 演示一下内置权限的使用:IsAdminUser,控制是否对网站后台有权限的人
# 1 创建超级管理员
# 2 写一个测试视图类
from rest_framework.permissions import IsAdminUser
from rest_framework.authentication import SessionAuthentication
class TestView3(APIView): 
    authentication_classes=[SessionAuthentication]  # 默认的认证类
    permission_classes = [IsAdminUser]
     def get(self,request,*args,**kwargs):
        return Response('这是测试数据,超级管理员可以看')
# 3 超级用户登录到admin,再访问test3就有权限
# 4 正常的话,普通管理员,没有权限看(判断的是is_staff字段是否为True)

猴子补丁

# 程序运行的过程中,动态的替换对象的属性或方法

自定义封装response对象

# 自己封装一个CommonResponse对象,使用方法如下
return CoomonResponse('100','成功',boo_ser.data)
return CoomonResponse('101','验证失败',boo_ser.errors)


# 1. 自已2b版:太固定了很不好,没办法传原来response的其他参数,使用时也必须.get_dict()
from rest_framework.response import Response
class CommonResponse(Response):
    def __init__(self, status, msg, data):
        self.status = status
        self.msg = msg
        self.data = data
        super().__init__(self.get_dict)
     
    @property
    def get_dict(self):
        return {'status': self.status, 'msg': self.msg, 'data': self.data}
    
    
# 2.老师通用版
from rest_framework.response import Response
class APIResponse(Response):
    def __init__(self, code=100, msg='成功', data=None, status=None, headers=None, **kwargs):
        dic = {'code': code, 'msg': msg}
        if data:
            dic['data'] = data
        dic.update(kwargs)  # 可以灵活的添加,需要返回的键值对
        super().__init__(data=dic, status=status, headers=headers)
        
        
# 注意:因为ModelViewSet内部的各接口方法,返回的是rest_framework的response对象
故我们也可以将 重写方法的公共类(APIResponseView), 写在该文件里 

作业

# 1 图书,出版社,作者 每个的5个接口都写好
    -返回的格式都带 {code:100,msg:'成功',data:} 
    
# 解决方案1:
    # 1.自定义一个公共类(继承ModelViewSet),重写父类的方法(将源码拿出来),最后返回自定义的response对象
    class APIResponseView(ModelViewSet):
        def list(self, request, *args, **kwargs):
            return APIResponse(data=serializer.data)

        def create(self, request, *args, **kwargs):
            return APIResponse(data=serializer.data, status=status.HTTP_201_CREATED, headers=headers)

        def retrieve(self, request, *args, **kwargs):
            return APIResponse(data=serializer.data)

        def update(self, request, *args, **kwargs):
            return APIResponse(data=serializer.data)

        def destroy(self, request, *args, **kwargs):
            return APIResponse(status=status.HTTP_204_NO_CONTENT)
        
    # 2.各视图类接口就都继承公共类 (不直接继承ModelViewSet)
    class BookView(APIResponseView)
    class PublishView(APIResponseView):
    class AuthorView(APIResponseView):
        
# 解决方案2:
    # 1. 可以直接将公共类(APIResponseView),也封装到自定义封装response对象的文件中
    class APIResponse(Response):   
    class APIResponseView(ModelViewSet):
        
    # 2.各视图类接口就都继承公共类 (不直接继承ModelViewSet)
    class BookView(APIResponseView)
    class PublishView(APIResponseView):
    class AuthorView(APIResponseView):
    
    
# 2 写一个登录功能(action)
    # 为了防止每次都要输入用户名和密码,登录之后返回前端一个token,下次判断是否传token,就不用重新登录
    # 故 后端需要存储 token表

# 3 除了登录,所有接口必须登录才能访问----认证功能
    全局使用,局部禁用(login禁用)

# 4 作者5个接口,必须超级用户才能访问----权限功能

# 5 使用simplerouter自动生成路由

# 6 继承ModelViewSet,获取所有的,只获取前5条 (就是重写内部list方法)
# Create your views here.
class BookView(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer

    # 重写父类list() 只获取所有的前5条
    def list(self, request, *args, **kwargs):
        # 只获取所有的前5条
        queryset = self.filter_queryset(self.get_queryset())[:5]

        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 APIResponse(data=serializer.data)
posted @ 2021-12-07 00:18  Edmond辉仔  阅读(74)  评论(0编辑  收藏  举报