zoukankan      html  css  js  c++  java
  • drf——认证,权限,限流,过滤,排序,分页,异常处理,接口文档生成

    一认证Authentication

    认证失败会有两种可能的返回值:

    • 401 Unauthorized 未认证

    • 403 Permission Denied 权限被禁止

    全局配置 :配置文件
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.SessionAuthentication',  # session认证
            'rest_framework.authentication.BasicAuthentication',   # 基本认证
        )
    }
    
    局部配置
    from rest_framework.authentication import SessionAuthentication, BasicAuthentication
    from rest_framework.views import APIView
    
    class ExampleView(APIView):
        authentication_classes = [SessionAuthentication, BasicAuthentication]
    
    自定义认证
    1 类 继承 BaseAuthentication
    
      方法 authenticate方法 认证逻辑
        
      认证通过,返回两个值 : user , token 
      认证失败,抛异常:APIException或者AuthenticationFailed
      from rest_framework.exceptions import AuthenticationFailed
      raise AuthenticationFailed('认证失败')
    
    2 全局使用,局部使用
    
    3 其实不继承 BaseAuthentication , 写认证类也可以,鸭子类型,这个类只是强制要写
      需要注意,如果配置多个认证类,要把返回两个值的放到最后
    
    源码分析
    self.perform_authentication(request)
    就一句话:request.user,需要去drf的Request对象中找user属性(方法) 
    
    Request类中的user方法,刚开始来,没有_user,走 self._authenticate()
    
    dispatch定制Request时:authenticators=self.get_authenticators()  # 得到的是列表,元素是认证类对象
        def get_authenticators(self):
            return [auth() for auth in self.authentication_classes]
        
        authentication_classes可以在类视图中自定义,或者配置文件默认:
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    	DEFAULTS = {
        	'DEFAULT_AUTHENTICATION_CLASSES': [
            	'rest_framework.authentication.SessionAuthentication',
            	'rest_framework.authentication.BasicAuthentication'
        	],}
    
    	request.user : Request类的 执行_authenticate(self):
    
        self.authenticators = authenticators or ()
        def _authenticate(self):
     # 遍历拿到一个个认证器,进行认证
    # self.authenticators配置的一堆认证类产生的认证类对象组成的 list
    # self.authenticators 你在视图类中配置的一个个的认证类:
    # authentication_classes=[认证类1,认证类2],对象的列表
            for authenticator in self.authenticators:
                try:
                    # 认证器(对象)调用认证方法authenticate(认证类对象self, request请求对象)
                    # 返回值:登陆的用户 与 认证的信息 组成的 tuple
                    # 该方法被try包裹,代表该方法会抛异常,抛异常就代表认证失败
                    user_auth_tuple = authenticator.authenticate(self) #注意这self是request对象
                except exceptions.APIException:
                    self._not_authenticated()
                    raise
    
                # 返回值的处理
                if user_auth_tuple is not None:
                    self._authenticator = authenticator
                    # 如何有返回值,就将 登陆用户 与 登陆认证 分别保存到 request.user、request.auth
                    self.user, self.auth = user_auth_tuple
                    return
            # 如果返回值user_auth_tuple为空,代表认证通过,但是没有 登陆用户 与 登陆认证信息,代表游客
            self._not_authenticated()
    
    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    from app01.models import UserToken
    
    class MyAuthentication(BaseAuthentication):
        def authenticate(self, request):
            token=request.GET.get('token')
            if  token:
                user_token=UserToken.objects.filter(token=token).first()
                if user_token:
                    return user_token.user,token
                else:
                    raise AuthenticationFailed('认证失败')
            else:
                raise AuthenticationFailed('请求地址中需要携带token')
    
    使用
    全局使用,在setting.py中配置
    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",]
    }
    
    局部使用
    authentication_classes=[MyAuthentication]
    局部禁用
    authentication_classes=[]
    

    二 权限Permissions

    全局设置 配置文件
    REST_FRAMEWORK = {
        
        'DEFAULT_PERMISSION_CLASSES': (
            'rest_framework.permissions.IsAuthenticated',
        )
    }
    
    如果未指明,则采用如下默认配置
    'DEFAULT_PERMISSION_CLASSES': (
       'rest_framework.permissions.AllowAny',
    )
    
    局部使用
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.views import APIView
    
    class ExampleView(APIView):
        permission_classes = (IsAuthenticated,)
    

    提供的权限

    • AllowAny 允许所有用户
    • IsAuthenticated 仅通过认证的用户
    • IsAdminUser 仅管理员用户
    • IsAuthenticatedOrReadOnly 已经登陆认证的用户可以对数据进行增删改操作,没有登陆认证的只能查看数据。
    from rest_framework.authentication import SessionAuthentication
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.generics import RetrieveAPIView
    
    class StudentAPIView(RetrieveAPIView):
        queryset = Student.objects.all()
        serializer_class = StudentSerializer
        
        authentication_classes = [SessionAuthentication]  # 认证
        permission_classes = [IsAuthenticated]            # 权限
    

    自定义权限

    继承rest_framework.permissions.BasePermission父类,并实现以下两个方法的一个或全部

    • .has_permission(self, request, view)

      是否可以访问视图, view表示当前视图对象

    • .has_object_permission(self, request, view, obj)

      是否可以访问数据对象, view表示当前视图, obj为数据对象

    当前子应用下,创建一个权限文件permissions.py中声明自定义权限类:

    from rest_framework.permissions import BasePermission
    
    class IsXiaoMingPermission(BasePermission):
        def has_permission(self, request, view):
            if( request.user.username == "xiaoming" ):
                return True
            #  有权限 True 无权限 False
    
    全局使用
    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",],
        'DEFAULT_PERMISSION_CLASSES': [
            'app01.app_auth.UserPermission',
        ],
    }
    
    局部使用
    from .permissions import IsXiaoMingPermission
    class StudentViewSet(ModelViewSet):
        queryset = Student.objects.all()
        serializer_class = StudentSerializer
        
        permission_classes = [IsXiaoMingPermission]
    
    局部禁用
    class TestView(APIView):
        permission_classes = []
    

    三 限流Throttling

    可以对接口访问的频次进行限制,以减轻服务器压力。

    一般用于付费购买次数,投票等场景使用.

    使用

    全局使用
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': (
            'rest_framework.throttling.AnonRateThrottle',
            'rest_framework.throttling.UserRateThrottle'
        ),
        'DEFAULT_THROTTLE_RATES': {
            'anon': '100/day',
            'user': '1000/day'
        }
    }
    

    DEFAULT_THROTTLE_RATES 可以使用 second, minute, hourday来指明周期。

    局部使用
    from rest_framework.throttling import UserRateThrottle
    from rest_framework.views import APIView
    
    class ExampleView(APIView):
        throttle_classes = (UserRateThrottle,)
    

    可选限流类

    1) AnonRateThrottle

    限制所有匿名未认证用户,使用IP区分用户。

    使用DEFAULT_THROTTLE_RATES['anon'] 来设置频次

    2)UserRateThrottle

    限制认证用户,使用User id 来区分。

    使用DEFAULT_THROTTLE_RATES['user'] 来设置频次

    3)ScopedRateThrottle

    限制用户对于每个视图的访问频次,使用ip或user id。

    不同的视图,设置 throttle_scope:

    class ContactListView(APIView):
        throttle_scope = 'contacts'
    
    
    class ContactDetailView(APIView):
        throttle_scope = 'contacts'
    
    
    class UploadView(APIView):
        throttle_scope = 'uploads'
    
    
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': (
            'rest_framework.throttling.ScopedRateThrottle',
        ),
        'DEFAULT_THROTTLE_RATES': {
            'contacts': '1000/day',
            'uploads': '20/day'
        }
    }
    

    实例

    全局配置
        'DEFAULT_THROTTLE_RATES': {
            'anon': '3/minute',
            'user': '10/minute'
        }
    
    from rest_framework.authentication import SessionAuthentication
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.generics import RetrieveAPIView
    from rest_framework.throttling import UserRateThrottle
    
    class StudentAPIView(RetrieveAPIView):
        queryset = Student.objects.all()
        serializer_class = StudentSerializer
        
        authentication_classes = [SessionAuthentication]
        permission_classes = [IsAuthenticated]
        
        throttle_classes = (UserRateThrottle,)
    

    根据ip进行频率限制:

    # 类,继承SimpleRateThrottle,重写get_cache_key 
    
    from rest_framework.throttling import ScopedRateThrottle,SimpleRateThrottle
    
    class MyThrottle(SimpleRateThrottle):
        scope='luffy'
        def get_cache_key(self, request, view):
            return request.META.get('REMOTE_ADDR')
        
    # 局部使用,全局使用
    
    REST_FRAMEWORK={
        'DEFAULT_THROTTLE_CLASSES': (
            'utils.throttling.MyThrottle',
        ),
        'DEFAULT_THROTTLE_RATES': {
            'luffy': '3/m'  # key要跟类中的scop对应
        },
    }
    
    # python3 manage.py runserver 0.0.0.0:8000   局域网相互访问
    

    自定义频率

    # 自定制频率类,需要写两个方法
    	# 判断是否限次:没有限次True,限次False
        	def allow_request(self, request, view):
                
        # 限次后调用,显示还需等待多长时间才能再访问,返回等待的时间seconds
        	def wait(self):
    
    import time
    
    class IPThrottle():
        
        VISIT_DIC = {}
        def __init__(self):
            self.history_list=[]
        def allow_request(self, request, view):
            ip=request.META.get('REMOTE_ADDR')
            ctime=time.time()
            if ip not in self.VISIT_DIC:
                self.VISIT_DIC[ip]=[ctime,]
                return True
            self.history_list=self.VISIT_DIC[ip]   #当前访问者时间列表拿出来
            while True:
                if ctime-self.history_list[-1]>60:
                    self.history_list.pop() # 把最后一个移除
                else:
                    break
            if len(self.history_list)<3:
                self.history_list.insert(0,ctime)
                return True
            else:
                return False
    
        def wait(self):
            ctime=time.time()
            return 60-(ctime-self.history_list[-1])
    
    #全局使用,局部使用
    
    # SimpleRateThrottle源码分析
        def get_rate(self):
            if not getattr(self, 'scope', None):
                msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                       self.__class__.__name__)
                raise ImproperlyConfigured(msg)
    
            try:
                return self.THROTTLE_RATES[self.scope]  # scope:'user' => '3/min'
            except KeyError:
                msg = "No default throttle rate set for '%s' scope" % self.scope
                raise ImproperlyConfigured(msg)
        def parse_rate(self, rate):
            if rate is None:
                return (None, None)
            #3  mmmmm
            num, period = rate.split('/')  # rate:'3/min'
            num_requests = int(num)
            duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
            return (num_requests, duration)
        def allow_request(self, request, view):
            if self.rate is None:
                return True
            #当前登录用户的ip地址
            self.key = self.get_cache_key(request, view)  # key:'throttle_user_1'
            if self.key is None:
                return True
    
            # 初次访问缓存为空,self.history为[],是存放时间的列表
            self.history = self.cache.get(self.key, [])
            # 获取一下当前时间,存放到 self.now
            self.now = self.timer()
    
            # Drop any requests from the history which have now passed the
            # throttle duration
    
            # 当前访问与第一次访问时间间隔如果大于60s,第一次记录清除,不再算作一次计数
            # 10 20 30 40
            # self.history:[10:23,10:55]
            # now:10:56
            while self.history and  self.now - self.history[-1] >= self.duration:
                self.history.pop()
    
            # history的长度与限制次数3进行比较
            # history 长度第一次访问0,第二次访问1,第三次访问2,第四次访问3失败
            if len(self.history) >= self.num_requests:
                # 直接返回False,代表频率限制了
                return self.throttle_failure()
    
            # history的长度未达到限制次数3,代表可以访问
            # 将当前时间插入到history列表的开头,将history列表作为数据存到缓存中,key是throttle_user_1,过期时间60s
            return self.throttle_success()
    

    四 过滤Filtering

    pip install django-filter
    

    配置文件中增加过滤后端的设置:

    INSTALLED_APPS = [
        'django_filters',  # 需要注册应用,
    ]
    
    REST_FRAMEWORK = {
        'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
    }
    

    视图中添加filter_fields属性

    class StudentListView(ListAPIView):
        queryset = Student.objects.all()
        serializer_class = StudentSerializer
        filter_fields = ('age', 'sex')
    
    # 127.0.0.1:8000/four/students/?sex=1
    

    五 排序

    对于列表数据,REST framework提供了OrderingFilter过滤器来帮助我们快速指明数据按照指定字段进行排序。

    使用方法:

    在类视图中设置filter_backends,使用rest_framework.filters.OrderingFilter过滤器

    class StudentListView(ListAPIView):
        queryset = Student.objects.all()
        serializer_class = StudentModelSerializer
        
        filter_backends = [OrderingFilter]
        ordering_fields = ('id', 'age')
    
    # 127.0.0.1:8000/books/?ordering=-age
    # -id 表示针对id字段进行倒序排序
    # id  表示针对id字段进行升序排序
    

    如果需要在过滤以后再次进行排序,则需要两者结合!

    from rest_framework.generics import ListAPIView
    from students.models import Student
    from .serializers import StudentModelSerializer
    from django_filters.rest_framework import DjangoFilterBackend
    
    class Student3ListView(ListAPIView):
        queryset = Student.objects.all()
        serializer_class = StudentModelSerializer
        
        filter_fields = ('age', 'sex')
        # 因为局部配置会覆盖全局配置,所以需要重新把过滤组件核心类再次声明,
        # 否则过滤功能会失效
        filter_backends = [OrderingFilter,DjangoFilterBackend]
        ordering_fields = ('id', 'age')
    

    六 分页Pagination

    REST framework提供了分页的支持。

    全局使用
    REST_FRAMEWORK = {
        'DEFAULT_PAGINATION_CLASS':  'rest_framework.pagination.PageNumberPagination',
        'PAGE_SIZE': 100  # 每页数目
    }
    
    局部使用
    from  rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
    
    class LargeResultsSetPagination(PageNumberPagination):
        page_size = 1000
        page_size_query_param = 'page_size'
        max_page_size = 10000
        
    class BookDetailView(RetrieveAPIView):
        queryset = BookInfo.objects.all()
        serializer_class = BookInfoSerializer
        
        pagination_class = LargeResultsSetPagination
    
    局部禁用
    pagination_class = None
    

    可选分页器

    1) PageNumberPagination

    前端访问网址形式:

    GET  http://127.0.0.1:8000/students/?page=4
    

    可以在子类中定义的属性:

    • page_size 每页数目
    • page_query_param 前端发送的页数关键字名,默认为"page"
    • page_size_query_param 前端发送的每页数目关键字名,默认为None
    • max_page_size 前端最多能设置的每页数量
    from rest_framework.pagination import PageNumberPagination
    
    class StandardPageNumberPagination(PageNumberPagination):
        page_size = 2
        page_size_query_param = "size"
        max_page_size = 10
        page_query_param = "p"
    
    class StudentAPIView(ListAPIView):
        queryset = Student.objects.all()
        serializer_class = StudentModelSerializer
        
        pagination_class = StandardPageNumberPagination
    
    # 127.0.0.1/four/students/?p=1&size=5
    
    2)LimitOffsetPagination

    前端访问网址形式:

    GET http://127.0.0.1/four/students/?limit=100&offset=400
    

    可以在子类中定义的属性:

    • default_limit 默认限制,默认值与PAGE_SIZE设置一直
    • limit_query_param limit参数名,默认'limit'
    • offset_query_param offset参数名,默认'offset'
    • max_limit 最大limit限制,默认None
    from rest_framework.pagination import LimitOffsetPagination
    
    class StandardLimitOffsetPagination(LimitOffsetPagination):
        default_limit = 2
        limit_query_param = "size"
        offset_query_param = "start"
    
    class StudentAPIView(ListAPIView):
        queryset = Student.objects.all()
        serializer_class = StudentModelSerializer
        pagination_class = StandardLimitOffsetPagination
    
    3)CursorPagination

    ​ cursor_query_param = 'cursor' 每一页查询的key
    ​ page_size = 2 每页显示的条数
    ​ ordering = '-id' 排序字段

    class MyCursorPagination(CursorPagination):
        cursor_query_param = 'cursor'
        page_size = 2
        ordering = '-id'
        
    class BookView(ListAPIView):
        # queryset = models.Book.objects.all().filter(is_delete=False)
        queryset = models.Book.objects.all()
        serializer_class = BookModelSerializer
        
        pagination_class = MyCursorPagination
    
    APIView 分页
    class BookView(APIView):
        
        def get(self,request,*args,**kwargs):
            book_list=models.Book.objects.all()
            # 实例化得到一个分页器对象
            page_cursor=MyPageNumberPagination()
    
            book_list=page_cursor.paginate_queryset(book_list,request,view=self)
            next_url =page_cursor.get_next_link()
            pr_url=page_cursor.get_previous_link()
            book_ser=BookModelSerializer(book_list,many=True)
            
            return Response(data=book_ser.data)
    
    #settings.py
    REST_FRAMEWORK={
        'PAGE_SIZE': 2,
    }
    

    七 异常处理 Exceptions

    REST framework提供了异常处理,我们可以自定义异常处理函数

    from rest_framework.views import exception_handler
    
    def custom_exception_handler(exc, context):
        # 先调用REST framework默认的异常处理方法获得标准错误响应对象
        response = exception_handler(exc, context)
    
        # 自定义异常处理
        if response is None:
            response.data['status_code'] = response.status_code
    
        return response
    

    配置文件中声明自定义异常处理

    REST_FRAMEWORK = {
        'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
    }
    

    如果未声明,会采用默认的方式,如下

    rest_frame/settings.py

    REST_FRAMEWORK = {
        'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
    }
    

    补充 处理关于数据库的异常

    from rest_framework.views import exception_handler as drf_exception_handler
    from rest_framework import status
    from django.db import DatabaseError
    
    def exception_handler(exc, context):
        response = drf_exception_handler(exc, context)
        if response is None:
            view = context['view']
            if isinstance(exc, DatabaseError):
                response = Response({'detail': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
        return response
    

    REST framework定义的异常

    • APIException 所有异常的父类
    • ParseError 解析错误
    • AuthenticationFailed 认证失败
    • NotAuthenticated 尚未认证
    • PermissionDenied 权限决绝
    • NotFound 未找到
    • MethodNotAllowed 请求方式不支持
    • NotAcceptable 要获取的数据格式不支持
    • Throttled 超过限流次数
    • ValidationError 校验失败

    也就是说,很多的没有在上面列出来的异常,就需要我们在自定义异常中自己处理了。

    八 自动生成接口文档

    REST framework可以自动帮助我们生成接口文档。

    接口文档以网页的方式呈现。

    自动接口文档能生成的是继承自APIView及其子类的视图。

    8.1. 安装依赖

    pip install coreapi
    

    8.2. 设置接口文档访问路径

    路由配置

    rest_framework.documentation.include_docs_urls

    参数title为接口文档网站的标题。

    from rest_framework.documentation import include_docs_urls
    
    urlpatterns = [
        path('docs/', include_docs_urls(title='站点页面标题'))
    ]
    

    8.3. 文档描述说明的定义位置

    1) 单一方法的视图,可直接使用类视图的文档字符串,如

    class BookListView(generics.ListAPIView):
        """
        返回所有图书信息.
        """
    

    2)包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如

    class BookListCreateView(generics.ListCreateAPIView):
        """
        get:
        返回所有图书信息.
    
        post:
        新建图书.
        """
    

    3)对于视图集ViewSet,仍在类视图的文档字符串中封开定义,但是应使用action名称区分,如

    class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
        """
        list:
        返回图书列表数据
    
        retrieve:
        返回图书详情数据
    
        latest:
        返回最新的图书数据
    
        read:
        修改图书的阅读量
        """
    

    8.4. 访问接口文档网页

    浏览器访问 127.0.0.1:8000/docs/,即可看到自动生成的接口文档。

    接口文档网页

    两点说明:

    1) 视图集ViewSet中的retrieve名称,在接口文档网站中叫做read

    2)参数的Description需要在模型类或序列化器类的字段中以help_text选项定义,如:

    class Student(models.Model):
        ...
        age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄')
        ...
    

    class StudentSerializer(serializers.ModelSerializer):
        class Meta:
            model = Student
            fields = "__all__"
            extra_kwargs = {
                'age': {
                    'required': True,
                    'help_text': '年龄'
                }
            }
    
  • 相关阅读:
    Codeforces 451A Game With Sticks
    POJ 3624 Charm Bracelet
    POJ 2127 Greatest Common Increasing Subsequence
    POJ 1458 Common Subsequence
    HDU 1087 Super Jumping! Jumping! Jumping!
    HDU 1698
    HDU 1754
    POJ 1724
    POJ 1201
    CSUOJ 1256
  • 原文地址:https://www.cnblogs.com/pythonwl/p/13295511.html
Copyright © 2011-2022 走看看