DRF 6 过滤排序分页含源码详解
6,过滤,排序,分页,自动生成接口文档
6.1 过滤和排序
drf源生排序过滤
使用:GenericAPIView和ListModelMixin的配合使用效果最佳,APIView单独使用排序器原理和使用分页类似不在赘述.
先配置后使用
全局配置全局生效:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.OrderingFilter',
'django_filters.rest_framework.SearchFilter',)
}
局部配:写在视图类配置过滤类和过滤字段
filter_backends = [OrderingFilter,SearchFilter]
ordering_fields = ('id', 'price')
search_fields= ('name',)
使用:
class Book2View(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
#如果全局配置了过滤,局部使用排序和过滤,需要在局部里再额外加入SearchFilter,因为它们使用同一个 filter_backends,局部配置会的覆盖全局的配置导致局部没有过滤功能.
filter_backends = [OrderingFilter,SearchFilter]
ordering_fields = ('id', 'price')
search_fields= ('name',)
url传参:
http://127.0.0.1:8000/books2/?ordering=-price #倒叙
http://127.0.0.1:8000/books2/?ordering=price,id&search=name
源码分析:
只copy部分关键代码分析
1,OrderingFilter部分源码
def filter_queryset(self, request, queryset, view):
#取出参数的排序字段返回一个列表
ordering = self.get_ordering(request, queryset, view)
if ordering:
#讲参数列表打散并把queryset排序
return queryset.order_by(*ordering)
return queryset
def get_ordering(self, request, queryset, view):
params = request.query_params.get(self.ordering_param)
#ordering_param,rest-framework的settings默认的排序和筛选参数如下:
"""
'SEARCH_PARAM': 'search',
'ORDERING_PARAM': 'ordering',
"""
if params:
fields = [param.strip() for param in params.split(',')]
ordering = self.remove_invalid_fields(queryset, fields, view, request)
if ordering:
return ordering
return self.get_default_ordering(view)
2,SearchFilter部分源码
def filter_queryset(self, request, queryset, view):
#获取视图类中配置的过滤字段
search_fields = self.get_search_fields(view, request)
#根据SEARCH_PARAM获取URL的过滤参数
search_terms = self.get_search_terms(request)
if not search_fields or not search_terms:
return queryset
orm_lookups = [
self.construct_search(str(search_field))
for search_field in search_fields
]
base = queryset
conditions = []
for search_term in search_terms:
queries = [
models.Q(**{orm_lookup: search_term})
for orm_lookup in orm_lookups
]
conditions.append(reduce(operator.or_, queries))
queryset = queryset.filter(reduce(operator.and_, conditions))
if self.must_call_distinct(queryset, search_fields):
queryset = distinct(queryset, base)
#返回过滤后的queryset
return queryset
2,GenericAPIView部分源码
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
#取出配置的排序过滤类生成对象,并调用对象的filter_queryset方法得到排序过滤后的queryset
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
3,ListModelMixin部分源码
class ListModelMixin:
def list(self, request, *args, **kwargs):
#调用GenericAPIView重写的filter_queryset方法得到排序过滤后的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)
源生的SearchFilter有一个缺陷,不能根据外键过滤,所以我们引入了django-filter 插件
6.2 使用django-filter 插件
django-filter 插件的作用:
1,支持外键字段过滤
2,改变过滤字段的传参形式
安装:pip3 install django-filter
注册,在app中注册
先配置后使用
全局配:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
局部配:写在视图类里
filter_backends = [DjangoFilterBackend]
使用:
#视图层必须继承了 GenericAPIView或其子类 的视图类才能使用过滤器
#视图层必须配置django-filter的过滤字段 filter_fields = ('name',)或者配一个类
方法一:配置过滤字段
class BookView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend]
filter_fields = ('name',)
传参:
http://127.0.0.1:8000/books2/?name=xxx
方法二:配置类实现区间过滤
filters.py
class CourseFilterSet(FilterSet):#继承的类是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'] #过滤的字段
视图层
class BookView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend]
filter_class = CourseFilterSet
源码分析:
DjangoFilterBackend部分源码
过程:将配置的过滤字段或类整合,校验,用qs过滤
filter_queryset这个方法就是让GenericAPIView来调用的
def filter_queryset(self, request, queryset, view):
filterset = self.get_filterset(request, queryset, view)
if filterset is None:
return queryset
if not filterset.is_valid() and self.raise_exception:
raise utils.translate_validation(filterset.errors)
return filterset.qs
6.3 自定义过滤器
对于一些特殊需求,以上两种方法都不能满足,可以考虑自定义一个过滤类
# 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]
# 使用:在视图类中配置
filter_backends=[DjangoFilterBackend,OrderingFilter,MyFilter]
6.4 分页器
对查询结果分页,默认提供了3中分页器类
1,先说GenericAPIView和ListModelMixin的配合使用:
使用方法:
# 查所有,才需要分页
# 内置三种分页方式依次只能使用一种
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
PageNumberPagination的使用举例:
1)直接配置使用默认功能:
在项目的settings配置全局使用:
REST_FRAMEWORK = {'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 5, }
在视图类里配置局部使用:
pagination_classes=MyPageNumberPagination
2)分页器类功能介绍:
这里以重写分页器类的参数的方式来介绍
PageNumberPagination
class MyPageNumberPagination(PageNumberPagination):
page_query_param='page' #查询第几页的key
#传参举例:page=2,获取第二页
page_size_query_param='size' # 每一页显示的条数的key
#传参举例:size=3,一页返回3条数据
max_page_size=5 # 默认每页最大显示条数
page_size=3 #置默认值每页返回条数
LimitOffsetPagination
class MyLimitOffsetPagination(LimitOffsetPagination):
offset_query_param = 'offset' # 标杆
#传参举例:offset=4,从地条数据开始
limit_query_param = 'limit' # 往后拿几条
#传参举例:limit=3,获取3条数据
max_limit = 5 # 默认每页最大几条
default_limit = 3 # 默认每次返回几条
CursorPagination:该类只有上一页和下一页两个选项
class MyCursorPagination(CursorPagination):
ordering = '-id' #默认按哪个字段排序
cursor_query_param = 'cursor' # 每一页查询的key
#传参举例:cursor=3,游标放到地3条数据上
page_size = 2 #从游标开始往后或往前取几条为一页
源码分析:
1,GenericAPIView部分
@property
def paginator(self):
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
#调用视图类配置的分页器类并生成对象
return self._paginator
def paginate_queryset(self, queryset):
if self.paginator is None:
return None
#重写了分页器类的paginate_queryset方法,内部还是调用了分页器类的该方法
return self.paginator.paginate_queryset(queryset, self.request, view=self)
def get_paginated_response(self, data):
#重写了分页器类的paginator.get_paginated_response方法,内部还是调用了分页器类的该方法
assert self.paginator is not None
return self.paginator.get_paginated_response(data)
2,ListModelMixin部分
class ListModelMixin:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
#调用了GenericAPIView的paginate_queryset方法
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
#调用了GenericAPIView的get_paginated_response方法
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
3,PageNumberPagination部分源码
def paginate_queryset(self, queryset, request, view=None):
#该函数将queryset分页,返回一个列表,源码过多不与展示
def get_paginated_response(self, data):
#该函数返回带有分页的格式的数据
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('results', data)
]))
def get_next_link(self):
#该函数返回下一页的标签
if not self.page.has_next():
return None
url = self.request.build_absolute_uri()
page_number = self.page.next_page_number()
return replace_query_param(url, self.page_query_param, page_number)
def get_previous_link(self):
#该函数返回上一页的标签
if not self.page.has_previous():
return None
url = self.request.build_absolute_uri()
page_number = self.page.previous_page_number()
if page_number == 1:
return remove_query_param(url, self.page_query_param)
return replace_query_param(url, self.page_query_param, page_number)
可以看出内部逻辑是GenericAPIView重写了分页器类的两个关键方法,ListModelMixin取调用了GenericAPIView重写的方法
2,GenericAPIView单独使用(不推荐使用)
由上述的源码可以看出,自己手动调用GenericAPIView重写的两个关键方法
3,APIView单独使用分页
由于APIView没有提供在视图类里配置分页器类的功能,如果想要使用分页器,需要直接调用分页器类对象的方法
举例:
class BookView(APIView):
def get(self,request,*args,**kwargs):
book_list=models.Book.objects.all()
# 实例化得到一个分页器对象
page_cursor=PageNumberPagination()
# 调用分页器对象的分页功能,需要传入3个参数
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)
#包装结果
dic={"data":book_ser.data,"next_url":next_url,"pr_url":pr_url}
return Response(data=dic)
6.3 自动生成接口文档
插件有coreapi和swagger,这里以coreapi举例
# 1 安装:pip install coreapi
# 2 配置
settings配置:
REST_FRAMEWORK={
'DEFAULT_SCHENA_CLASSES':('rest_framework.schemas.coreapi.AutoSchema'),
}
路由配置:
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='站点页面标题'))
]
#3 视图类:自动接口文档能生成的是继承自APIView及其子类的视图。
-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:
修改图书的阅读量
"""