[Django drf]视图层大总结 ViewSetMixin源码分析 路由系统 action装饰器

科技资讯 投稿 9300 0 评论

[Django drf]视图层大总结 ViewSetMixin源码分析 路由系统 action装饰器

目录
    九个视图子类
  • 视图集
    • 继承ModelViewSet类写五个接口
    • 继承 ReadOnlyModelView编写2个只读接口
  • ViewSetMixin源码分析
      查找as_view方法
  • setattr修改对象的属性
  • from rest_framework.viewsets包下的类
  • 视图层大总结
      任意命名视图类的方法
  • 如何选择视图类
  • 路由系统
      路由写法的三种情况
  • 路由类的使用
      使用步骤
    • 使用SimpleRouter(常用
    • 使用DefaultRouter
  • 注册路由的两种方式
      直接添加到urlpatterns列表
  • 使用路由分发include
  • 自动生成路由底层实现
  • action装饰器
      无法自动生成的路由
  • 不同action使用不同序列化类
  • 认证组件前戏
      登录接口
  • 九个视图子类

    # 两个视图基类 
    1.APIView       2.GenericAPIView
    APIView:       renderer_classes响应格式类 parser_classes请求解析类    跟数据库解耦合
    GenericAPIView:queryset数据集 serializer_class序列化类                跟数据库耦合
    
    # 5个视图扩展类 (提供方法
    ListModelMixin      -->  list      -->  查询所有
    RetrieveModelMixin  -->  retrieve  -->  查询一个
    CreateModelMixin    -->  create    -->  新增一个
    UpdateModelMixin    -->  update    -->  修改一个
    DestroyModelMixin   -->  destroy   -->  删除一个
    
    # 9个视图子类 
    继承关系公式: 视图子类 = n * 视图扩展类 + GenericAPIView 
    
    # 示例:
    ListAPIView     =  ListModelMixin     + GenericAPIView 
    RetrieveAPIView =  RetrieveModelMixin + GenericAPIView 
    CreateAPIView   =  CreateModelMixin   + GenericAPIView 
    ...
    RetrieveDestroyAPIView = RetrieveModelMixin + DestroyModelMixin + GenericAPIView 
    RetrieveUpdateDestroyAPIView = RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin + GenericAPIView
    
    '''
    总结:9个视图子类都继承GenericAPIView
    '''
    

    使用视图子类写五个接口:这里上一节讲过,所以不再赘述。

    ## 路由
    urlpatterns = [
        path('books/', views.BookView.as_view(,
        path('books/<int:pk>/', views.BookView.as_view(,
    ]
    
    # 视图类
    class BookView(ListCreateAPIView:  # 查询所有,新增一个
        queryset = Book.objects.all(
        serializer_class = BookSerializer
    
    
    class BookDetailView(RetrieveUpdateDestroyAPIView: # 新增一个,修改一个,删除一个
        queryset = Book.objects.all(
        serializer_class = BookSerializer
    

    可以通过继承不同的视图类实现。
    只要查询所有和删除一个,怎么写?

    因为必须先查出来,再修改或删除,所以没有这个组合。

    问题:
    1.有两个get请求对应一个CBV
    2.两个路由路径对应一个CBV

    视图集

    继承ModelViewSet类写五个接口

    # 路由
    urlpatterns = [
        path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'},
        path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'},
    ]
    
    # 视图类
    class BookView(ModelViewSet:  # 查询所有,新增一个
        queryset = Book.objects.all(
        serializer_class = BookSerializer
    

    查看ModelViewSet内部继承关系:

    create(list(update(retrieve(destroy(这些方法
    但是我们请求来了,还是会调用视图类中的get(post(put(delete(这些方法呀。
    比如一个扩展子类:ListAPIView

    ModelViewSet内部居然没有写,这是怎么回事?
    这是因为ModelViewSet继承的最后一个类GenericViewSet,这是一个魔法类,他重写了as_view。

    现在需要这样写:

      对于books/这个路由:
      get请求 --执行--> list方法
      post请求 --执行--> create方法

    • books/<int:pk>/这个路由:
      get请求 --执行--> retrieve方法
      put请求 --执行--> updata方法
      delete请求 --执行--> destroy方法

    继承 ReadOnlyModelView编写2个只读接口

    # 路由
    urlpatterns = [
        path('books/', views.BookView.as_view({'get': 'list'},
        path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve'},
    ]
    
    # 视图类
    class BookView(ReadOnlyModelViewSet:  # 查询所有,新增一个
        queryset = Book.objects.all(
        serializer_class = BookSerializer
    

    查看 readonlymodelview内部继承关系:

    所以继承这个类就只能写两个只读接口:查询所有、查询一个

    ViewSetMixin源码分析

    ViewSetMixin是个魔法类,重写了as_view:

    查找as_view方法

    导致路由写法变了的原因是: ViewSetMixin
    当请求来了之后,会执行ViewSetMixin类中的as_view方法的返回值。

    # 请求来了,路由匹配成功---》get请求,匹配成功books,会执行  views.BookView.as_view({'get': 'list', 'post': 'create'}(------>读as_view【这个as_view是ViewSetMixin的as_view】
    

    从路由层开始分析,根据继承属性一个一个找as_view方法(从左往右)

    ListModelMixin,RetrieveModelMixinCreateModelMixinUpdateModelMixin DestroyModelMixin这些方法中都没有as_view。
    所以会进入到GenericViewSet:

    所以会先执行ViewSetMixin的as_view(:

    @classonlymethod
    def as_view(cls, actions=None, **initkwargs:
        # 如果没有传actions,直接抛异常,路由写法变了后,as_view中不传字典,直接报错
        if not actions:
            raise TypeError("The `actions` argument must be provided when "
                            "calling `.as_view(` on a ViewSet. For example "
                            "`.as_view({'get': 'list'}`"
    	# 。其他代码不用看
        def view(request, *args, **kwargs:
            self = cls(**initkwargs
            if 'get' in actions and 'head' not in actions:
                actions['head'] = actions['get']
            self.action_map = actions
            for method, action in actions.items(:
                handler = getattr(self, action
                setattr(self, method, handler
    
            return self.dispatch(request, *args, **kwargs
        # 去除了csrf校验
        return csrf_exempt(view
    

    如果不给actions传参数,直接抛出异常。
    也就是不给as_view(传字典,就会抛出异常。
    as_view执行完后会返回内层函数view:(这里执行的view是去除了csrf校验的

    # 路由匹配成功执行views.BookView.as_view({'get': 'list', 'post': 'create'}(----》本质执
    行ViewSetMixin----》as_view----》内的view(---》代码贴过来
        def view(request, *args, **kwargs:
                #actions 是传入的字典--->{'get': 'list', 'post': 'create'}
                self.action_map = actions
                # 第一次循环:method:get,action:list
                # 第一次循环:method:post,action:create
                for method, action in actions.items(:
                    # 反射:去视图类中反射,action对应的方法,action第一次是list,去视图类中反射list方法
                    # handler就是视图类中的list方法
                    handler = getattr(self, action
                    # 反射修改:把method:get请求方法,handler:list
                    # 视图类的对象的get方法,变成了list
                    setattr(self, method, handler
    
                return self.dispatch(request, *args, **kwargs #dispatch是APIView的
            
    # 关于这里self.dipatch的说明
    self.dipatch是APIView的dispatch
    '''
    self.dipatch --进行--> 封装新request, 执行三大认证 --调用--> django view的dispatch
    '''
    # 关于反射的总结
    	反射得到的是我们继承的List create方法
    	反射修改对象的属性 比如将get方法修改为存放list方法
    	最后的dispatch作用是获取你写的CBV类中的get方法(此时get方法 --> list方法)。
    	魔法类可以修改对象中的属性所指向的方法。
            
     # 关于整体的总结:
    	-1 只要继承ViewSetMixin的视图类,路由写法就变了(重写了as_veiw
        -2 变成需要需要传入字典映射方法:{'get': 'list', 'post': 'create'}
        	-只要传入actions,以后访问get就是访问list,访问post,就是访问create
        -3 其他执行跟之前一样 
        -4 以后视图类类中的方法名,可以任意命名,只要在路由中做好映射即可【重要】
        
    
    
    

    setattr修改对象的属性

    实际上ModelViewSet中根本没有get方法,我们通过setattr给CBV的对象新增了一个get属性,里面存放的就是list方法。
    而这个list方法又是通过反射在CBV的父类获取到的。所以就产生了这么神奇的效果。
    我们也可以在自己的CBV中重写list方法,这样getattr获取到的就是我们重写的list方法,然后get请求来了之后,也会执行我们重写的这个list。重写list之后,建议使用super方法调用一下父类的list,这样就可以在父类list的基础上,新增一些功能。

    # 示例:
    def token_auth(func:
        def inner(self, request, *args, **kwargs:
            token = request.query_params.get('token'
            token_exist = UserToken.objects.filter(token=token
            if token_exist:
                res = func(self, request, *args, **kwargs
                return res
            else:
                return Response({'code': 100, 'msg': '请先登录'}
    
        return inner
    
    class BookView(ModelViewSet:  # 针对 获取一个 修改一个 删除一个 接口添加token验证 
        queryset = Book.objects
        serializer_class = BookSerializer
        
    	@token_auth
        def retrieve(self, request, *args, **kwargs:  
            res = super(.retrieve(request, *args, **kwargs
            return res
        
    	@token_auth
        def update(self, request, *args, **kwargs:
            res = super(.update(request, *args, **kwargs
            return res
        
    	@token_auth
        def destroy(self, request, *args, **kwargs:
            res = super(.update(request, *args, **kwargs
            return res
    

    from rest_framework.viewsets包下的类

    
    # from rest_framework.viewsets下有这几个类:
    
    ViewSetMixin:魔法类,重写了as_view,只要继承他,以后路由写法变成了映射方法
    ModelViewSet: 5个视图扩展类 + ViewSetMixin(魔法类 + GenericAPIView
    ReadOnlyModelViewSet: 2个视图扩展类 + ViewSetMixin(魔法类 + GenericAPIView   只读的两个
    ViewSet:ViewSetMixin(魔法类  + APIView
    GenericViewSet:ViewSetMixin(魔法类 + GenericAPIView
    
    # 重点
    	以后,你想继承APIView,但是想变路由写法【视图类中方法名任意命名】,要继承ViewSet
        以后,你想继承GenericAPIView,但是想变路由写法【视图类中方法名任意命名】,要继承GenericViewSet
        
    # 总结
    只要想变路由,就要继承ViewSetMixin,但是ViewSetMixin不是CBV视图类,他没有list,create等方法,所以要配合APIView, GenericAPIView一起使用,所以会出现ViewSet,GenerucViewSet,帮助我们继承好了。
    ViewSet:       ViewSetMixin(魔法类  + APIView
    GenericViewSet:ViewSetMixin(魔法类  + GenericAPIView
    

    视图层大总结

    # 1. 两个视图基类
    	-APIView,GenericAPIView
    # 2. 5个视图扩展类,不是视图类,必须配合GenericAPIView
    
    # 3. 9个视图子类,是视图类,只需要继承其中某一个即可
    
    # 4. 视图集 
    	-ModelViewSet:路由写法变了,只需要写两行,5个接口都有了
        -ReadOnlyModelViewSet:路由写法变了,只需要写两行,2个只读接口都有了
        -ViewSetMixin:不是视图类,魔法,重写了as_view,路由写法变了,变成映射了
        	views.BookView.as_view({'get': 'list', 'post': 'create'}
        -ViewSet:ViewSetMixin+ APIView
    	-GenericViewSet:ViewSetMixin+ GenericAPIView
    
        
        
    # 举例子:发送短信接口,视图类叫SendView,方法叫send_sms,路由配置变了
    	get--->send_sms
    	class SendView(ViewSet:
            def send_sms(self,request:
                
    

    任意命名视图类的方法

    映射关系就行。

    如何选择视图类

      为什么要使用APIview?

      其不跟数据库打交道:继承ViewSet
      ViewSet = 魔法类 + APIView
      因为APIView不需要配置queryset和序列化类
      继承GenericViewSet会查数据库,这是一种资源的浪费。
      所以跟数据库打交道:继承GenericViewSet
      GenericViewSet = 魔法类 + GenericAPIview

    • 9个视图子类 + 魔法类
      因为通常我们对一个数据库资源比如:user
      对于这些数据资源,我们不一定会提供全部接口,很可能只会写其中的几个接口。

    路由系统

    路由写法的三种情况

    # drf 由于继承ViewSetMinxin类,路由写法变了
    	-原生+drf,以后的路由写法,可能会有如下情况(三种情况
        	-path('books/', views.BookView.as_view(  
                  # 原生django写法
            -path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'}
                  # 魔法类路由写法
            -自动生成 ---> 还有扩展
    

    路由类的使用

    使用路由类是为了自动生成路由。

    # drf提供了两个路由类,继承ModelViewSet后,路由可以自动生成
                  
    # 使用步骤:
        # 第一步:导入路由类
        # 第二步,实例化得到对象(两个类,一般使用SimpleRouter)
        # 第三步:注册:router.register('books', views.BookView, 'books'
        # 第四步:在urlpatterns中注册,两种方式
            -urlpatterns += router.urls
            -include:path('/api/v1/', include(router.urls  方式多一些
                                
    # 底层实现:自动生成路由就
           -本质是自动做映射,能够自动成的前提是,视图类中要有 5个方法的某要给或多个
               get--->list
               get---->retrieve
               put---->update
               post---->create
               delete---->destory
           -ModelViewSet,ReadOnlyModelViewSet可以自动生成
                  
           -9个试图子类+配合ViewSetMixin   才可以自动生成
           -GenericAPIView+5个试图扩展类+配合ViewSetMixin   才能自动生成
    

    使用步骤

    第一步:导入路由类 使用simplerouter 就生成两个路由 使用DefaultRouter -->生成的路由更多

    使用SimpleRouter(常用

    from rest_framework.routers import SimpleRouter
    
    router =  SimpleRouter(
    router.register('books', views.BookView, 'books'
    
    urlpatterns = [
        path('admin/', admin.site.urls,
    ]
    urlpatterns += router.urls
    

    关于router.register:

    第二个参数:该路由地址对应的视图类
    第三个参数:相当于是一个路由别名

    register( 方法有两个强制参数:
    
      prefix - 用于此组路由的URL前缀。
    • viewset - 处理请求的viewset类。
      base_name - 用于创建的URL名称的基本名称。如果不设置该参数,将根据视图集的queryset属性(如果有)来自动生成基本名称。
    • 注意,如果视图集不包括queryset属性,那么在注册视图集时必须设置base_name

    SimpleRouter会生成两个接口:

    books/另一个是books/pk/

    使用DefaultRouter

    访问根,可以看到有哪些地址:

    注册路由的两种方式

    直接添加到urlpatterns列表

    from rest_framework.routers import SimpleRouter, DefaultRouter
    
    router = DefaultRouter(
    router.register('books', views.BookView, 'books'
    # router.register('api/v1/books', views.BookView, 'books' 
    
    urlpatterns = [
       path('admin/', admin.site.urls,
    ]
    
    urlpatterns += router.urls  # 在这里添加
    
    # router.urls也是一个列表:
    [<URLPattern '^books/$' [name='books-list']>, <URLPattern '^books/(?P<pk>[^/.]+/$' [name='books-detail']>]
    

    使用路由分发include

    from rest_framework.routers import SimpleRouter
    
    router = SimpleRouter(
    router.register('books', views.BookView, 'books'
    
    urlpatterns = [
        path('admin/', admin.site.urls,
        path('api/v1/', include(router.urls
    ]
    

    自动生成路由底层实现

    # 自动生成路由底层实现
           -本质是自动做映射,能够自动生成的前提是,视图类中要有 5个方法的某个或多个
               get--->list
               get---->retrieve
               put---->update
               post---->create
               delete---->destory
                  
           - ModelViewSet,ReadOnlyModelViewSet 可以自动生成
           - 9个试图子类 + 配合ViewSetMixin   可以自动生成
           - GenericAPIView + 5个试图扩展类+配合ViewSetMixin 可以自动生成
    

    什么时候可以自动生成路由?
    前提: 有list,create...等方法 有ViewSetMixin魔法类

    所以如下这个组合用的多:
    9个试图子类 + 配合ViewSetMixin
    9个视图子类提供listcreate...方法 ViewSetMixin反射进行对象属性替换,使得get对应list 。

    action装饰器

    原路由:send/
    装饰器新增的路由:send/方法名/

    # action 写在视图类的方法上,可以自动生成路由
    
    # 使用步骤
    	- 1 写在视图类方法上
        class SendView(ViewSet:
            # methods指定请求方法,可以传多个
            # detail:只能传True和False
            	-False,不带id的路径:send/send_sms/
                -True,带id的路径:send/2/send_sms/
            # url_path:生成send后路径的名字,默认以方法名命名 
            # url_name:别名,反向解析使用,了解即可
            @action(methods=['POST'], detail=False
            def send_sms(self, request:
                
                
     # 以后看到的drf路由写法
    	后期,都是自动生成,一般不在urlpatterns 加入路由了
        
     # 补充:
    	-1 不同请求方式可以使用不同序列化类
        -2 不同action使用不同序列化类
    class SendView(GenericViewSet:
        queryset = None
        serializer_class = '序列化类'
    
        def get_serializer(self, *args, **kwargs:
            if self.action=='lqz':
                return '某个序列化类'
            else:
                return '另一个序列化列'
        @action(methods=['GET'], detail=True
        def send_sms(self, request,pk:
            print(pk
            # 手机号,从哪去,假设get请求,携带了参数
            phone = request.query_params.get('phone'
            print('发送成功,%s' % phone
            return Response({'code': 100, 'msg': '发送成功'}
    
        @action(methods=['GET'], detail=True
        def lqz(self,request:  # get
            # 序列化类
            pass
    
        @action(methods=['GET'], detail=True
        def login(self,request:  # get
            # 序列化类
            pass
    

    无法自动生成的路由

    我们知道路由自动生成,是实现了请求(get和类中方法(list的对应。
    如果我们在类中写listcreate,...,updata之外的方法呢?
    还能自动生成这些方法的路由吗?

    路由怎么写?

    自动生成路由,只能映射到list,create...,但是我们需要执行send_sms,并且区分开原来的list方法,所以需要加drf提供的装饰器:
    加上这个装饰之后,会新增一个路由send/send_sms/

    不同action使用不同序列化类

    class SendView(GenericViewSet:
        queryset = None
        serializer_class = '序列化类'
    
        def get_serializer(self, *args, **kwargs:
            if self.action=='lqz':
                return '某个序列化类'
            else:
                return '另一个序列化列'
        @action(methods=['GET'], detail=True
        def send_sms(self, request,pk:
            print(pk
            # 手机号,从哪去,假设get请求,携带了参数
            phone = request.query_params.get('phone'
            print('发送成功,%s' % phone
            return Response({'code': 100, 'msg': '发送成功'}
    
        @action(methods=['GET'], detail=True
        def lqz(self,request:  # get
            # 序列化类
            pass
    
        @action(methods=['GET'], detail=True
        def login(self,request:  # get
            # 序列化类
            pass
    

    如何实现不同的方法,使用不同的序列化类?

    在ViewSetMixin:

    查看self.action:

    认证组件前戏

    登录接口

    # 访问某个接口,需要登陆后才能访问
    
    # 第一步:写个登录功能,用户表
    	-User表
        -UserToken表:存储用户登录状态 [这个表可以没有,如果没有,把字段直接写在User表上也可以]
    

    随机字符串可以放在user表,也可以放在usertoken表里。

    用户删掉掉了之后,用户token没有存在的必要。所以可以使用级联删除。

    user属性。(反向查询表名小写

    登录接口是不需要使用序列化类的。
    使用uuid模块生成随机字符串。

    时间戳怎么重复?不同机器可能出现同一时间戳,及不同机器同一时间登录。

    updata_or_create方法:
    根据user去查,如果能查到,就把default里面的token给放进去。也就是如果有token就更新,如果没有就创建。

    登录接口:

    #### 表模型
    
    class User(models.Model:
        username = models.CharField(max_length=32
        password = models.CharField(max_length=32
    
    
    class UserToken(models.Model:  # 跟User是一对一
        token = models.CharField(max_length=32
        user = models.OneToOneField(to='User', on_delete=models.CASCADE, null=True
        # user :反向,表名小写,所有有user字段
    
    ### 路由
    router.register('user', views.UserView, 'user'  # /api/v1/user/login     post 请求
    
    # 视图类
    ####  登录接口  自动生成路由+由于登录功能,不用序列化,继承ViewSet
    from .models import User, UserToken
    import uuid
    
    
    class UserView(ViewSet:
        @action(methods=['POST'], detail=False
        def login(self, request:
            username = request.data.get('username'
            password = request.data.get('password'
            user = User.objects.filter(username=username, password=password.first(
            if user:
                # 用户存在,登录成功
                # 生成一个随机字符串--uuid
                token = str(uuid.uuid4(  # 生成一个永不重复的随机字符串
                # 在userToken表中存储一下:1 从来没有登录过,插入一条,   2 登录过,修改记录
                # 如果有就修改,如果没有就新增  (if 自己写
                # kwargs 传入的东西查找,能找到,使用defaults的更新,否则新增一条
                UserToken.objects.update_or_create(user=user, defaults={'token': token}
                return Response({'code': '100', 'msg': '登录成功', 'token': token}
            else:
                return Response({'code': '101', 'msg': '用户名或密码错误'}
    

    编程笔记 » [Django drf]视图层大总结 ViewSetMixin源码分析 路由系统 action装饰器

    赞同 (55) or 分享 (0)
    游客 发表我的评论   换个身份
    取消评论

    表情
    (0)个小伙伴在吐槽