- 1 权限Permissions(权限是在认证之后的)
- 1.1 权限源码分析
- 1.2 自定义权限(和认证一样)
- 1.3 内置权限以及认证
- 2 频率限制/限流(Throttling)
- 2.1 自定义频率类
- 自定义频率类
- 简单使用
- 2.2 内置频率类
- 内置频率限制之全局或局部限制登录用户的访问频次
- 限制的用户
- 2.3 SimpleRateThrottle源码分析
- 3 过滤Filtering
- 自定义过滤规则
- django-filter的区间过滤
- django-filters指定以某个字段过滤有两种方式
- 4 排序
- 局部配置
- 全局配置(settings.py中配置排序,视图中配置排序字段)
- 5 过滤与排序补充
- rest_framework自带的过滤和排序使用以及第三方的django-filter的使用
- rest_framework自带的过滤(OrderingFilter)和排序(SearchFilter)的源码流程分析
- 先分析OrderingFilter
- SearchFilter
- 6 异常处理
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限制
过滤:筛选,查询出符合条件的
排序:结果进行排序
异常:全局异常(自定义,全局配置)