drf-过滤、排序、分页
目录
一 过滤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) # 数据
]))