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:
        修改图书的阅读量
        """
posted @ 2020-07-19 20:53  Franciszw  阅读(383)  评论(0编辑  收藏  举报