• 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': '年龄'
                }
            }
    
  • 相关阅读:
    HDU
    HDU
    (4)数据--相似性与相异性
    (3)数据--操作
    (2)数据--基本概念
    五、按生命周期划分数据(二)
    五、常用数据类型(一)
    四、坏耦合的原因与解耦(三)
    四、强化耦合(二)
    四、初识耦合(一)
  • 原文地址:https://www.cnblogs.com/pythonwl/p/13295511.html
Copyright © 2020-2023  润新知