drf-认证、权限、频率、过滤、排序、分页
1.认证组件
1.1 局部认证
1.首先写两个接口,一个查询单个一个查询所有,我们利用视图扩展类和视图子类写在一个视图类上:
views.py:
from rest_framework.viewsets import ViewSetMixin
from rest_framework.generics import ListAPIView
from rest_framework.mixins import RetrieveModelMixin
class BookView(ViewSetMixin,ListAPIView,RetrieveModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
# ViewSetMixin:负责自动生成路由
# ListAPIView:继承扩展类ListModelMixin,里面有list方法
# RetrieveModelMixin:里面有retrieve方法
serializer.py:
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['name','price','publish','authors','publish_detail','author_detail']
extra_kwargs = {
'name':{'max_length':8},
'price': {'max_length': 8},
'publish_detail':{'read_only':True},
'author_detail':{'read_only':True},
'publish':{'write_only':True},
'authors':{'write_only':True}
}
models.py:
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
class UserToken(models.Model):
token = models.CharField(max_length=64)
user = models.OneToOneField(to='User',on_delete=models.CASCADE)
"""
写在两个视图类上按照以下写法:
"""
views.py:
class BookView(ViewSetMixin,ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookDetailView(ViewSetMixin,RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# RetrieveAPIView继承RetrieveModelMixin和GenericAPIView,GenericAPIView中有queryset和serializer_class方法,所以继承RetrieveAPIView不继承RetrieveModelMixin。
models.py、serializer.py和之前一样
2.接下来我们想在查询单个图书接口上加上认证组件:
1.写一个认证类,继承BaseAuthentication
2.重写authenticate方法,拿到请求的数据(GET请求请求体当中不能携带数据,一般选用在请求头和地址栏当中携带数据),用该数据在数据库UserToken表中查找
3.如果认证成功则返回两个值:用户对象和随机字符串token。认证失败则抛出异常AuthenticationFailed。
4.局部使用:
class BookDetailView(ViewSetMixin,RetrieveAPIView): authentication_classes = [LoginAuth]
3.代码:
authenticate.py:
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('token')
if token:
user_token = UserToken.objects.filter(token=token).first()
if user_token:
return user_token.user,token
else:
raise AuthenticationFailed('token认证失败')
else:
raise AuthenticationFailed('token未上传')
views.py:
class BookDetailView(ViewSetMixin,RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [LoginAuth]
"""
authentication_classes是在:RetrieveAPIView>>>GenericAPIView>>>APIView中,在APIView中完成了三大认证。
"""
serializer.py和models.py中代码未变
1.2 全局认证
在setting.py中设置:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['app01.authenticate.LoginAuth']
}
此时我们发现即使是登陆也需要上传uuid字符串,不符合逻辑,需要在登陆类免除登录设置:
views.py:
class UserView(ViewSet):
# 局部解除全局限制
authentication_classes = []
@action(methods=['POST'],detail=False,url_path='login',url_name='login')
def login(self,request):
username = request.data.get('username')
password = request.data.get('password')
user_obj = User.objects.filter(username=username,password=password).first()
if user_obj:
token = str(uuid.uuid4())
UserToken.objects.update_or_create(user=user_obj,defaults={'token':token})
return Response({'code':100,'msg':'登陆成功','token':token})
else:
return Response({'code':101,'msg':'用户名或密码错误'})
class BookView(ViewSetMixin,ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookDetailView(ViewSetMixin,RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
2.权限组件
2.1 局部权限
1.在某些软件上即使登陆成功,也不能访问,因为没有权限。这是由于在user表中增加了一个字段,用来区分是普通用户还是管理员(会员)。
2.步骤:
1 写一个权限类,继承BasePermission
2 重写has_permission方法,在该方法在中实现权限认证,在这方法中,request.user就是当前登录用户
3 如果有权限,返回True
4 没有权限,返回False,定制返回的中文: self.message='中文'
5 局部使用和全局使用
3.代码:
models.py:
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
user_type = models.IntegerField(choices=((1,'超级会员'),(2,'普通用户')),default=2)
permissions.py:
from rest_framework.permissions import BasePermission
class MyPermission(BasePermission):
def has_permission(self, request, view):
if request.user.user_type == 1:
return True
else:
# self.message是定制的中文提示
self.message = '您是%s,无权访问' % request.user.get_user_type_display()
# get_字段名_display()可以拿到字段中choice中的注释
return False
views.py:
class BookDetailView(ViewSetMixin,RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [LoginAuth]
permission_classes = [MyPermission]
2.2 全局权限
如果很多视图类都要执行该权限,可以在设置中设置全局权限,并且可以对指定的视图类免除该权限:
settings.py:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ['app01.permissions.MyPermission']
}
views.py:
class BookDetailView(ViewSetMixin,RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [LoginAuth]
# 局部禁用
permission_classes = []
permissions.py同上
"""
如果我们用原生的认证和权限,完成该功能:只有登录的用户并且上传token需要配合原生的认证类和权限类使用:
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class CarModelView(ModelViewSet):
queryset = CarMode.objects.all()
serializer_class = CarModelSerializer
authentication_classes = [JSONWebTokenAuthentication] # 校验token有没有传
permission_classes = [IsAuthenticated] # 校验是否登陆
"""
3.频率组件
3.1 局部频率限制
1.方法:
1.写一个频率类,继承SimpleRateThrottle
2.重写get_cache_key方法,返回什么,就以什么做限制(一般以用户ip地址和id做限制)
3.配置一个类属性:scope = 'zkz'
4.在配置文件中配置:m:每分钟;h:每小时;d:每天
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'zkz': '3/m',
}
}
2.代码:
throttling.py:
from rest_framework.throttling import SimpleRateThrottle
class MyThrottle(SimpleRateThrottle):
scope = 'zkz'
def get_cache_key(self, request, view):
# 拿到客户端的ip地址
# print(request.META.get('REMOTE_ADDR')) # 127.0.0.1 本机访问是127.0.0.1
return request.META.get('REMOTE_ADDR')
"""
从地址栏取数据:request.query_params.get('token')
从原生django的cookie中取:request.COOKIE.get('sessioned')
get请求从请求头中取:request.META.get('HTTP_TOKEN')(前端的请求头键是token,经过了包装)
"""
views.py:
class BookDetailView(ViewSetMixin,RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [LoginAuth]
permission_classes = [MyPermission]
throttle_classes = [MyThrottle]
settings.py:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'zkz': '3/m',
}
}
3.2 全局频率限制
settings.py:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'zkz': '3/m',
},
'DEFAULT_THROTTLE_CLASSES':
['app01.throttling.MyThrottle'],
}
视图类中如果不需要频率限制只需要修改:throttle_classes = []
4.过滤
4.1 过滤
只有查询所有才有过滤
方式一:必须要继承GenericAPIView及其子类:
from rest_framework.filters import SearchFilter
class BookView(ViewSetMixin,ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [SearchFilter]
search_fields=['name']
访问的路由要修改成:?search=xxx
eg:?search=三:表示name字段中带有san自的数据对象都可以被筛选出来
"""
search_fields也可以上传多个字段,多个字段内如果都有待匹配的字段,都可以匹配得到:
search_fields=['name','price']
"""
方式二:
利用django-filter模块:可以多个条件同时匹配:
views.py:
from django_filters.rest_framework import DjangoFilterBackend
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name', 'price']
方式三:利用自定义过滤器:自定义一个py文件:filter.py,在里面编写过去条件,返回值是queryset
filter.py:
from rest_framework.filters import BaseFilterBackend
class MyFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
# 可以自定义大于或者小于
price = request.query_params.get('price__gt',None)
if price:
return queryset.filter(price__gt=price)
else:
return queryset
views.py:
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [MyFilter] # 可以定制多个,从左往右排
方式四:使用filterset_class做过滤
filterset_class可以在地址栏携带参数,可以利用参数做精确查询,模糊查询,大于小于比较等:
数据准备:
用法1:
views.py:
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
'''filterset_class需要和 filter_backends = [DjangoFilterBackend]配合使用'''
filter_backends = [DjangoFilterBackend]
filterset_class = BookFilter
'''专门应该有一个过滤文件存放过滤类。在这里暂时写在views.py中'''
class BookFilter(FilterSet):
name = filters.CharFilter(field_name='name', lookup_expr='icontains')
xxx = filters.CharFilter(field_name='price', method='filter_xxx')
# field_name是表中的字段,必须要和模型表中的字段保持一致。xxx是别名,写了之后地址栏可以使用xxx指定参数
class Meta:
model = Book
fields="__all__"
'''在地址栏使用xxx=值时就会触发method指定的方法。比如我地址栏携带的参数是:?xxx=888'''
def filter_xxx(self, queryset, name, value):
print(queryset) # <QuerySet [<Book: Book object (1)>, <Book: Book object (2)>, <Book: Book object (3)>, <Book: Book object (4)>]>
print(name) # price 字段对的名称,打印结果为field_name指定的参数
print(value) # 888
'''在这里添加过滤条件,但是必须要返回一个queryset。在这里形参的queryset是视图类中的queryset,并且我们在过滤类中没有做任何处理,直接返回了传入的全部queryset,所以我们查到的数据就是全部的queryset'''
return queryset.filter()
用法2:模糊查询
由于在在过滤类中有参数lookup_expr='icontains'的限制,所以只要我们输入name字段对应的值的部分内容,就能查到数据:
?name=三
底层代码分析:
代码:
views.py:
import django_filters
class TestFilter(django_filters.rest_framework.FilterSet):
name = django_filters.CharFilter(field_name='name',lookup_expr='icontains')
price = django_filters.CharFilter(method='price_method')
@property
def qs(self):
print('11qs')
parent = super().qs
return parent
@staticmethod
def price_method(queryset,name,value):
print('queryset,name,value',queryset,name,value)
return queryset.filter(price=value)
from rest_framework.viewsets import ModelViewSet
'''视图类,这种方法必须要配合filter_backends和filterset_class一起使用,必须指定filter_backends和filterset_class'''
class TestBookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = TestFilter
serializer.py:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
models.py:
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.IntegerField()
步骤一:执行到视图类中时,会执行过滤类TestFilter,过滤类必须继承django_filters.rest_framework.FilterSet及其子类,才能有django_filters,qs等方法。
CharFilter的参数:
field_name:指定模型表中的字段名。指定字段名之后在路由中就可以用接收的变量名来替代表模型中的变量名
lookup_expr:指定匹配方式,icontains是指模糊查询,只要查询到值中的某一个或几个字符,就可以查询到
method:指定一个方法,该方法在过滤类中写。
步骤二:不管个过滤方式是lookup_expr进行匹配,还是通过method指定方法过滤,都需要首先走过滤类中的qs方法,如果过滤类中没有该方法,需要到其父类BaseFilterSet中找。BaseFilterSet中的qs方法:
在其中有一个filter_queryset方法:该方法是真正执行过滤逻辑的方法。所以过滤的逻辑可以在这里写,也可以在过滤类中用method指定方法写:
def filter_queryset(self, queryset):
'''此时的queryset是视图类中的queryset,没有做任何过滤处理'''
print('queryset1',queryset) # queryset1 <QuerySet [<Book: Book object (1)>, <Book: Book object (2)>, <Book: Book object (3)>, <Book: Book object (4)>, <Book: Book object (5)>, <Book: Book object (6)>, <Book: Book object (7)>, <Book: Book object (8)>]>
for name, value in self.form.cleaned_data.items():
'''name,value就是路由中携带的参数,比如此时name就是name,value就是三'''
queryset = self.filters[name].filter(queryset, value)
assert isinstance(queryset, models.QuerySet), \
"Expected '%s.%s' to return a QuerySet, but got a %s instead." \
% (type(self).__name__, name, type(queryset).__name__)
'''此时的queryset是做完逻辑处理的queryset'''
print('queryset2',queryset) # queryset2 <QuerySet [<Book: Book object (1)>]>
return queryset
步骤三:如果有被method指定的方法,那么过滤逻辑同样会走qs方法,只不过返回的数据以method指定的方法为准。被@staticmethod修饰时,有以下几个参数:queryset,name,value。queryset就是视图类中没有进行任何过滤的queryset,name和value就是路由中携带的变量名和值。如果该方法没有被@staticmethod修饰,需要在前面多加一个形参self。可以在该方法中对queryset进行过滤,该方法必须要返回一个queryset
如果出现以下报错:
可能是由于rest_framework和django_filters没有注册APP的原因
5.排序
from rest_framework.filters import OrderingFilter
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 可以先过滤在排序,也可以先排序在过滤,也可以只排序或过滤
filter_backends = [MyFilter,OrderingFilter]
ordering_fields = ['price']
# 路由:http://127.0.0.1:8000/api/v1/books/?ordering=price:升序
#http://127.0.0.1:8000/api/v1/books/?ordering=-price:降序
"""
也可以按照多个字段排序:
ordering_fields = ['id','price']
路由:http://127.0.0.1:8000/api/v1/books/?ordering=price,-id:按照价格升序,按照id降序
"""
6.分页
方式一:自定义一个分页类:
mypage.py:
from rest_framework.pagination import PageNumberPagination
class MyPage(PageNumberPagination):
page_size = 2 # 每页显示2条
page_query_param = 'page' # page=10:查询第10页的数据
page_size_query_param = 'size' # page=10&size=5:查询10页每页显示5条
max_page_size = 5 # 每页最大显示条数
views.py:
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [MyFilter,OrderingFilter]
ordering_fields = ['id','price']
# pagination_class只能选一种,所以不能加列表
pagination_class = MyPage
方式二:
LimitOffset
class CommonLimitOffsetPagination(LimitOffsetPagination):
default_limit = 3 # 每页显示2条
limit_query_param = 'limit' # limit=3 取3条
offset_query_param = 'offset' # offset=1 从第一个位置开始,取limit条
max_limit = 5
# offset=3&limit=2 0 1 2 3 4 5
方式三:
class CommonCursorPagination(CursorPagination):
cursor_query_param = 'cursor' # 查询参数
page_size = 2 # 每页多少条
ordering = 'id' # 排序字段
# 配置在视图类上即可
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = []
authentication_classes = []
throttle_classes = []
# 之前的东西一样用 ,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用
# pagination_class = PageNumberPagination
#基本分页方式(基本是这种,网页端):http://127.0.0.1:8000/api/v1/books/?page=2&size=3
# pagination_class = LimitOffsetPagination
# 偏移分页 http://127.0.0.1:8000/api/v1/books/?limit=4&offset=1
# 从第一条开始,取4条
pagination_class = CommonCursorPagination
# 游标分页,只能下一页,上一页,不能跳到中间,但它的效率最高,大数据量分页,使用这种较好