1 权限Permissions(权限是在认证之后的)

权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。

  • 在执行视图的dispatch()方法前,会先进行视图访问权限的判断
  • 在通过get_object()获取具体对象时,会进行模型对象访问权限的判断

1.1 权限源码分析

# APIView---->dispatch---->initial--->self.check_permissions(request)(APIView的对象方法)
    def check_permissions(self, request):
        # 遍历权限对象列表得到一个个权限对象(权限器),进行权限认证
        for permission in self.get_permissions():
            # 权限类一定有一个has_permission权限方法,用来做权限认证的
            # 参数:权限对象self、请求对象request、视图类对象
            # 返回值:有权限返回True,无权限返回False
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request, message=getattr(permission, 'message', None)
                )

1.2 自定义权限(和认证一样)

app_auth.py写一个类,继承BasePermission,重写has_permission

# 写一个类,继承BasePermission,重写has_permission,如果权限通过,就返回True,不通过就返回False
from rest_framework.permissions import BasePermission


class UserPermission(BasePermission):
    def has_permission(self, request, view):    # view是视图类的对象
        # 不是超级用户,不能访问
        # 由于认证已经过了,request内就有user对象了,当前登录用户
        user = request.user  # 当前登录用户
        # 如果该字段用了choice,通过get_字段名_display()就能取出choice后面的中文
        print(user.get_user_type_display())
        if user.user_type == 1:
            return True
        else:
            return False

使用:

# 局部使用
class TestView(APIView):
    permission_classes = [app_auth.UserPermission]
# 全局使用
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",],
    'DEFAULT_PERMISSION_CLASSES': [
        'app01.app_auth.UserPermission',
    ],
}
# 局部禁用
class TestView(APIView):
    permission_classes = []

1.3 内置权限以及认证

# 演示一下内置权限的使用:IsAdminUser,控制是否对网站后台有权限的人
# 1 创建超级管理员
# 2 写一个测试视图类
# views.py
from rest_framework.permissions import IsAdminUser
from rest_framework.authentication import SessionAuthentication
class TestView3(APIView):
    authentication_classes=[SessionAuthentication,]
    permission_classes = [IsAdminUser]
    def get(self,request,*args,**kwargs):
        return Response('这是22222222测试数据,超级管理员可以看')

# urls.py
urlpatterns = [
    path('test3/', views.TestView3.as_view()),
]

# 3 超级用户登录到admin,再访问test3就有权限
# 4 正常的话,普通管理员,没有权限看(判断的是is_staff字段)

2 频率限制/限流(Throttling)

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

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

2.1 自定义频率类

# 自定制频率类,需要写两个方法
    -# 判断是否限次:没有限次可以请求True,限次了不可以请求False
        def allow_request(self, request, view):
    -# 限次后调用,显示还需等待多长时间才能再访问,返回等待的时间seconds
        def wait(self):

自定义频率类

  • throttling.py
# 自定制的频率限制类
import time
class IPThrottle():
    #定义成类属性,所有对象用的都是这个
    VISIT_DIC = {}
    def __init__(self):
        self.history_list=[]
    def allow_request(self, request, view):
        '''
        #(1)取出访问者ip
        #(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
        #(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        #(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        #(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        '''

        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])
  • settings.py
REST_FRAMEWORK = {
    'PAGE_SIZE': 1,
    'DEFAULT_THROTTLE_CLASSES': (
        'utils.throttling.IPThrottle',
    ),

}

简单使用

(继承SimpleRateThrottle,重写get_cache_key)

  • throttling.py
# 写一个类,继承SimpleRateThrottle,只需要重写get_cache_key
from rest_framework.throttling import ScopedRateThrottle,SimpleRateThrottle

#继承SimpleRateThrottle
class MyThrottle(SimpleRateThrottle):
    scope='luffy'
    def get_cache_key(self, request, view):
        print(request.META.get('REMOTE_ADDR'))
        return request.META.get('REMOTE_ADDR')   # 返回
  • views.py
from rest_framework.response import Response
from api import models
from rest_framework.views import APIView
from api.ser import BookModelSerializer
from rest_framework.pagination import PageNumberPagination

class MyPageNumberPagination(PageNumberPagination):
    page_size = 1
    page_query_param = "aaa"

# 如果使用APIView分页
from utils.throttling import MyThrottle
class BookView(APIView):
    def get(self,request,*args,**kwargs):
        throttle_classes = [MyThrottle, ]
        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()
        # print(next_url)
        # print(pr_url)
        book_ser=BookModelSerializer(book_list,many=True)
        return Response(data=book_ser.data)
  • settings.py
REST_FRAMEWORK = {
    'PAGE_SIZE': 1,
    'DEFAULT_THROTTLE_CLASSES': (
        'utils.throttling.MyThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'luffy': '3/m'  # key要跟类中的scop对应
    },
}

补充:

# python3 manage.py runserver 0.0.0.0:8000   #局域网就可以相互访问

2.2 内置频率类

  • settings.py设置限制匿名用户访问频次
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",],
    'DEFAULT_PERMISSION_CLASSES': [
        'app01.app_auth.UserPermission',
    ],
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '3/m',
    }
}
  • views.py
class TestView5(APIView):
    authentication_classes = []
    permission_classes = []
    throttle_classes = [AnonRateThrottle]

    def get(self, request, *args, **kwargs):
        return Response('我是未登录用户,TestView5')

内置频率限制之全局或局部限制登录用户的访问频次

# 需求:未登录用户1分钟访问5次,登录用户一分钟访问10次
全局:在setting中
  'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ),
    'DEFAULT_THROTTLE_RATES': {
        'user': '10/m',
        'anon': '5/m',
    }

局部配置:(settings.py中的设置频率的DEFAULT_THROTTLE_RATES还要写)
    然后再在视图类中配一个就行
    throttle_classes = [AnonRateThrottle]

限制的用户

1) AnonRateThrottle

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

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

2)UserRateThrottle

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

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

3)ScopedRateThrottle

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

2.3 SimpleRateThrottle源码分析

# SimpleRateThrottle源码分析
    def get_rate(self):
        """
        Determine the string representation of the allowed request rate.
        """
        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):
        """
        Given the request rate string, return a two tuple of:
        <allowed number of requests>, <period of time in seconds>
        """
        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()

3 过滤Filtering

对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展来增强支持。

# pip3 install django-filter

在配置文件中注册,并设置全局配,或者局部配置

INSTALLED_APPS = [
    ...
    'django_filters',  # 需要注册应用,
]

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",],
    'DEFAULT_PERMISSION_CLASSES': [
        'app01.app_auth.UserPermission',
    ],
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '5/m',
        'user': '10/m',
    },
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)   # 全局过滤,之后在视图中指明按照哪个字段过滤
}
  • views.py
class BookView(ListAPIView):
    authentication_classes = []
    permission_classes = []
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_fields = ('name',)  # 配置可以按照哪个字段来过滤
  • urls.py
urlpatterns = [
    path('books/', views.BookView.as_view()),
]

访问效果

过滤的局部配置:

from django_filters.rest_framework import DjangoFilterBackend  # 导入
# 之后在视图中使用
filter_backends = [DjangoFilterBackend]
filter_fields = ('name',)

自定义过滤规则

# filters.py
# 自定义过滤规则(例如:无论查出多少条,都显示一条)
from rest_framework.filters import BaseFilterBackend


class MyFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 真正的过滤规则
        # params=request.GET.get('teacher')
        # queryset.filter('''''')
        return queryset[:1]

# 视图中
from .filters import MyFilter
class CourseView(GenericViewSet, ListModelMixin, RetrieveModelMixin):
    queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('orders')
    serializer_class = serializers.CourseModelSerializer

    # 配置分页
    pagination_class = PageNumberPagination

    # 过滤和排序
    filter_backends = [OrderingFilter, DjangoFilterBackend, MyFilter]
    ordering_fields = ('id', 'price', 'students')
    filter_fields = ('course_category',)

django-filter的区间过滤

# django-filter插件 过滤类

# filters.py
from django_filters.filterset import FilterSet
from . import models
from django_filters import filters
class CourseFilterSet(FilterSet):
    # 区间过滤:field_name关联的Model字段;lookup_expr设置规则;gt是大于,gte是大于等于;
    min_price = filters.NumberFilter(field_name='price', lookup_expr='gte')
    max_price = filters.NumberFilter(field_name='price', lookup_expr='lte')
    class Meta:
        model = models.Course

        fields = ['course_category', 'min_price', 'max_price']


# views.py视图中
# 课程群查接口
from rest_framework.filters import OrderingFilter, SearchFilter
from django_filters.rest_framework import DjangoFilterBackend
from .filters import CourseFilterSet

class CourseView(GenericViewSet, ListModelMixin, RetrieveModelMixin):
    queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('orders')
    serializer_class = serializers.CourseModelSerializer
    # 过滤和排序

    filter_backends = [DjangoFilterBackend]
    filter_class = CourseFilterSet

# postman访问:
# http://127.0.0.1:8000/course/free/?course_category=1&min_price=50&max_price=100

django-filters指定以某个字段过滤有两种方式

'''
django-filters指定以某个字段过滤有两种方式
第一种:
    配置类:
    filter_backends=[DjangoFilterBackend]
    配置字段:
    filter_fields=['course_category']
第二种:
    配置类:
    filter_backends=[DjangoFilterBackend]
    配置类:(自己写的类)
    class CourseFilterSet(FilterSet):
        class Meta:
            model=models.Course
            fields=['course_category']
    filter_class = CourseFilterSet

第三种:实现区间过滤
    class CourseFilterSet(FilterSet):
        # 课程的价格范围要大于min_price,小于max_price
        min_price = filters.NumberFilter(field_name='price', lookup_expr='gt')
        max_price = filters.NumberFilter(field_name='price', lookup_expr='lt')
        class Meta:
            model=models.Course
            fields=['course_category']
    配置类:
        filter_backends=[DjangoFilterBackend]
    配置类:(自己写的类)
        -filter_class = CourseFilterSet
'''

4 排序

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

使用方法:

在类视图中设置filter_backends,使用rest_framework.filters.OrderingFilter过滤器,REST framework会在请求的查询字符串参数中检查是否包含了ordering参数,如果包含了ordering参数,则按照ordering参数指明的排序字段对数据集进行排序。

前端可以传递的ordering参数,例如:http://127.0.0.1:8000/books2/?ordering=-id(可选字段值需要在ordering_fields中指明)。

局部配置

  • views.py (局部配置直接在视图中配置)
from rest_framework.generics import ListAPIView
from app01.models import Book
from app01.serializers import BookSerializer
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter    # 排序,依赖于django-filters

# 过滤字段使用
class BookView(ListAPIView):
    authentication_classes = []
    permission_classes = []
    filter_backends = [DjangoFilterBackend]
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_fields = ('name', 'price')  # 配置可以按照哪个字段来过滤

# 排序组件使用
class BookView2(ListAPIView):
    authentication_classes = []
    permission_classes = []
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [OrderingFilter]   # 局部配置
    ordering_fields = ('id', 'price')    # 排序字段
  • urls.py
from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 过滤
    path('books/', views.BookView.as_view()),
    # 排序
    path('books2/', views.BookView2.as_view()),
]

全局配置(settings.py中配置排序,视图中配置排序字段)

  • settings.py
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': (
    'rest_framework.filters.OrderingFilter', )
}
  • views.py
from rest_framework.generics import ListAPIView
from app01.models import Book
from app01.serializers import BookSerializer
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter

# 过滤字段使用
class BookView(ListAPIView):
    authentication_classes = []
    permission_classes = []
    # filter_backends = [DjangoFilterBackend]
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_fields = ('name', 'price')  # 配置可以按照哪个字段来过滤

# 排序组件使用
class BookView2(ListAPIView):
    authentication_classes = []
    permission_classes = []
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # filter_backends = [OrderingFilter]
    ordering_fields = ('id', 'price')    # 排序字段

使用时,url后?ordering参数,参数前加-表示降序

# 使用:
http://127.0.0.1:8000/books2/?ordering=-price
http://127.0.0.1:8000/books2/?ordering=price
http://127.0.0.1:8000/books2/?ordering=-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')

5 过滤与排序补充

rest_framework自带的过滤和排序使用以及第三方的django-filter的使用

'''
    排序:
    按id正序倒叙排序,按price正序倒叙排列
    使用:http://127.0.0.1:8000/course/free/?ordering=-id
    配置类:
        filter_backends=[OrderingFilter]
    配置字段:
        ordering_fields=['id','price']


    内置过滤:
    使用:http://127.0.0.1:8000/course/free/?search=39
    按照price过滤(表自有的字段直接过滤)
    配置类:
        filter_backends=[SearchFilter]
    配置字段:
        search_fields=['price']

    扩展:django-filter
    安装:
    支持自由字段的过滤还支持外键字段的过滤
    http://127.0.0.1:8000/course/free/?course_category=1   # 过滤分类为1 (python的所有课程)
    配置类:
        filter_backends=[DjangoFilterBackend]
    配置字段:
        filter_fields=['course_category']
'''

rest_framework自带的过滤(OrderingFilter)和排序(SearchFilter)的源码流程分析

先分析OrderingFilter

# 课程群查接口
from rest_framework.filters import OrderingFilter, SearchFilter

class CourseView(GenericViewSet, ListModelMixin, RetrieveModelMixin):
    queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('orders')
    serializer_class = serializers.CourseModelSerializer

    # 过滤和排序
    '''
    OrderingFilter:排序
    SearchFilter:过滤,自带的这个不能以外键字段过滤,所以要使用第三方的django-filter
    '''
    filter_backends = [OrderingFilter]
    ordering_fields = ('id', 'price', 'students')

OrderingFilter是在群查下使用的,所以我们先去ListModelMixin下查看list方法

class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

    # 查看list方法之后发现是通过queryset = self.filter_queryset(self.get_queryset()),
    # 这个self.filter_queryset()方法然后得到排序或过滤后的queryset对象,这里的self是继承这个ListModelMixin的视图,这里也就是CourseView。
    # 而CourseView中没有filter_queryset()方法,到父类中找,发现GenericAPIView中有一个filter_queryset()方法,

GenericAPIView中de1filter_queryset()方法

# GenericAPIView中de1filter_queryset()方法
    def filter_queryset(self, queryset):

        for backend in list(self.filter_backends):   
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset

# list(self.filter_backends)会每次从CourseView视图中取配置的filter_backends,如果没有则为空,如果能取到,就一个一个实例化得到对象,调用对象的filter_queryset()
# 所以我们去看OrderingFilter下的filter_queryset()方法

OrderingFilter下的filter_queryset()方法

# OrderingFilter下的filter_queryset()方法
    def filter_queryset(self, request, queryset, view):
        # ordering得到排序的字段列表
        ordering = self.get_ordering(request, queryset, view)

        if ordering:
            return queryset.order_by(*ordering)

        return queryset

SearchFilter

# SearchFilter和OrderingFilter用的是一套的流程

6 异常处理

异常处理用于统一接口返回

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

使用方法:

  • app_auth.py,自定义异常处理
# 异常处理
from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework import status


def my_exception_handler(exc, context):
    response = exception_handler(exc, context)   # 调用一下原有方法,在原有基础上加处理
    # 两种情况,一个是None,drf没有处理,丢给django的,需要我们自己处理
    # response是Response对象,里面有个data
    # print(type(exc))

    if not response:
        if isinstance(exc, ZeroDivisionError):
            return Response(data={'status': 777, 'msg': "除以0的错误" + str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(data={'status': 999, 'msg': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
    else:
        # return response
        return Response(data={'status': 888, 'msg': response.data.get('detail')}, status=status.HTTP_400_BAD_REQUEST)

全局配置settings.py

REST_FRAMEWORK = {
    # "DEFAULT_AUTHENTICATION_CLASSES": ["app01.app_auth.MyAuthentication", ],
    # 'DEFAULT_PERMISSION_CLASSES': [
    #     'app01.app_auth.UserPermission',
    # ],
    # 'DEFAULT_THROTTLE_CLASSES': (
    #     'rest_framework.throttling.AnonRateThrottle',
    # ),

    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ),
    'DEFAULT_THROTTLE_RATES': {
        'user': '10/m',
        'anon': '5/m',
    },
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
    'EXCEPTION_HANDLER': 'app01.app_auth.my_exception_handler',    # 异常处理

}

drf几大组件

 drf几大组件
    请求(APIView源码,Requset对象)和响应(Response,自己封装Response),
    序列化,
    视图,
    路由,
    解析器(DEFAULT_PARSER_CLASSES,全局配置,局部配置),
    响应器(DEFAULT_RENDERER_CLASSES,全局配,局部配),
    认证:校验是否登录(有内置,自定义,全局配置,局部配置)
    权限:是否有权限访问某些接口(有内置,自定义,全局配置,局部配置)
    频率:限制访问频次(有内置,自定义,全局配置,局部配置),根据用户ip,根据用户id限制
    过滤:筛选,查询出符合条件的
    排序:结果进行排序
    异常:全局异常(自定义,全局配置) 
文档更新时间: 2022-04-16 11:27   作者:李延召