目录
分页,过滤,排序是针对数据进行的操作,主要是指为了减少数据库查询的压力。
分页
1. 定义一个分页类,继承drf提供的三个分页类,重写其中的属性
2. 视图函数必须继承GenericAPIView才能使用分页, 在视图类中加一个pagination_class属性,属性值是自定义的分页类, 一般只有查询所有数据的需求时才会用到分页
pagination.py
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination
class PageNumber(PageNumberPagination):
page_size = 3 # 每页展示数据个数
page_query_param = 'page' # 前端发送的页数关键字名,默认为”page”
page_size_query_param = 'size' # 前端发送的每页数目关键字名,默认为None
max_page_size = 10 # 前端最多能设置的每页数量
class LimitOffset(LimitOffsetPagination):
default_limit = 3 # 每页展示数据个数
limit_query_param = 'limit' # 前端发送的页数关键字名,默认为”limit”
offset_query_param = 'offset' # 偏移值
max_limit = 5 # 最大limit限制,默认None
class Cursor(CursorPagination):
cursor_query_param = 'cursor' # 默认查询关键字
page_size = 3 # 每页展示数据个数
ordering = 'id' # 按什么排序,需要指定
views.py
from .pagination import PageNumber, LimitOffset, Cursor
class BookView(ViewSetMixin, CreateModelMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializers
pagination_class = Cursor
分页源码
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)
def paginate_queryset(self, queryset):
"""
Return a single page of results, or `None` if pagination is disabled.
"""
if self.paginator is None:
return None
return self.paginator.paginate_queryset(queryset, self.request, view=self)
@property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator
排序
查询所有才涉及到排序,其他的接口不需要排序
1, 视图类必须继承GenericAPIView需要用get方法
2, 在视图类中指定filter_backends属性,是一个列表里边放排序类
3, 在视图类中指定排序字段ordering_fields是一个列表可以房多个字段
from rest_framework.filters import OrderingFilter, SearchFilter
class BookView(ViewSetMixin, ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializers
filter_backends = [OrderingFilter]
ordering_fields = ['price', id]
前端能支持的访问格式
http://127.0.0.1:8000/books/?ordering=price,id
先按照价格的升序排, 如果价格一样,按照id的升序排,-id是按照降序排
分页和排序可以一起使用, 先进行排序再分页
过滤
查询所有才会涉及到过滤
1, 视图类必须要继承GenericAPIView,要有get方法
2, 在视图类中指定filter_backends属性, 是一个列表里边放过滤器
3, 在视图类中指定排序字段search_fields,是哟个列表,可以放多个字段
from rest_framework.filters import OrderingFilter, SearchFilter
class BookView(ViewSetMixin, ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializers
filter_backends = [SearchFilter]
search_fields = ['name']
前端支持的访问格式
http://127.0.0.1:8000/books/?search=xxx
内置的过滤如果配置多了个字段,过滤的条件是或的关系, 只要符合一个条件就可以
过滤类的其他使用
在内置的过滤类中,只能通过search=搜索条件,这种方式进行搜索,条件一=xxx&条件二=xxx
第三方过滤模块:django-filter
1.下载模块pip3 install django-filter,一定要注意,在下载第三方模块时,与其有关联的模块可能会跟着一起更新,所以需要检查一下,是否被自动更新至最新版本
2.导入该模块
from django_filters.rest_framework import DjangoFilterBackend
3.配置在试图类中
filter_backends = [DjangoFilterBackend,]
4.配置搜索的字段
filterset_fields = ['搜索条件一', '搜索条件二']
5.支持的搜索方式
http://127.0.0.1:8000/访问视图/?搜索条件一=xxx&搜索条件二=xxx
6.搜索模式
精准搜索,条件是并且的关系,两个搜索必须精确多一少一都不可,两个只要有一个错误那么就搜索不到
手搓,自定义过滤类
1.自己写一个类方法,但是必须要继承BaseFilterBackend
2.重写某个方法
from rest_framework.filters import BaseFilterBackend
class AsBaseFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
name = request.query_params.get('name',None)
publish = request.query_params.get('publish', None)
queryset = queryset.filter(name__icontains=name,publish__icontains=publish)
return queryset
3.配置在视图类上
filter_backends = [AsBaseFilterBackend,]
多个过滤类和排序类可以共用,filter_backends=[],可以配置多个,执行顺序是从做往右,所以,放在最左侧的尽量先过滤掉大部分数据
过滤排序源码分析
首先回想我们的排序与过滤 想使用他们两个必须继承GenericAPIView + ListModelMixin及其子类 排序类OrderingFiler
只要在视图类中配置filter_backends、filterset_fields&search_fields就可以实现过滤和排序了 内置的过滤类SearchFilter
Django-filter 自定义写一个类 继承BaseFilterBackend 重写filter_queryset 返回的queryset对象 就是过滤或排序后的数据了
再次我们使用排序与过滤的时候只会在获取信息的时候才会用到 所以我们进到源码中查找list方法就好了
def list(self, request, *args, **kwargs):
# self.get_queryset()所有数据,经过了self.filter_queryset返回了qs
# self.filter_queryset完成的过滤
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)
-self.filter_queryset完成了过滤,当前在视图类中,self是视图类的对象,去视图类中找没找到,去父类---》GenericAPIView---》filter_queryset
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
'''
-写的过滤类要重写filter_queryset,返回qs(过滤或排序后)对象
-后期如果不写过滤类,只要在视图类中重写filter_queryset,在里面实现过滤也可以
'''
异常处理
drf中无论是三大认证还是视图类的方法中执行,只要报错(主动抛异常),都能执行一个函数(异常处理的函数)
只要除了异常在APIView的dispatch中捕获了执行了配置文件中的配置REST_FRAMEWORK = {
'EXCEPTION_HANDLER':'app01.exctptions.common_exception_handler'
}
from rest_framework.views import exception_handler
from rest_framework.filters import SearchFilter
from rest_framework.response import Response
import time
import datetime
def common_exception_handler(exc,context):
# 我们之后如果写代码的话,需要记录日志,只要进入了这里那么就一定是出现了异常,只要出现异常我们就可以到日志中寻找是哪里出现异常,也方便了纠错
print('裂开来')
print(exc) # 查看exc是什么错误
print(context) # 查看内容
request = context.get('request') # 获取当前请求的request
# 因为我们只能捕获外面的异常如果这里面有异常是捕获不到的,所以我们应该在内部也有异常捕获
try:
# 获取username
username = request.user.username
except:
username = '没有登录'
ctime = time.time()
path = request.path
method_type = request.method
# 我们提示错误信息
print(f'{username}用户,在{ctime}时间,访问{path}接口,通过{method_type}请求访问,除了错误:{str(exc)}')
# 内置的这个异常处理函数只能处理drf的异常(继承了APIException的异常)
response = exception_handler(exc, context)
# 如果response有值,说明错误被处理了(Http404,PermissionDenied,APIException)
if response:
return Response({'code':701, 'msg':'drf错误,错误原因:%s'%response.data.get('detail','未知错误')})
else: # 如果没有值,这个错误就没有被处理,说明不是drf的错误,而是django的错误
return Response({'code': 601, 'msg': '系统错误,错误原因:%s'% str(exc)})
异常捕获源码分析
# 我们在读APIView源码的时候 我们知道了APIView的dispatch中写了全局异常捕获只要是三大认证或则是视图类代码出错 都会被捕获到
# 然后我们可以看一下它是怎么捕获的
def dispatch(self, request, *args, **kwargs):
try:
self.initial(request, *args, **kwargs)
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc) # 上面只要出错都会返回错误 然后我们可以看一下handle_exception写了啥
# 然后通过查找顺序 最后还是在APIView中找到了该方法
def handle_exception(self, exc):
exception_handler = self.get_exception_handler()
response = exception_handler(exc, context)
return response
# 我们可以知道该方法返回了response 然后我们就可以看到上面的最后的是get_exception_handler赋值给了response
# 然后根据名称的查找顺序最后又是在APIVIew中找到了该方法 所以我们看一下get_exception_handler该方法写了啥
def get_exception_handler(self):
return self.settings.EXCEPTION_HANDLER
# 该方法返回了配置文件中的EXCEPTION_HANDLER
# 然后根据查找顺序我们要去rest_framework中的settings中查找EXCEPTION_HANDLER是什么
# 最后是这个 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler' 可以知道是这个
# 那么我们可以看一下exception_handler该方法写了啥
from rest_framework.views import exception_handler
def exception_handler(exc, context):
if isinstance(exc, Http404): # 这个处理404异常
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied): # 这个是权限异常
exc = exceptions.PermissionDenied()
if isinstance(exc, exceptions.APIException):
# 这个是我们在写认证类抛异常的类AuthenticationFailed继承的类 可以理解为认证异常
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
if isinstance(exc.detail, (list, dict)):
data = exc.detail
else:
data = {'detail': exc.detail}
set_rollback()
return Response(data, status=exc.status_code, headers=headers)
return None # 这个None 就是除了drf的错误 其他错误没有处理
# 该方法就是全局异常最后调用的方法
# 总结
-只要出了异常在APIView的dispatch中捕获了,执行了配置文件中配置的:'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'