drf-过滤、排序、分页

发布时间 2023-05-27 20:14:19作者: 星空看海

一 过滤Filtering

前提条件:

  • 带过滤的接口只有:查询所有
  • 必须是继承GenericAPIView及其子类,才能用。
  • 过滤有三种方式
# restful规范中
	-请求地址中带过滤条件

# 加快筛选速度的方法:使用多个过滤类时,最左侧直接把大部分数据过滤掉。

1.1 内置过滤类

from rest_framework.filters import SearchFilter

class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [SearchFilter, ]
    # 配合的字段
    # search_fields = ['name']
    # 只支持这种搜索方式,模糊匹配
    # http://127.0.0.1:8000/api/v1/books/?search=红

    # 多个字段
    # http://127.0.0.1:8000/api/v1/books/?search=33  只要name或price中带33都能搜出来
    search_fields = ['name', 'price']

1.2 第三方过滤类

# pip3 install django-filter
from django_filters.rest_framework import DjangoFilterBackend

class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [DjangoFilterBackend, ]
    # 配合的字段
    # filterset_fields = ['name']
    # 可以使用字段名,精准匹配
    # http://127.0.0.1:8000/api/v1/books/?name=红楼梦

    # 多个字段
    # http://127.0.0.1:8000/api/v1/books/?name=红楼梦&price=2
    # 按名字和价格精准匹配
    filterset_fields = ['name', 'price']

1.3 自定义过滤类

价格等于44或者书名是红楼梦

视图类:

from .filter import MyFilter

class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [MyFilter, ]
    # 自己写的过滤类,返回的数据,就是过滤后的数据,所以不需要配合字段了
    # 地址:http://127.0.0.1:8000/api/v1/books/?price=44&name=红楼梦

filter.py文件

from rest_framework.filters import BaseFilterBackend
from django.db.models import Q

# 自己写的过滤类
class MyFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # queryset是要序列化的数据对象,queryset对象
        # 返回的数据,就是过滤后的数据
        # http://127.0.0.1:8000/api/v1/books/?price=44&name=红楼梦   按名字或价格
        # 地址中就只能写这种,我们要自己获取问号后的数据,然后自己写过滤条件
        price = request.query_params.get('price')
        name = request.query_params.get('name')
        queryset = queryset.filter(Q(name=name) | Q(price=price))
        return queryset

价格在100----200之间的图书

filter.py文件:

class ChangeFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 前端传入的数据是: http://127.0.0.1:8000/api/v1/books/?min_price=100&max_price=200
        min_price = request.query_params.get('min_price')
        max_price = request.query_params.get('max_price')
        # queryset = queryset.filter(price__range=[min_price, max_price])
        queryset = queryset.filter(price__gte=min_price, price__lte=max_price)
        return queryset

二 排序

前提条件:

  • 带过滤的接口只有:查询所有
  • 必须是继承GenericAPIView及其子类,才能用。
  • 过滤有三种方式

对于列表数据,REST framework提供了OrderingFilter过滤器来帮助我们快速指明数据按照指定字段进行排序。

使用方法:

在类视图中设置filter_backends,使用rest_framework.filters.OrderingFilter过滤器,REST framework会在请求的查询字符串参数中检查是否包含了ordering参数,如果包含了ordering参数,则按照ordering参数指明的排序字段对数据集进行排序。

前端可以传递的ordering参数的可选字段值需要在ordering_fields中指明。

视图类

# restful规范中
	-请求地址中带过滤条件
# 排序功能的接口:查询所有
# 必须是继承GenericAPIView及其子类,才能用,drf提供了要给排序类,用它的就可以了。
# 如果继承APIViwe,不能这么用

from rest_framework.filters import OrderingFilter


class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [OrderingFilter]  # 排序类
    # 配合一个类属性,按哪些字段可以排序
    # 1.单个字段
    # 地址写法:http://127.0.0.1:8000/api/v1/books/?ordering=price
    # 按照价格字段升序
    # ordering_fields = ['price']
    # /?ordering=-price,按照价格降序,地址中字段前面加符号

    # 2.多个字段
    ordering_fields = ['price', 'id']
    # http://127.0.0.1:8000/api/v1/books/?ordering=-price,id
    # 先按价格倒序排,价格一样,再按id升序排
    # http://127.0.0.1:8000/api/v1/books/?ordering=-price
    # 写了多个字段可以只使用1个

过滤和排序一起使用

# 过滤和排序可以一起使用
from rest_framework.filters import OrderingFilter
from rest_framework.filters import SearchFilter


class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ['name', 'price']
    ordering_fields = ['price']
    # 地址:http://127.0.0.1:8000/api/v1/books/?search=33&ordering=price

三 分页Pagination

查询所有的接口都需要有分页功能。

    -分页的展现形式
    	web:下一页点解
        app,小程序:下滑下一页
    -接口都一样,要支持分页

视图类

# drf提供给咱们,三种分页方式
# 基本分页
# 偏移分页
# 游标分页


from .page import MyPageNumberPagination,MyLimitOffsetPagination,MyCursorPagination
# 这种方式用的多
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier
    # http://127.0.0.1:8000/api/v1/books/?page=2&page_size=3
    # pagination_class = MyPageNumberPagination  # 只能按一种方式分页,不要放到列表中了

    # http://127.0.0.1:8000/api/v1/books/?limit=4&offset=3  # 从第三条数据开始,取4条
    # pagination_class = MyLimitOffsetPagination  # 只能按一种方式分页,不要放到列表中了

    # 只能点击上一页或下一页
    # http://127.0.0.1:8000/api/v1/books/
    pagination_class = MyCursorPagination  # 只能按一种方式分页,不要放到列表中了

page.py

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination


## 基本分页
class MyPageNumberPagination(PageNumberPagination):
    # 重写几个类属性 :4个
    page_size = 2  # 每页显示的条数
    page_query_param = 'page'  # page=4 表示当前是第4页
    page_size_query_param = 'page_size'  # page=4&page_size=5 表示查询第4页,每页显示5条
    max_page_size = 5  # 每页最大显示多少条


## 偏移分页
class MyLimitOffsetPagination(LimitOffsetPagination):
    # 重写几个类属性 :4个
    default_limit = 2  # 每页显示多少条
    limit_query_param = 'limit'  # limit=3  这一页取3条
    offset_query_param = 'offset'  # 偏移量是多少  offset=3&limit=2  从第3条开始,拿2条
    max_limit = 5  # 最多取5条


## 游标分页,只能上一页和下一页,不能直接跳到某一页,但是这个的速度快---> app上用它多
class MyCursorPagination(CursorPagination):
    # 重写几个类属性 :3个
    cursor_query_param = 'cursor'  # 查询参数,其实用不到
    page_size = 2  # 每页显示多少条
    ordering = 'id'  # 必须是要分页的数据表中的字段,一般按id来

四 基于APIView写过滤、排序和分页

上面都是继承GenericAPIView写的,现在我们来继承APIView写这三种形式。

4.1 写过滤和排序

# 继承了APIView就不能用drf自己的内置类了,针对的不同情况就需要我们自己判断了
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book
from .serializer import BookSerializer


class BookView(APIView):
    # 前端传入的网址:
    # /books/?ordering=-price&name=红楼梦
    def get(self, request):
        # 取出地址中过滤条件的参数
        ordering = request.query_params.get('ordering')
        name = request.query_params.get('name')
        book_list = Book.objects.all()
        if name:  # 过滤
            book_list = book_list.filter(name__contains='红')
        if ordering:  # 排序
            book_list = book_list.order_by(ordering)
        ser = BookSerializer(instance=book_list, many=True)
        return Response({'code': 100, 'msg': '查询成功', 'data': ser.data})

    
# 查询地址:http://127.0.0.1:8000/book/?ordering=-price&name=%E7%BA%A2
# 路由:
    path('book/', views.BookView.as_view()),

4.2 写分页

from rest_framework.mixins import ListModelMixin
from rest_framework.generics import GenericAPIView
from .serializer import BookSerializer
from rest_framework.views import APIView
from rest_framework.response import Response

from .page import MyPageNumberPagination


class BookView(APIView):
    def get(self, request):
        ordering = request.query_params.get('ordering')
        name = request.query_params.get('name')
        book_list = Book.objects.all()
        if ordering:
            book_list = book_list.order_by(ordering)
        if name:
            book_list = book_list.filter(name__contains=name)


        # 加入分页

        # page=MyPageNumberPagination().paginate_queryset(book_list, request,self)
        # 得到分页对象
        pagination = MyPageNumberPagination()
        # 分页对象调用它的paginate_queryset方法
        page = pagination.paginate_queryset(book_list, request, self)
        # 把page序列化
        ser = BookSerializer(instance=page, many=True)
        # 分页类对象调用分页父类中的get_paginated_response方法
        return pagination.get_paginated_response(ser.data)
        # 模仿get_paginated_response中的返回值Response
        return Response({'code': 100, 'msg': '成功',
                         'count': pagination.page.paginator.count,
                         'next': pagination.get_next_link(),
                         'previous': pagination.get_previous_link(),
                         'results': ser.data})


''' 根据源码写自己的分页
# 配置的分页类的对象调用了paginate_queryset(queryset, self.request, view=self)
page = self.paginate_queryset(queryset) #  是paginate_queryset的GenericAPIView
##### 重写了这句话

if page is not None:
    # 把page进行了序列化
   serializer = self.get_serializer(page, many=True)
   # 返回格式
   return self.get_paginated_response(serializer.data) # GenericAPIView--》self.paginator.get_paginated_response(data)


PageNumberPagination类的get_paginated_response方法
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)
            ]))
'''

4.3 分页的源码

-1.入口,查询所有才会有分页,入口就在ListModelMixin类的list方法中
class ListModelMixin:
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        # 视图扩展类不会单独使用,会与GenericAPIView一起使用
	    # self是视图类的对象
        # self.paginate_queryset(queryset)是GenericAPIView中的方法
        # 最后结果是:自己写的分页类的对象调用了它的paginate_queryset(queryset, self.request, view=self)方法,并传了一些参数---> 第4步
        page = self.paginate_queryset(queryset)  # 分页对象
        if page is not None:
            # 把page进行了序列化
            serializer = self.get_serializer(page, many=True)
            # 返回格式  ---> 第5步
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)
    
    
-2. GenericAPIView中的paginate_queryset方法
    def paginate_queryset(self, queryset):
        if self.paginator is None:
            return None
        # self.paginator--> 配置的分页类的对象
        # 分页类对象调用了paginate_queryset方法
        return self.paginator.paginate_queryset(queryset, self.request, view=self)
    
-3.paginator: 是GenericAPIView中方法
    @property
    def paginator(self):
        if not hasattr(self, '_paginator'):
            if self.pagination_class is None:
                self._paginator = None
            else:
                # ***
                # self是视图类的对象
                # 如果分页类存在,就加括号,把对象赋值给self._paginator
                self._paginator = self.pagination_class()
        return self._paginator  # 配置的分页类的对象

-4 PageNumberPagination一个分页类的paginate_queryset方法
class PageNumberPagination(BasePagination):
    def paginate_queryset(self, queryset, request, view=None):
        # 这个只关注传入进来的参数即可
        self.request = request
        return list(self.page)

    
# self.get_paginated_response(serializer.data)    
-5 get_paginated_response这个方法ListModelMixin中没有,所以又找到了GenericAPIView中
    def get_paginated_response(self, data):
        # self是视图类的对象
        # data就是serializer.data,序列化后的数据
        assert self.paginator is not None  # 分页类的对象不是None
        # 返回分页类的对象调用get_paginated_response(serializer.data)方法,并把序列化后的数据传入
        return self.paginator.get_paginated_response(data)
    
    
-6 PageNumberPagination类的get_paginated_response方法
    def get_paginated_response(self, data):
        # self是分页类的对象
        return Response(OrderedDict([
            ('count', self.page.paginator.count),  # 总数量
            ('next', self.get_next_link()),  # 下一页的地址
            ('previous', self.get_previous_link()),  # 上一页的地址
            ('results', data)  # 数据
        ]))