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': '年龄'
            }
        }
原文地址:https://www.cnblogs.com/pythonwl/p/13295511.html