Django RestFramework常用组件笔记

知识点预览:

  • 注:本文适合有一定Django基础的同学shiyong
  • 注:本文主要用来复习restframework相关知识
  • 如果有什么重要的内容,我会补上去。

总体说明

  • 我们直接打开rest_framework.views文件源码,可以看到它包含一个类APIView,而这个类也基本包含了rest_framework所封装的组件了。也就是说我们的学习目标就是下面这些组件的功能、以及如何配置。

    ... ...
    class APIView(View):
    
        # The following policies may be set at either globally, or per-view.
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
        parser_classes = api_settings.DEFAULT_PARSER_CLASSES
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
        throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
        permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
        content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
        metadata_class = api_settings.DEFAULT_METADATA_CLASS
        versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
        ... ...
    
  • 实际上,上面展示的类库我并不建议从上往下看,我们列出这些类的功能:

    • renderer_classes渲染器
    • parser_classes解析器
    • authentication_classes认证器
    • throttle_classes限流器
    • permission_classes权限
    • content_negotiation_class依据contentType选择渲染器。
    • metadata_class元数据
    • versioning_class版本
  • 这些类的使用方法都可以由我们自行配置,例如authentication_classes认证器我们可以看到,他会去api_settings.DEFAULT_AUTHENTICATION_CLASSES找到默认配置。于是我们就可以为他添加默认配置,打开setting文件添加以下配置:

    REST_FRAMEWORK={
        'DEFAULT_AUTHENTICATION_CLASSES':['api2.utils.auth.FirstAuthtication',
                                          'api2.utils.auth.Authtication']
    }
    

    这样一来,就可以使用我们自己设置的权限认证器。具体逻辑需要在api2.utils.auth.FirstAuthtication去编写代码。
    所有的DEFAULT_XXX_....就都可在此处配置默认值。

认证、权限、限流组件

  • 刚开始,我们从认证、权限、限流开始学习。
    • authentication_classes认证器
    • permission_classes权限
    • throttle_classes限流器
  • authentication_classes认证器
    • BasicAuthentication是django rest framework为我们提供了最基本的认证类,该类中其中定义的两个方法authenticate和authenticate_header(认证失败返回的响应头),使用时候重写该两个方法进行认证
      class Authtication(BasicAuthentication):
          def authenticate(self,req):
              token=req._request.GET.get('token')
              token_obj=models.UserToken.objects.filter(token=token).first()
              if not token_obj:
                  raise exceptions.AuthticationFailed('用户认证失败')
              return (token_obj.user,token_obj)
      
          def authenticate_header(self,req):
              return 'Basic realm="api"'
      
      要启用全局配置就需要把他放进我上面提到的全局配置属性REST_FRAMEWORK={}rest_framework.authentication还提供了其他类
      BasicAuthentication  #基于浏览器进行认证,浏览器弹框
      SessionAuthentication #基于django的session进行认证
      RemoteUserAuthentication #基于django admin中的用户进行认证,这也是官网的示例
      TokenAuthentication #基于drf内部的token认证
      
  • permission_classes权限,使用方法和认证类几乎是一样的。
    • 继承BasePermission
      from rest_framework.permissions import BasePermission
      class MyPermission(BasePermission):
          message="必须是SSVIP才能访问!"
          def has_permission(self,request,view):
              if request.user.user_type!='ssvip':
                  return False
              return True
      
      class MyPermission2(BasePermission):
          def has_permission(self,request,view):
              if request.user.user_type=='ssvip':
                  return False
              return True
      
      如果已经使用了全局配置我们可以自定义CBV视图函数中的认证和权限了。
      class VIPOrderView(APIView):
          authentication_classes = [Authtication, ] #认证,不需要认证比如登录操作这里必须写[]
          permission_classes = [MyPermission2, ] #权限
          def get(self, req, *args, **kwargs):
              ret = {'code': 1000, 'msg': None, 'data': None}
              ret['data'] = {'vip资源': {'a': 1,
                                       'b': 2}}
              return JsonResponse(ret)
      
  • throttle_classes限流器,使用方法几乎和上面两种一样,仍是以列表形式在CBV视图函数中配置,或是在全局配置。打开rest_framework.throttling里面有个SimpleRateThrottle类,可以发现它读取了配置DEFAULT_THROTTLE_RATES。那么可发现在全局setting中可以配置默认访问频率规则。而DEFAULT_THROTTLE_CLASSES的配置类仅仅是返回了被限制用户的对象。
    • SimpleRateThrottle继承内置的限流类。
      # 未登录用户
      class UserThrottle(SimpleRateThrottle):
          scope = "user"
      
          def get_cache_key(self, request, view):
              return self.get_ident(request)
      
      # vip 用户
      class VipThrottle(SimpleRateThrottle):
          scope = "vip"
      
          def get_cache_key(self, request, view):
              return request.user.username
      
      然后我们可以在setting全局配置里面配置我们的访问频率、已经默认使用的规则:
      REST_FRAMEWORK={
         #'DEFAULT_AUTHENTICATION_CLASSES':['api2.utils.auth.FirstAuthtication',
                                           #'api2.utils.auth.Authtication'],
         #'DEFAULT_PERMISSION_CLASSES':['api2.utils.Permission.MyPermission',
                                       #'api2.utils.Permission.MyPermission2',],
         'DEFAULT_THROTTLE_CLASSES':['api2.utils.throttle.VisitThrottle'],
         'DEFAULT_THROTTLE_RATES':{
             "vip":"10/m",
             "user":"5/m",
         }
      }
      
      可以看到,vip用户10次每分钟,普通用户5次每分钟。我注释掉的内容就是之前配置的默认认证类和权限类。如果不想使用默认配置 我们就得去CBV函数中自行配置。例如,我希望配置vip用户规则throttle_classes = [VipThrottle,]
      class VIPOrderView(APIView):
          authentication_classes = [Authtication, ]
          permission_classes = [MyPermission2, ]
          throttle_classes = [VipThrottle,]
          def get(self, req, *args, **kwargs):
              ret = {'code': 1000, 'msg': None, 'data': None}
              ret['data'] = {'vip资源': {'a': 1,
                                       'b': 2}}
              return JsonResponse(ret)
      

版本、解析器

  • versioning_class 版本信息。也就是url中的参数信息获取。
    from rest_framework.versioning import URLPathVersioning
    class UserView(APIView):
        authentication_classes = []
        permission_classes = []
        versioning_class = URLPathVersioning  #ParamVersion也可以自己定义一个这样的函数,如下
        def get(self, req, *args, **kwargs):
            print(req.version)
            return HttpResponse('用戶列表')
    #也选择自己定义一个这样的函数,url后加?version=1.1
    class ParamVersion(object):
        def dermine_version(self,req, *args, **kwargs):
            version=req.query_params.get('version')
            return version
    
    然后我们就可以在url里面填写版本信息了。
    urlpatterns = [
    url(r'^(?P<version>[v1|v2]+)/users/$',views.UserView.as_view(),name='aaa'),
    ]
    
    仍然可以在全局配置'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning"。我们直接使用内置类,和上面不同的是我们根本没有继承,而是直接使用了框架为我们提供的。
    • url反向生成:
      class UserView(APIView):
          authentication_classes = []
          permission_classes = []
          versioning_class = URLPathVersioning  # ParamVersion
      
          def get(self, req, *args, **kwargs):
              print(req.version) #打印参数url参数
              ul = req.versioning_scheme.reverse(viewname='aaa', request=req) #url中找到name属性的路径。
              print(ul) #打印反向生成的url
              return HttpResponse('用戶列表')
      
    • 它还提供了其他内置版本类:
      class BaseVersioning
      class AcceptHeaderVersioning(BaseVersioning)
      class NamespaceVersioning(BaseVersioning)
      class HostNameVersioning(BaseVersioning)
      class QueryParameterVersioning(BaseVersioning)
      
  • parser_classes解析器,用来自动解析数据例如json格式数据,当然也包括其他格式。也内置了很多类。我们看个例子
    from rest_framework.parsers import JSONParser,FormParser
    class parserView(APIView):
        parser_classes = [JSONParser,FormParser,]  #使用解析器
        def post(self,req,*args,**kwargs):
            print(req.data) #不再使用json.loads(req.body)了
            return HttpResponse('parserView')
    
    上面使用了两个解析器JSONParser可以解析application/json请求头。FormParser可以解析application/x-www-form-urlencoded请求头。
    • 同样,框架包含了很多我们可以直接使用的内置类。
      class MultiPartParser(BaseParser) #支撑'multipart/form-data'
      class FileUploadParser(BaseParser) #media_type = '*/*'支持全部类型。
      
      我们需要文件上传时就要在CBV写上:parser_classes = [FileUploadParser,]

序列化、用序列化验证post请求

  • 引入部分,了解一下就好rest_framework.serializers.Serializer
    from rest_framework import serializers
    class RoleSerializer(serializers.Serializer):
        id=serializers.IntegerField()
        title = serializers.CharField()
        
    import json
    class rolesView(APIView):
        authentication_classes = []
        permission_classes = []
        versioning_class = URLPathVersioning  # ParamVersion
        def get(self, req, *args, **kwargs):
            roles = models.Role.objects.all()
            ser = RoleSerializer(instance=roles, many=True)
            ret = json.dumps(ser.data,ensure_ascii=False)
            return HttpResponse(ret)
    
    #不使用框架时候的样子
    import json
    class rolesView_0(APIView):
        def get(self, req, *args, **kwargs):
            roles = models.Role.objects.all().values('id', 'title')
            roles = list(roles)
            ret = json.dumps(roles)
            return HttpResponse(ret) #没有解码。
    
  • 定制序列化内容,仍然是了解一下就好,因为框架帮我们做好了封装。下面是一个用户对应一个组,对应多个角色。
    class UserinfoSerializer(serializers.Serializer):
        xxx=serializers.CharField(source="user_type")
        username=serializers.CharField()
        password=serializers.CharField()
        gp=serializers.CharField(source="group.title")
        rls=serializers.SerializerMethodField(source="roles.all") #自行定制!
    
        def get_rls(self,row):
            role_obj_list=row.roles.all()
            ret=[]
            for i in role_obj_list:
                ret.append({'id':i.id,'title':i.title})
            return ret
    
    class Userinfo(APIView):
        def get(self, req, *args, **kwargs):
            users=models.UserInfo.objects.all()
            ser=UserinfoSerializer(instance=users,many=True)
            ret=json.dumps(ser.data,ensure_ascii=False)
            return HttpResponse(ret)
    
  • Model序列化可以继承ModelSerializer非常重要 ,但是代码量非常少~,用于大多情况。也可以选择定制。
    class modUserinfoSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.UserInfo
            fields = "__all__"
            #fields=['id','username','password','rls']
            #depth=1 #外鍵層數,可以展开。
            #rls=serializers.SerializerMethodField()
            def get_rls(self,row): #也可以定制
                pass
    
    class Userinfo(APIView):
        def get(self, req, *args, **kwargs):
            users = models.UserInfo.objects.all()
            ser = modUserinfoSerializer(instance=users, many=True)
            ret = json.dumps(ser.data, ensure_ascii=False)
            return HttpResponse(ret)
    
    • 还有点小操作,在返回的json中,反向生成url,科用于分页。
      class modUserinfoSerializer(serializers.ModelSerializer):
      group=serializers.HyperlinkedIdentityField(view_name='gp')
          class Meta:
              model = models.UserInfo
              fields = "__all__"
      
      class Userinfo(APIView):
          def get(self, req, *args, **kwargs):
              users = models.UserInfo.objects.all()
              ser = modUserinfoSerializer(instance=users, many=True,context={'request':req}) #必须
              ret = json.dumps(ser.data, ensure_ascii=False)
              return HttpResponse(ret)
      
  • 用序列化验证用户请求
    class GroupSerializer(serializers.Serializer):
        title=serializers.CharField(error_messages={'required':'title不能为空'})
        name=serializers.CharField(error_messages={'required':'name不能为空'})
    
    class groupView(APIView):
        def post(self,req,*args,**kwargs):
            ser=GroupSerializer(data=req.data)
            if ser.is_valid():
                print(ser.validated_data)
            else:
                print(ser.errors)
            return HttpResponse('提交数据')
    

分页

  • PageNumberPagination分页类的基本使用(了解即可)
    #分页需要序列化
    from rest_framework.response import Response #渲染器
    from rest_framework.pagination import PageNumberPagination #分页类
    
    class PagerSerialiser(serializers.ModelSerializer):
        class Meta:
            model=models.Role
            fields = "__all__"
    
    class pager1View(APIView):
        def get(self, req, *args, **kwargs):
            #获取所有数据
            roles=models.Role.objects.all()
            #创建分页对象、在数据库中获取分页数据
            pa=PageNumberPagination()
            pager_roles=pa.paginate_queryset(queryset=roles,request=req)
            #序列化分页后的数据
            ser=PagerSerialiser(instance=pager_roles,many=True)
            return Response(ser.data)
    
    还没完我们打开这个类的源码,发现他有个page_size = api_settings.PAGE_SIZE属性。即默认分页大小,我们这时就要去setting里配置全局分页PAGE_SIZE大小。我给每页显示两条数据。就是看最下面那行就可以,前几行是之前配置的其他默认类:
    REST_FRAMEWORK={
        'DEFAULT_AUTHENTICATION_CLASSES':['api2.utils.auth.FirstAuthtication',
                                          'api2.utils.auth.Authtication'],
        'DEFAULT_PERMISSION_CLASSES':['api2.utils.Permission.MyPermission',
                                      'api2.utils.Permission.MyPermission2',],
        'DEFAULT_THROTTLE_CLASSES':['api2.utils.throttle.UserThrottle',
                                    'api2.utils.throttle.VipThrottle',],
        'DEFAULT_THROTTLE_RATES':{
            "vip":"10/m",
            "user":"5/m",
        },
        "PAGE_SIZE":2,
    }
    
  • PageNumberPagination类的继承-自定制显示内容,实际上我们打开这个类就可以看到那些我们需要自行定制的属性。我举个简单例子。
    class MyPageNumberPagination(PageNumberPagination): #继承定制分页
        page_size=4
        page_size_query_param = 'size'
        max_page_size = 5
        page_query_param = 'page'
    class pager1View(APIView):
        def get(self, req, *args, **kwargs):
    	... ...
            pa=MyPageNumberPagination() #使用分页对象
    	... ...
    
    还可以自动生成上一页下一页的url地址到response中。
    class pager1View(APIView):
        def get(self, req, *args, **kwargs):
    	... ...
            #return Response(ser.data)
            return pa.get_paginated_response(ser.data)
    
    效果
    {
        "count": 10,
        "next": "http://127.0.0.1:8000/api/v1/pager1/?page=3",
        "previous": "http://127.0.0.1:8000/api/v1/pager1/",
        "results": [
            {
                "id": 5,
                "title": "无业"
            },
            {
                "id": 6,
                "title": "阿毛"
            }
        ]
    }
    
    这样的分类策略只返回页数。非常有规律的分成了N也,要哪也数据写哪页就好。而下面在看一种分页策略。
  • LimitOffsetPagination继承及使用,这个就比较常规了,就像我们数据库里面分页一样,他添加了offset参数。没事的话看看一看源码,里面都有配置。
    class MyPageNumberPagination2(LimitOffsetPagination):
        default_limit=2
        limit_query_param='limit'
        offset_query_param='offset'
        max_limit = 5
    
    class pager1View(APIView):
        def get(self, req, *args, **kwargs):
            #获取所有数据
            roles=models.Role.objects.all()
            #创建分页对象、在数据库中获取分页数据
            pa=MyPageNumberPagination2()
            pager_roles=pa.paginate_queryset(queryset=roles,request=req)
            #序列化分页后的数据
            ser=PagerSerialiser(instance=pager_roles,many=True)
            #return Response(ser.data)
            return pa.get_paginated_response(ser.data)
    
    效果:
    {
        "count": 10,
        "next": "http://127.0.0.1:8000/api/v1/pager1/?limit=2&offset=4",
        "previous": "http://127.0.0.1:8000/api/v1/pager1/?limit=2",
        "results": [
            {
                "id": 3,
                "title": "警察"
            },
            {
                "id": 4,
                "title": "小姐"
            }
        ]
    }
    
    这样的分页策略仍然是有规律的,如果我们需要只有在本页才能看到上一页和下一页路径则可以使用以下策略。
  • CursorPagination根据游标分页。继承这个类,看一下源码,配置他的可配置项。
    class MyPageNumberPagination3(CursorPagination):
        cursor_query_param = 'cursor'
        page_size = 2
        ordering = 'id'
    
    class pager1View(APIView):
        def get(self, req, *args, **kwargs):
    	... ...
            pa=MyPageNumberPagination3()
    	... ...
            return pa.get_paginated_response(ser.data)
    
    
    看一下效果,达成了游标位置无法判断效果。只有在本页才能看到下一页和上一页数据,性能最高:
    {
        "next": "http://127.0.0.1:8000/api/v1/pager1/?cursor=cD00",
        "previous": "http://127.0.0.1:8000/api/v1/pager1/?cursor=cj0xJnA9Mw%3D%3D",
        "results": [
            {
                "id": 3,
                "title": "警察"
            },
            {
                "id": 4,
                "title": "小姐"
            }
        ]
    }
    

视图

  • GenericAPIView(了解)如果你看了之前的内容,到了这一步会发现,视图函数这么简单就能完成。之间我们继承了APIView,如今我们继承GenericAPIView,打开源码发现它帮我们继承了APIView并丰富了相当多的功能,我们只需要配置下一下就能完成复杂的操作。看个查询、分页、序列化后的接口实例:
    from rest_framework.generics import GenericAPIView
    class view1View(GenericAPIView):
        queryset = models.Role.objects.all()    #查询全部数据
        serializer_class = PagerSerialiser      #序列化类
        pagination_class = PageNumberPagination #分页类
        def get(self, req, *args, **kwargs):
            roles=self.get_queryset()
            pager_roles=self.paginate_queryset(roles)
            ser=self.get_serializer(instance=pager_roles,many=True)
            return Response(ser.data)
    
    只需要配置一下分页、序列化所用类,GenericAPIView就能帮我们完成操作。但是他还可以被继承。
  • GenericViewSet他继承了ViewSetMixin,和generics.GenericAPIView,因为继承了ViewSetMixin所以需要给as_view函数参入参数,帮我们把请求METHOD转化为类的方法。
    urlpatterns = [
    url(r'^(?P<version>[v1|v2]+)/v1/$',views.view1View.as_view({'get':'list'}),name='v1',),
    ]  #url.py :{'get':'list'}将GET方法映射到list()上。
    
    class view1View(GenericViewSet):
        queryset = models.Role.objects.all()    #查询全部数据
        serializer_class = PagerSerialiser      #序列化类
        pagination_class = PageNumberPagination #分页类
        def list(self, req, *args, **kwargs):
            roles=self.get_queryset()
            pager_roles=self.paginate_queryset(roles)
            ser=self.get_serializer(instance=pager_roles,many=True)
            return Response(ser.data)
    
  • 重要:有了以上的基础之后我们视图函数就要飞升了。
    class view1View(ListModelMixin,GenericViewSet,CreateModelMixin):
        queryset = models.Role.objects.all()    #查询全部数据
        serializer_class = PagerSerialiser      #序列化类
        pagination_class = PageNumberPagination #分页类
    
    url(r'^(?P<version>[v1|v2]+)/v1/$',views.view1View.as_view({'get':'list','post':'create'}),name='v1',),
    url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)$',views.view1View.as_view({'get':'retrieve','delete':'destroy','put':'update','patch':'partial_update'}),name='v1',),
    
    
    • 总结视图函数如何写?
      • 增删改查:ModelViewSet
        增删:CreateModelMixin,DestroyModelMixin,GenericViewSet
        逻辑复杂:GenericViewSetAPIView

路由、渲染

  • 路由rest_framework.routers类,自动帮我们生成路由。

    from django.conf.urls import url,include
    from rest_framework import routers
    router=routers.DefaultRouter()
    router.register(r'xxxx',views.view1View)
    router.register(r'rt',views.view2View) #假如这个视图函数继承了ModelViewSet,即增删改查。
    
    urlpatterns = [
        url(r'^',include(router.urls)),
    ]
    

    自动帮我们生成以下路由,http://127.0.0.1:8000/api/rt/分页查询全部,http://127.0.0.1:8000/api/rt/1/查询ID为1的数据。http://127.0.0.1:8000/api/rt.jsonhttp://127.0.0.1:8000/api/rt/?format=json以json形式返回:

    ^api/ ^ ^xxxx/$ [name='role-list']
    ^api/ ^ ^xxxx\.(?P<format>[a-z0-9]+)/?$ [name='role-list']
    #直接生成4个路由!
    ^api/ ^ ^rt/$ [name='role-list']
    ^api/ ^ ^rt\.(?P<format>[a-z0-9]+)/?$ [name='role-list']
    ^api/ ^ ^rt/(?P<pk>[^/.]+)/$ [name='role-detail']
    ^api/ ^ ^rt/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='role-detail']
    
  • JSONRenderer渲染器rest_framework.renderers类,这个类包含了很多渲染器。我们直接访问http://127.0.0.1:8000/api/rt/?format=json会调用这个渲染器。实际上,我们网页打开http://127.0.0.1:8000/api/rt/并不是json数据,而是一个漂亮接口交互界面调用了渲染器BrowsableAPIRenderer的渲染结果。
    在这里插入图片描述
    要是想不渲染直接返回JSON需要在视图函数中配置:

    class view2View(ModelViewSet):
    
        renderer_classes = [JSONRenderer,] #仅仅使用JSON渲染器
        #之前还学了
        authentication_classes = []     #不使用认证
        permission_classes = []         #不使用权限
        versioning_class = []           #不版本
        throttle_classes = []           #不使用限流
        
        queryset = models.Role.objects.all()    #查询全部数据
        serializer_class = PagerSerialiser      #序列化类
        pagination_class = PageNumberPagination #分页类
    

    当然也可以在全局配置DEFAULT_RENDERER_CLASSES来配配置全局渲染器。

Reference:
https://www.cnblogs.com/honey-badger/p/9740207.html
https://www.bbsmax.com/A/rV57PkAXdP/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值