DRF十大组件使用

DRF 十大组件总结:

  • 认证
  • 权限
  • 节流
  • 版本
  • 解析器
  • 序列化
  • 分页
  • 视图
  • 路由
  • 渲染器

认证

  • 作用:判断用户是否登录,之后登录后的用户才有权限访问接口

  • 自定义认证类继承BaseAuthentication

  • 认证(判断用户是否登录):

    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    from drf.models import Token
    class MyBaseAuthentication(BaseAuthentication):
        ###自定义认证类
        def authenticate(self, request):
            #在这里完成认证的逻辑
            token = request._request.GET.get('token')
            #token_obj ->Token
            token_obj = Token.objects.filter(token=token).first()
            if token_obj:
                #说明用户是登录的用户
                #######   user          auth
                return (token_obj.user,token)
            else:
                raise AuthenticationFailed('用户未登录,没有权限访问')
    
    #局部使用
    class BooksView(APIView):
        #http://127.0.0.1:8000/api/books/?token=xxxxxxxx
        # #设置认证类
        authentication_classes = [MyBaseAuthentication,]
        def get(self,request,*args,**kwargs):
            pass
            request.user:对应的是认证成功后返回元祖中的第一个参数
            request.auth:对应的是认证成功后返回元祖中的第二个参数
    
    
    #全局使用
    #REST_FRAMEWORK:配置Restframework的全局设置
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES':['tools.authentication.MyBaseAuthentication']
        # 设置匿名用户
        'UNAUTHENTICATED_USER':lambda :'匿名用户小偷',
    'UNAUTHENTICATED_TOKEN':lambda :'123456skcaslbvkasvcvacaj',
    
    }
    
    
    
    #注意:如果设置了全局的认证类,那么想要在某些视图中不进行认证,设置如下:
    class BooksView(APIView):
        #http://127.0.0.1:8000/api/books/?token=xxxxxxxx
        #如果设置了全局的认证,不想让某些视图执行认证
        #将authentication_classes设置为空列表
        authentication_classes = []
        def get(self,request,*args,**kwargs):
            pass
    

权限

  • 作用:根据用户的身份类型,限定某些接口的访问权限自定义继承自(BasePermission)
    class MyBasePermission(BasePermission):
        #has_permission必须实现,在这里写权限逻辑
        message = '用户没有权限访问该接口'
        
        def has_permission(self, request, view):
            #通过这个方法判断用户是否有权限访问接口
            if request.user.usertype == 2:
                #返回True,表示有权限访问接口
                return True
            #返回Flase,表示没有权限访问接口
            return False
    
    #局部使用(在视图内使用)
    class BooksView(APIView):
        ##设置权限类
        permission_classes = [MyBasePermission]
    
    #全局使用
    #REST_FRAMEWORK:配置Restframework的全局设置
    REST_FRAMEWORK = {
        'DEFAULT_PERMISSION_CLASSES':['tools.permissions.MyBasePermission'],
    }
    
    #注意:如果设置了全局的权限判断,在某些视图中如果不需要进行权限判断
    class BooksView(APIView):
        ##设置权限类
        permission_classes = []
    

节流

  • 节流作用:节流的作用,限定用户的访问频率(基于ip、基于用户)
    #自定义节流类(方式一:BaseThrottle)
    class MyVisitThrottle(BaseThrottle):
        
        def __init__(self):
            self.history = None
        
        def allow_request(self, request, view):
            
            #在这里完成节流的逻辑
            #获取用户的主机ip(基于ip进行节流)
            # ip_addr = request.META.get('REMOTE_ADDR')
            ip_addr = self.get_ident(request)
            # print(ip_addr)
            
            ##基于用户(id或者用户的其他唯一表示)
            # ip_addr = request.user.id
    
            #获取用户访问的时间
            ctime = time.time()
            if ip_addr not in VISIT_HISTORY:
                #用户是第一次访问,返回True表示允许用户访问
                VISIT_HISTORY[ip_addr] = [ctime]
                return True
            ##获取用户的访问记录
            history = VISIT_HISTORY[ip_addr]
            self.history = history
            ##怎么判断用户,是否还有访问次数
            ## (是否访问达到了最大频率)
            #12:04:00
            #                 12:02:30      12:03:00
            while history and history[-1] < ctime-60:
                history.pop()
        
            print(len(history))
            
            if len(history) <= 10:
                history.insert(0,ctime)
                return True
    
            return False
    
        def wait(self):
            #提示用户需要等待的时间
            """
                Optionally, return a recommended number of seconds to wait before
                the next request.
                """
            return 60 - (time.time() - self.history[-1])
    
    #自定义节流类(方式二:SimpleRateThrottle)
    class MySimpleRateThrottle(SimpleRateThrottle):
    scope = 'throttle'
        def get_cache_key(self, request, view):
            return self.get_ident(request)
            #return request.user.id
        
    #需要在settings文件中添加如下参数
    'DEFAULT_THROTTLE_RATES':{
        'throttle':'10/m'
    }
    
    #局部使用
    class BooksView(APIView):
        ###用户每一分钟只能够访问10次  10/60
        ###设置节流类,限定用户的访问频率
        throttle_classes = [MySimpleRateThrottle]
    
    #全局使用
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES':['tools.throttle.MySimpleRateThrottle'],
    }
    
    #注意:如果设置了全局的限制频率,在某些视图中如果想限制用户的请求频率,
    class BooksView(APIView):
        throttle_classes = []
    
    ##源码
    
    #用户未登录每分钟允许访问10次,登录后每分钟允许访问30次
    class MySimpleRateThrottle(SimpleRateThrottle):
       scope = 'unlogin'
       # scope = 'logined'
       def get_cache_key(self, request, view):
           return self.get_ident(request)
               #return request.user.id
               
        def get_rate_with_request(self,request):
           #自定义get_rate_with_request:根据request,判断用户是否登录
           #重置scope属性,获取登录或者未登录的请求频率
           self.scope = 'logined' if request.user else 'unlogin'
               return self.THROTTLE_RATES[self.scope]
        
        def allow_request(self, request, view):
           #在我们判断用户请求是否超出最大访问频率之前,
           #更新num_requests, duration两个字段
           self.rate = self.get_rate_with_request(request)
           self.num_requests, self.duration = self.parse_rate(self.rate)
           return super(MySimpleRateThrottle,self).allow_request(request, view)
    
        'DEFAULT_THROTTLE_RATES':{
            'unlogin': '10/m',
                'logined': '30/m'
        }
    

版本

  • 作用:标记url地址
    1.强行要求开发在url地址是那个拼接参数
    自定义版本(BaseVersioning)
    from rest_framework.versioning import BaseVersioning,URLPathVersioning
    #自定义版本类(方法一)
    class MyVersing(BaseVersioning):
        def determine_version(self, request, *args, **kwargs):
            #通过这个方法获取版本号,并返回
            # http://127.0.0.1:8000/?version=v1
            version = request._request.GET.get('version')
            return version
    
    #局部使用
    class BooksView(APIView):
        ##设置版本类
        versioning_class = MyVersing
    
    #方式二:使用URLPathVersioning(推荐)
    #局部使用
    class BooksView(APIView):
        ##设置版本类
        versioning_class = URLPathVersioning
        
    #注意路由的格式:
    urlpatterns = [
                   url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
                   url(r'^(?P<version>[v1|v2]+)/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail')
                   ]
        
    ##方式三QueryParameterVersioning
    class BooksView(APIView):
        ##设置版本类
        versioning_class = QueryParameterVersioning
    
    
    ##全局配置版本
    REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
            'DEFAULT_VERSION':'v1',
           'ALLOWED_VERSIONS':['v1','v3'],
           'VERSION_PARAM':'version'
    
    }
    

解析器

  • 作用post请求如果参数以json数据传递,我们要想获取参数,就需要使用到解析器
    如果请求头中Content-Type:application/x-www-form-urlencoded
    name=li&age=23
    request._request.POST.get('')
    
    #json数据格式
    #如果请求头中Content-Type:application/json
    {"name":"li","age":23}
     data = json.loads(request.body.decode('utf-8'))
     name = data.get('name')
     age = data.get('age')
     print(name,age)
    
    #    使用DRF解析器
    #from rest_framework.parsers import FormParser,JSONParser
    #FormParser:Content-Type:application/x-www-form-urlencoded
    #JSONParser:Content-Type:application/json
    
    #局部使用解析器
    class BooksView(APIView):
    ###设置解析器
        parser_classes = [FormParser,JSONParser]
        def post(self,request,*args,**kwargs):
            #post请求获取数据
            #设置解析器之后取值request.data
            print(request.data.get('name'),request.data.get('age'))
            
            return HttpResponse('post请求')
    
     #全局设置
        'DEFAULT_PARSER_CLASSES': (
           'rest_framework.parsers.JSONParser',
           'rest_framework.parsers.FormParser',
           'rest_framework.parsers.MultiPartParser'
        ),
     
     #选择使用哪种解析器
     #源码从request.data方法入手,
     #最终到达如下代码部分
         def select_parser(self, request, parsers):
             """
                 Given a list of parsers and a media type, return the appropriate
                 parser to handle the incoming request.
                 """
                     for parser in parsers:
                         if media_type_matches(parser.media_type, request.content_type):
                             return parser
                                 return None
    

序列化

  • 1.对queryset的序列化
    序列化的作用:将model的序列化为一个字典
    方式一:继承自serializers.Serializer(定制性较高,所有的字段都需要自己序列化)
    class MovieSerializer(serializers.Serializer):
        """电影列表的序列化"""
        #使用HyperlinkedIdentityField,反向生成url地址
        url = serializers.HyperlinkedIdentityField(
               view_name='moviedetail',
               #lookup_field:表中的id
               lookup_field = 'id',
               #lookup_url_kwarg:url地址上拼接的参数
               lookup_url_kwarg = 'pk'
        )
       name = serializers.CharField()
       publishtime = serializers.DateTimeField(format='%Y-%m-%d %H:%M')
       #返回枚举的文本
       country = serializers.CharField(source='get_country_display')
       #获取分类的名称
       category = serializers.CharField(source='category.name')
       category_id = serializers.IntegerField(source='category.id')
    
       #自定义序列化方法
       category = serializers.SerializerMethodField()
       def get_category(self,row):
           #自定义序列化方法
           #row:-> 当前序列化的类针对于哪个model的序列化,row就指的是这个model
           
           return 要返回的数据
    
    ####方式二继承自serializers.ModelSerializer,是在serializers.Serializer基础上进行了封装,我们使用起来比较简单
    class MovieDetailSerializer(serializers.ModelSerializer):
        """电影详情的序列化"""
        class Meta:
            #指定要序列化的model
            model = Movie
            #指定要序列化的字段
            fields = "__all__"   ##序列化所有字段
            # fields = ['name','id'] ##序列化部分字段
    
    ####请求数据的验证(post请求)
     class CategorySerializer(serializers.ModelSerializer):
         #自定义错误信息
         name = serializers.CharField(
             error_messages={
                 'required':'分类名为必填字段'
             }
         )
    
         class Meta:
             model = Category
             fields = '__all__'
    
         #自定义序列化的验证方法
         def validate_name(self,value):
             if value == '动作片':
                 raise ValidationError('该分类数据已存在')
             return value
    
      ####视图中使用序列化验证字段
      class CategoryView(APIView):
          def post(self,request,*args,**kwargs):
              #添加分类数据
              result = {
                  'code':1
                      }
              data = request.data
              ser = CategorySerializer(data=data)
              
              if ser.is_valid():
                  print('验证通过')
                  print(ser.validated_data)
                  #验证通过,将数据保存至数据库
                  ser.save()
                  result['msg'] = '创建成功'
              else:
                  print('数据验证失败',ser.errors)
                  result['code'] = 0
                  result['msg'] = '创建失败'
                      
            return Response(result)
    
    ######注意:如果序列化的类继承自serializers.Serializer,
    #在更新或者添加数据的时候,必须实现update()、create()方法
    # 相关实现可以在serializers.ModelSerializer寻找
        class CategorySerializer(serializers.Serializer):
            """分类序列化"""
            name = serializers.CharField(max_length=10)
            info = serializers.CharField(max_length=256)
            add_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M')
            
            def create(self, validated_data):
                intance = Category.objects.create(**validated_data)
                return intance
    
    

分页

  • 作用:数据量过多的时候,我们需要对数据进行分页
    ####方式一:继承自PageNumberPagination
    http://www.baidu.com/?page=1&pagesize=20
    from rest_framework.pagination import PageNumberPagination
    class MyPageNumberPagination(PageNumberPagination):
        #每页返回的条数( 默认值)
        page_size = 2
        #url地址上表示页码的参数
        page_query_param = 'page'
        #url地址上表示每页返回多少条数据的参数
        page_size_query_param = 'pagesize'
        #每页最多返回多少条数据
        max_page_size = 20
    
    ####方式二:继承自LimitOffsetPagination
    # http://www.baidu.com/?offset=10&limit=10
    from rest_framework.pagination import LimitOffsetPagination
    from collections import OrderedDict
    class MyLimitOffsetPagination(LimitOffsetPagination):
        ##每页返回的条数( 默认值)
        default_limit = 2
        #url地址上表示限制返回多少条数据的参数
        limit_query_param = 'limit'
        #url地址上表示偏移量的参数
        offset_query_param = 'offset'
        #每页最多限定返回多少条数据
        max_limit = None
    
    ####方式三:继承自CursorPagination
    ###分页方式三(会对url地址上的数据加密)
    from rest_framework.pagination import CursorPagination
    class MyCursorPagination(CursorPagination):
        #url地址上的分页参数
        cursor_query_param = 'cursor'
        ##每页返回的条数( 默认值)
        page_size = 2
        #设置排序字段(-id:表示倒叙,id:表示正序)
        ordering = 'id'
        # url地址上表示每页返回多少条数据的参数
        page_size_query_param = 'pagesize'
        #限定每页最多返回多少条数据
        max_page_size = 20
    
    ###注意如果需要使用get_paginated_response返回分页的响应结果,并且需要对结果数据进行自定义
    ###可以重写get_paginated_response方法
    #可以重写get_paginated_response方法,自定义分页返回结果数据
        from collections import OrderedDict
        def get_paginated_response(self, data):
            return Response(OrderedDict([
                                         ('code',1),
                                         ('count', self.count),
                                         ('next', self.get_next_link()),
                                         ('previous', self.get_previous_link()),
                                         ('results', data)
                                         ]))
    ###在视图中的使用方式
    class MovieView(APIView):
        
        def get(self,request,*args,**kwargs):
            #get请求获取电影列表
            result = {
                'code':1
            }
            pk = kwargs.get('pk')
            if not pk:
                # pk不存在:获取全部电影列表数据
                #从电影表中查询所有数据
                queryset = Movie.objects.all()
                #先获取当前分页数据
                #pg = MyPageNumberPagination()
                # pg = MyLimitOffsetPagination()
                pg = MyCursorPagination()
                
                page_data = pg.paginate_queryset(
                     queryset=queryset,
                     request=request,
                     view=self
                 )
                 #使用MovieSerializer将queryset序列化
                 ser = MovieSerializer(instance=page_data,many=True,context={'request': request})
                 # result['count'] = queryset.count()
                 #返回分页格式的响应结果
                return pg.get_paginated_response(ser.data)
    
    

视图

  • 作用:处理业务逻辑程,实现数据的增删该查

    	####最原始(FBV):
    def ShoppingCar(request):
        
        if request.method == "GET":
            pass
        elif request.method == "POST":
            pass
        ....
    ####最原始:使用的是Django的View视图(CBV)
    from django.views import View
    class ShoppingCarView(View):
        
        def get(self,request,*args,**kwargs):
            pass
        
        def post(self,request,*args,**kwargs):
            pass
        
        def delete(self,request,*args,**kwargs):
            pass
        
        def put(self,request,*args,**kwargs):
            pass
        
        def patch(self,request,*args,**kwargs):
            pass
    
    ##进化1:使用的是restframework的View视图
    class ShoppingCarView(APIView):
        
        def get(self,request,*args,**kwargs):
            pass
        
        def post(self,request,*args,**kwargs):
            pass
        
        def delete(self,request,*args,**kwargs):
            #获取商品的id
            result = {
                'code':1
            }
            try:
                pk = kwargs.get('pk')
                # from rest_framework.viewsets import ModelViewSet
                instance = models.ShopingCar.objects.filter(user=request.user.id,good=pk).first()
    
                if instance:
                    #从表中删除数据
                    instance.delete()
                    result['msg'] = '删除成功'
                else:
                    result['code'] = 0
                    result['msg'] = '删除失败'
            except Exception as err:
                print(err)
                result['code'] = 0
                result['msg'] = '请求异常'
                        
            return Response(result)
    
        def put(self,request,*args,**kwargs):
            #自定义更新数据的方法
            result = {
                'code':1
                }
                data = request.data
                #先获取需要更新的数据
                pk = kwargs.get('pk')
                instance = models.ShopingCar.objects.filter(id=pk).first()
                if instance:
                    #默认进行全字段更新
                    ser = ShopingCarSerializer(instance=instance,data=data)
                    if ser.is_valid():
                        ser.save()
                        result['msg'] = '数据更新成功'
                    else:
                        result['code'] = 0
                        print(ser.errors)
                        result['msg'] = '数据更新失败'
            else:
                result['code'] = 0
                result['msg'] = '数据更新失败,数据不存在'
            
            
            return Response(result)
        
        def patch(self,request,*args,**kwargs):
            #自定义更新数据的方法
            result = {
                'code':1
            }
            data = request.data
            #先获取需要更新的数据
            pk = kwargs.get('pk')
            instance = models.ShopingCar.objects.filter(id=pk).first()
            if instance:
                #设置partial=True,表示局部更新
                ser = ShopingCarSerializer(instance=instance,data=data,partial=True)
                if ser.is_valid():
                    ser.save()
                    result['msg'] = '数据更新成功'
                else:
                    result['code'] = 0
                    print(ser.errors)
                    result['msg'] = '数据更新失败'
            else:
                result['code'] = 0
                result['msg'] = '数据更新失败,数据不存在'
            return Response(result)
    
    ##进化2:使用restframe视图中的GenericAPIView,
    #####只是在ApiView的基础上实现了一些方法和参数,来实现序列化和分页等
    from rest_framework.generics import GenericAPIView
    
    class ShoppingCarGenericAPIView(GenericAPIView):
         #获取数据库表中的结果集
         queryset = models.ShopingCar.objects.all()
         #指定要序列化的类
         serializer_class = ShopingListCarSerializer
         #添加分页,指定分页的类
         pagination_class = MyPageNumberPagination
    
         def get(self,request,*args,**kwargs):
             car_goods = self.get_queryset()
             #获取当前分页的数据
             page_data = self.paginate_queryset(car_goods) #=> pg=MyPageNumberPagination()   pg..paginate_queryset(car_goods)
             #序列化分页数据
             ser = self.get_serializer(instance=page_data,many=True) #=>ShopingListCarSerializer(instance=car_goods,many=True)
             print(ser.data)
    
             ###获取如果做分页了,返回分页的结果集
             return self.get_paginated_response(ser.data)
    
             # return Response(ser.data)
    
    ####进化3:(注意如果使用如下视图,路由发生了该改变)
    #使用GenericViewSet和ListModelMixin,UpdateModelMixin,
    #CreateModelMixin,DestroyModelMixin,RetrieveModelMixin
    ##GenericViewSet:继承自ViewSetMixin, generics.GenericAPIView,一般情况下
    ##跟ListModelMixin,UpdateModelMixin,CreateModelMixin,DestroyModelMixin,
    #GenericViewSet
    # RetrieveModelMixin配合使用
    # ListModelMixin:get请求,获取列表数据
    # UpdateModelMixin:put请求,更新数据
    # CreateModelMixin:post请求,创建数据
    # DestroyModelMixin:delete请求,删除数据
    # RetrieveModelMixin:get请求,获取详情数据
    
    class ShoppingCarGenericAPIView(ListModelMixin,CreateModelMixin,
                                    DestroyModelMixin,UpdateModelMixin,
                                    RetrieveModelMixin,GenericViewSet):
        # 获取数据库表中的结果集
        queryset = models.ShopingCar.objects.all()
        # 指定要序列化的类
        serializer_class = ShopingListCarSerializer
        # 添加分页,指定分页的类
        pagination_class = MyPageNumberPagination
        
        #动态设置序列化的类
        def get_serializer_class(self):
            if self.action == 'list':
                return ShopingListCarSerializer
            elif self.action == 'retrieve':
                #获取详情数据
                return ShopingCarDetailSerializer
            
            return ShopingCarSerializer
        
        def retrieve(self, request, *args, **kwargs):
            result = {}
            instance = self.get_object()
            serializer = self.get_serializer(instance)
            result['code'] = 1
            result['data'] = serializer.data
            return Response(result)
    
    #### 注意路由的变化:
    url(r'^api/(?P<version>[v1|v2]+)/shopcar/$', ShoppingCarGenericAPIView.as_view({'get': 'list','post':'create'})),
        #destroy
    url(r'^api/(?P<version>[v1|v2]+)/shopcar/(?P<pk>\d+)/$', ShoppingCarGenericAPIView.as_view({"get":"retrieve",'delete': 'destroy',"put":"update",'patch':'partial_update'})),
    
    ####总结:
    1.如果视图部分的逻辑比较复杂,ListModelMixin...都满足不了的时候,建议使用APiView
    写视图类
    2.如果只是要实现一些简单的增删改查功能,也可以重写xxxxModelMixin中的方法实现自己的视图逻辑
        # GenericViewSet和一下类配合使用
        # RetrieveModelMixin配合使用
        # ListModelMixin:get请求,获取列表数据
        # UpdateModelMixin:put请求,更新数据
        # CreateModelMixin:post请求,创建数据
        # DestroyModelMixin:delete请求,删除数据
        # RetrieveModelMixin:get请求,获取详情数据
    
    3.如果要实现全部的增删改查可以直接继承自ModelViewSet
    
    

路由

  • 自动路由匹配使用如下

    from rest_framework.routers import DefaultRouter
    ##实例化自动路由对象
    router = DefaultRouter()
    router.register(r'shopcar',ShoppingCarGenericAPIView,base_name='shopcar')
    urlpatterns = [
                   url(r'^api/(?P<version>[v1|v2]+)/',include(router.urls))
                   ]
    ##会帮我们生成如下url地址
    #^api/(?P<version>[v1|v2]+)/ ^shopcar/$ [name='shopcar-list']
    #^api/(?P<version>[v1|v2]+)/ ^shopcar\.(?P<format>[a-z0-9]+)/?$ [name='shopcar-list']
    #^api/(?P<version>[v1|v2]+)/ ^shopcar/(?P<pk>[^/.]+)/$ [name='shopcar-detail']
    #^api/(?P<version>[v1|v2]+)/ ^shopcar/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='shopcar-detail']
    
    

渲染器

  • 返回数据,展示的模版

    from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer,AdminRenderer
    
    ##使用:
    class ShoppingCarGenericAPIView(ModelViewSet):
        ##设置渲染器
        renderer_classes = [JSONRenderer,AdminRenderer]
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值